VisualVM对OQL的支持
上上篇文章已经简单地介绍了如何通过VisualVM查看堆内存快照中的对象信息。但通常堆内存快照十分庞大,快照中的类数量也很多,很难通过浏览的方式找到所需的内容。为此,VisualVM提供了对OQL(对象查询语言)的支持,以方便开发人员在庞大的堆内存数据中快速定位所需的资源。
一,VisualVM的OQL基本语法
VisualVM的OQL是一种类似于SQL的查询语言。它的基本语法如下:
OQL由3个部分组成:select子句、from子句和where子句。select子句指定查询结果要显示的内容;from子句指定查询范围,可指定类名,如java.lang.String、char[]和Ljava.io.File(File数组);where子句指定查询条件。
select子句和where子句支持使用JavaScript语法处理较为复杂的查询逻辑,select子句可以使用类似JSON的语法输出多个列。from子句中可以使用instanceof关键字,将给定类的子类也包括到输出列表中。
在VisualVM的OQL中,可以直接访问对象的属性和部分方法。如下例中,直接使用了String对象的count属性,筛选出长度大于等于100的字符串。
如果要选取长度大于等于256的int数组,则语法如下:
如果要筛选出表示两位数的整数字符串,则语法如下:
上例中,select子句使用JSON语法,指定输出为String对象和String.toString();where子句使用正则表达式指定符合/^\d{2}$/条件的字符串。本例的部分输出数据如图6.59所示。
下例筛选出所有的文件路径及文件对象,其中调用了类的toString()方法。
下例使用instanceof关键字选取所有的ClassLoader,包括子类。
由于在Java程序中,一个Class类可能会被多个ClassLoader同时载入,因此在这种情况下可能需要使用Class的ID来指定Class。如下例,选出了所有ID为0x37A014D8的Class的对象实例。
注意:VisualVM的OQL语言支持JavaScript作为其子表达式。
二,内置heap对象
heap对象是VisualVMOQL的内置对象,通过heap对象可以实现一些强大的OQL功能。heap对象的主要方法如下:
·forEachClass():对每一个类对象执行一个回调操作。它的使用方法类似于heap.forEachClass(callback),其中callback为JavaScript函数。
·findClass():查找给定名称的类对象,返回类的方法和属性参考表6.3。它的调用方法类似于heap.findClass(className)。
·classes():返回堆快照中所有类的集合。使用方法类似于heap.classes()。
·objects():返回堆快照中所有的对象集合。使用方法类似于heap.objects(clazz,[includeSubtypes],[filter]),其中clazz指定类名称,includeSubtypes指定是否选出子类,filter为过滤器,指定筛选规则。includeSubtypes和filter可以省略。
·livepaths():返回指定对象的存活路径,即显示哪些对象直接或者间接引用了给定对象。它的使用方法类似于heap.livepaths(obj)。
·roots():返回这个堆的根对象。使用方法类似于heap.roots()。表6.3使用findClass()返回的Class对象拥有的属性和方法
例如,查找java.util.Vector类,语法如下:
查找java.util.Vector的所有父类,语法如下:
输出结果如下:
查找所有在java.io包下的对象,语法如下:
查找字符串“56”的引用链,语法如下:
以下是一种可能的输出结果,其中java.lang.String#274即字符串“56”。
查找这个堆的根对象,语法如下:
查找当前堆中所有java.io.File对象的实例,语法如下:
三,对象函数
在VisualVM中还为OQL语言提供了一组以对象为操作目标的内置函数。通过这些函数,可以获取目标对象的更多信息。本节将介绍常用的对象函数。
1.classof()函数
classof()函数返回给定的Java对象的类,调用方法形如classof(objname)。其返回的类对象具有以下属性。
·name:类名称。·superclass:父类。·statics:类的静态变量的名称和值。·fields:类的域信息。
返回的类对象拥有以下方法。·isSubclassOf():是否是指定类的子类。·isSuperclassOf():是否是指定类的父类。·subclasses():返回所有子类。·superclasses():返回所有父类。
下例将返回所有Vector类及子类的类型。
输出结果如下:
2.objectid()函数
objectid()函数返回对象的ID。使用方法为objectid(objname)。下例将返回所有Vector对象(不包含子类)的ID。
3.reachables()函数
reachables()函数返回给定对象的可达对象集合,使用方法为reachables(obj,[filter]),其中obj为给定对象,filter指定忽略给定对象中的某一字段的可达引用。
下例将返回“56”这个String对象的所有可达对象。
输出结果如下:
这里的返回结果是java.lang.String.value域的引用对象,即给定的String类型的value域指向对象char[]#264。如果使用过滤,要求输出结果中不包含java.lang.String.value域的引用对象,代码如下:
以上代码输出结果为空,因为String对象只有value包含对其他对象的引用。
4.referrers()函数referrers()函数返回引用给定对象的对象集合,使用方法为referrers(obj)。
下例将返回引用“56”的String对象的对象集合。
输出结果如下:
这说明一个Object数组和一个File文件对象引用了“56”这个字符串对象。在查询结果中单击java.lang.Object[]#311,可进一步找到引用java.lang.Object[]#311对象的是一个Vector对象,如图6.60所示,这表明“56”字符串被一个Vector所持有。
单击java.io.File#9,可以找到File对象的哪个字段引用了字符串“56”,如图6.61所示。
下例将找出长度为2,并且至少被2个对象引用的字符串。
注意:where子句中使用的逻辑运算符是&&。这是JavaScript语法,不能像SQL一样使用AND操作符。
5.referees()函数
referees()函数返回给定对象的直接引用对象集合,使用方法为referees(obj)。
下例将返回File对象的静态成员引用。
下例将返回长度为2,并且至少被2个对象引用的字符串的直接引用。
6.sizeof()函数
sizeof()函数返回指定对象的大小(不包括它的引用对象),即浅堆(ShallowSize)。
注意:sizeof()函数返回对象的大小不包括对象的引用对象。因
此,sizeof()的返回值由对象的类型决定,和对象的具体内容无关。
下例将返回所有int数组的大小及对象。
下例将返回所有Vector的大小及对象。
输出结果如图6.62所示。
不论Vector集合包含多少对象,Vector对象所占用的内存大小始终为24KB。这是由Vector本身的结构决定的,与其内容无关。sizeof()函数就是返回对象的固有大小。
7.rsizeof()函数
rsizeof()函数返回对象及其引用对象的大小总和,即深堆(RetainedSize)。这个数值不仅与类本身的结构有关,还与对象的当前数据内容有关。
下例显示了所有Vector对象的ShallowSize及RetainedSize。
部分输出结果如图6.63所示。
注意:rsizeof()函数取得对象及其引用对象的大小总和。因此,
它的返回值与对象的当前数据内容有关。
8.toHtml()函数
toHtml()函数将对象转换为HTML显示。
下例将Vector对象的输出使用HTML进行加粗和斜体显示。
输出结果如图6.64所示。
四,集合/统计函数
VisualVM中还有一组用于集合操作和统计的函数,可以方便地对结果集进行后处理或者统计操作。集合/统计函数主要有contains()、count()、filter()、length()、map()、max()、min()、sort()和top()等。
1.contains()函数
contains()函数判断给定集合是否包含满足给定表达式的对象。它的使用方法为contains(set,boolexpression),其中set为给定集合,boolexpression为表达式。在boolexpression中,可以使用contains()函数的如下内置对象。
·it:当前访问对象。·index:当前对象索引。·array:当前迭代的数组/集合。
下例返回被File对象引用的String对象集合。首先通过referrers(s)得到所有引用String对象的对象集合,然后使用contains()函数及其参数布尔等式表达式classof(it).name=='java.io.File'),将contains()的筛选条件设置为类名是java.io.File的对象。
本例的输出结果如下:
通过该OQL,得到了当前堆中所有File对象的文件名称,可以理解为当前Java程序通过java.io.File已打开或持有的所有文件(56为文件名)。
2.count()函数
count()函数返回指定集合内满足给定布尔表达式的对象数量。它的基本使用方法为count(set,[boolexpression])。其中参数set指定要统计总数的集合,boolexpression为布尔条件表达式,可以省略,但如果指定,count()函数将只计算满足表达式的对象个数。在boolexpression表达式中,可以使用以下内置对象。
·it:当前的访问对象。·index:当前的对象索引。·array:当前迭代的数组/集合。
下例将返回堆中所有java.io包中的类的数量,布尔表达式使用正则表达式表示。
下例将返回堆中所有类的数量。
3.filter()函数
filter()函数返回给定集合中,满足某一个布尔表达式的对象子集合,使用方法为filter(set,boolexpression)。在boolexpression中,可以使用以下内置对象。
·it:当前的访问对象。·index:当前的对象索引。
·array:当前迭代的数组/集合。下例将返回所有java.io包中的类。
下例将返回当前堆中,引用了java.io.File对象并且不在java.io包中的所有对象实例。首先使用referrers()函数得到所有引用java.io.File对象的实例,接着使用filter()函数进行过滤,只选取不在java.io包中的对象。
4.length()函数
length()函数返回给定集合的数量,使用方法为length(set)。下例将返回当前堆中所有类的数量。
5.map()函数
map()函数将结果集中的每一个元素按照特定的规则进行转换,以方便输出显示。它的使用方法为map(set,transferCode),其中set为目标集合,transferCode为转换代码。在transferCode中可以使用以下内置对象。
·it:当前的访问对象。·index:当前的对象索引。·array:当前迭代的数组/集合。
下例将当前堆中的所有File对象进行格式化输出。
输出结果为:
注意:map()函数可以用于对输出的数据格式化,它可以将集合中的每一个对象转换成特定的输出格式。
6.max()函数
max()函数用于计算并获得给定集合的最大元素,使用方法为max(set,[express]),其中set为给定的集合,express为比较表达式,指定元素间的比较逻辑。参数express可以省略,若省略,则执行数值比较。参数express可以使用以下内置对象。
·lhs:用于比较的左侧元素。·rhs:用于比较的右侧元素。
下例将显示当前堆中最长的String长度。首先使用heap.objects()函数得到所有String对象,接着使用map()函数将String对象集合转换为String对象的长度集合,最后使用max()函数得到集合中的最大元素。
以上OQL的输出为最大字符串长度,输出结果如下:
下例将取得当前堆的最长字符串。它在max()函数中设置了比较表达式,指定了集合中对象的比较逻辑。
与上例相比,它得到的是最大字符串对象,而非对象的长度,输出结果如下:
7.min()函数
min()函数计算并获得给定集合的最小元素,使用方法为min(set,[expression]),其中set为给定的集合,expression为比较表达式,指定元素间的比较逻辑。参数expression可以省略,若省略,则执行数值比较。参数expression可以使用以下内置对象。
·lhs:用于比较的左侧元素。
·rhs:用于比较的右侧元素。
下例将返回当前堆中数组长度最小的Vector对象的长度。
下例将得到数组元素长度最长的一个Vector对象。
8.sort()函数
sort()函数对指定的集合进行排序。它的一般使用方法为sort(set,expression),其中set为给定的集合,expression为集合中对象的排序逻辑。在expression中可以使用以下内置对象。
·lhs:用于比较的左侧元素。·rhs:用于比较的右侧元素。
下例将当前堆中的所有Vector按照内部数组的大小进行排序。
下例将当前堆中的所有Vector类(包括子类),按照内部数据长度从小到大进行排序,并输出Vector对象的实际大小及对象本身。
9.top()函数
top()函数返回在给定的集合中,按照特定顺序排序的前几个对象。一般使用方法为top(set,expression,num),其中set为给定的集合,expression为排序逻辑,num指定输出前几个对象。在expression中,可以使用以下内置对象。
·lhs:用于比较的左侧元素。
·rhs:用于比较的右侧元素。
下例将显示长度最长的前5个字符串。
下例将显示长度最长的5个字符串,并输出它们的长度与对象。
10.sum()函数
sum()函数用于计算集合的累计值。它的一般使用方法为sum(set,[expression]),其中参数set为给定的集合,参数expression用于映射当前对象到一个整数。参数expression可以省略,如果省略,可以使用map()函数作为替代。
下例将计算所有java.util.Properties对象的可达对象的总大小。
下例将使用sum()函数的第二个参数expression代替map()函数,实现相同的功能。
11.unique()函数
unique()函数将除去指定集合中的重复元素,返回不包含重复元素的集合。它的一般使用方法为unique(set)。
下例将返回当前堆中有多少个不同的字符串。
五,程序化OQL
VisualVM不仅支持在OQL控制台上执行OQL查询语言,也可以通过其OQL相关的JAR包将OQL查询程序化,从而获得更加灵活的对象查询功能,实现堆快照分析的自动化。
在进行OQL开发前,工程需要引用VisualVM安装目录下的JAR包,如图6.65所示。
以下代码是一段程序化OQL查询的示例代码,展示了如何通过程序来获取堆快照中的对象数据。
第9、10行代码指定打开一个堆快照文件,第21行代码打印出堆中所有的类,第32行的visit()方法传入参数Object为OQL查询结果集中的一个对象。visit()方法如果返回false,则将继续访问结果集中的后续对象;如果返回true,则停止访问后续对象。
注意:使用VisualVM的相关JAR包,可以将堆内存分析工作根据应用的自身情况进行自动化处理,大大降低了手工分析的工作量。
本文给大家讲解的内容是visualvm对oql的支持
- 下篇文章给大家讲解的内容是MAT对OQL的支持
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
**