坦白的说,有点标题党,一股走进科学的感觉,其实就是与大家分享一下关于 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,那么你一定记的有一段经典代码,用于演示存在内存泄漏风险的场景。
如果没有读过也没关系,这段代码很简单,笔者也粘贴了过来:
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文件,我将启动参数增加了下面两条:
即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的读者一套新的思路,可不是什么解决方案啊~