前言
坦白的说,有点标题党,一股走进科学的感觉,其实就是与大家分享一下关于OQL的一些内容。
OQL是什么?
它是基于java堆上快照的对象查询语言,语法呢,和我们平常的SQL,HQL等等是近似的,毕竟QL一家亲。
根据它堆上查询的特性,所以自然是可以进行内存诊断,或者出一个你想要的报表等等用途。
笔者本人也比较喜欢使用OQL,毕竟能自己写代码,还是颇为灵活,灵活自然是惹人亲睐的因素。
第二个原因自然是:
今天从介绍,到实际例子,带给还不清楚“OQL”的盆友,多一种调试的思路。
正如《深入理解java虚拟机》所说:“没有什么工具是'秘密武器',拥有了就能‘包治百病’”。 所以多一项技能,也只是多一种调试的思路。
正文
如果你坚持看到了正文,相信你是了解heap dump
是何物,
所以内存结构等概念在本文不过多介绍。
heap dump
分析工具众多,从JConsole到Jhat,以及广为应用的MAT,它们自带的功能已经很强大了,还用自己写OQL吗?
这个好理解,看Report和写SQL,自然不是一个场景,前者是专注特定领域的结果,后者是灵活的开发语言。
先从简单例子开始,后面笔者会用《Effective Java》中那段经典的“可能存在内存泄漏”示例代码,展示怎样写出“有趣的OQL”。
简单的例子
第一步肯定是搞一个heap dump
,既然是先举简单的例子,可以随便找一个heap dump
入手。
Tip: 每个人的工具习惯不一样,笔者今天的操作,比如heap dump,读取hprof文件,以及执行OQL,都是在jVisualVm中进行的。
如果是有经验的盆友,当然可以跳过简单的例子,继续看下面的内容~
首先,先从左侧列表中的幸运进程中抽取一位观众,进行heap dump,看名字应该是笔者正在跑的IDEA中内部进程。
不出意外,会跳到heapDump的分析页面,其它功能暂且掠过,将左上角的下拉框选到今天的主题“OQL Console”。
查询所有“字符串实例”,可以算是OQL中的“hello world”了。 本文也不例外:
select
s
from
java.lang.String s
复制代码
将以上OQL写到下面的文本框中
从上图看出,已经输出了所有String的实例,是不是和普通的DB可视化工具没什么区别?输入完OQL运行,上面就显示了查询结果,顺着查询结果,可以找到该对象的值,引用等等信息。
这里还有一个小细节,其实OQL也是有方言一说,只不过是受限于运行工具不同,
比如在有些书籍上是以select * from java.lang.String
作为第一个入门语句,
但是在笔者当前的visualVM2.0中运行则会报错,MAT下会正常输出。
虽然大体语法是一致的,但是细节上,还要以你习惯使用工具的官网介绍的语法为准。
Effective Java中的例子
如果你阅读过Effective Java,那么你一定记的有一段经典代码,用于演示存在内存泄漏风险的场景。
如果没有读过也没关系,这段代码很简单,笔者也粘贴了过来:
class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
return elements[--size];
}
/**
* Ensure space for at least one more element, roughly
* doubling the capacity each time the array needs to grow.
*/
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
复制代码
代码很简单,一个自己实现的栈,出栈操作pop忘记了消除elements数组中的引用,存在内存泄漏的风险。
那么笔者就用这段代码做演示,看看OQL都能做些什么。
笔者测试代码:
public static void main(String[] args) throws Exception {
// stack1
Stack stack1 = new Stack();
// stack1入栈30个元素
addItem(stack1);
// stack2
Stack stack2 = new Stack();
// stack2入栈30个元素
addItem(stack2);
// stack2出栈20元素
for (int i = 0; i < 20; i++) {
stack2.pop();
}
System.out.println("----Over----");
// 通知full gc, 如果jvm心情不错,可以拿到dump
System.gc();
Thread.sleep(5000);
}
复制代码
笔者的测试很简单,实例化了两个上文的栈,一个没有出栈操作,一个有出栈操作,有出栈操作的实例,自然保存了过期的引用。
为了拿到dump文件,我将启动参数增加了下面两条:
- -XX:+HeapDumpBeforeFullGC
- -XX:HeapDumpPath=/Users/vt/logs/jvm
即FullGC之前dump一下。
继续,一起看看可以用OQL做一些什么事情? 下面的内容为了省略篇幅,OQL和输出结果,用文字Input和Output表示。
Input:
select
s.elements.length
from instanceof
com.vt.example.LeakTest$Stack s
复制代码
Output:
33
33
复制代码
可以看出打印出来两个Stack的实例结果,与测试内容一致。 并且其中elements的长度都是被扩容到了33。
因为结果输出,支持json的格式,我们不妨再多查询一些内容。
Input:
select
{
"elements's length" :s.elements.length,
"instance": s,
"size attr" : s.size,
"count" : (actualCount = count(filter(s.elements, "it != null")))
}
from instanceof
com.vt.example.LeakTest$Stack s
复制代码
Output:
{
instance = com.vt.example.LeakTest$Stack#1,
count = 30,
size attr = 30,
elements's length = 33
}
{
instance = com.vt.example.LeakTest$Stack#2,
count = 30,
size attr = 10,
elements's length = 33
}
复制代码
上面笔者的OQL语句分别查询了,实例地址,实例size属性,实例中数组容器的长度,以及真实的size大小。
真实的size大小是统计了数组容器中不为null的元素。
也就是说,上文提到的stack问题是:出栈时没有把过期的元算置于null,导致了内存泄漏风险。
那么实际看看上面的OQL结果,是不是知道了什么?
没错,那就是第二个实例实际保存的元素,和size属性大小不符,自然是已经发生问题的实例。
但是看的还不是那么清晰,我们再优化一个版本看看如何:
Input:
select
{
"实例中数组容器当前大小" :s.elements.length,
"实例地址": s,
"当前size属性" : s.size,
"实际保存的引用数量" : (actualCount = count(filter(s.elements, "it != null"))),
"疑似泄漏" : (actualCount > s.size) ? "<font color='red'>有</font>" : "无"
}
from instanceof
com.vt.example.LeakTest$Stack s
复制代码
Output:
最后这版本用了中文描述,并且还有文字高亮,是不是更清晰了一些?
用OQL直接查出我们想要的一个报表,依靠基本功能可不一定能做得到哦。
最后
看到这,大家应该对OQL有一个初步的了解和如何简单“玩转”了,本文也就结束了。
实际中,可不是区区上面的OQL就能查出来内存泄漏,内容是演示功能为主,带给不了解OQL的读者一套新的思路,可不是什么解决方案啊~