分析 Java 内存问题时,dump 堆内存,然后导入到 MAT 中进行分析,是每个 Java 程序员手到擒来的事情,但实际上大家的使用浮于表面,很多好用的功能都没用过。
最近做了几次内存快照的分析,发现 OQL 是个好东西,蛮有意思的。以前大多直接使用 MAT 中的各种功能,但比较少去关注 OQL 这块。
Eclipse的Wiki在24年就要准备关闭了。趁着还能访问,赶紧把文档整理一波。
https://wiki.eclipse.org/MemoryAnalyzer/OQL#GROUP_BY
概述
Object Query Language,即OQL,是一种SQL风格的查询语言,用于对堆转储文件进行分析。
示例:
<font style="color:rgb(76, 77, 78);">SELECT * FROM java.lang.String</font>
列出所有String对象 as tree
<font style="color:rgb(76, 77, 78);">SELECT s as String,s.value as "characters" FROM java.lang.String s</font>
列出所有String对象 as table
<font style="color:rgb(76, 77, 78);">SELECT s as String,s.value as "characters", inbounds(s),inbounds(s).@length FROM java.lang.String s</font>
inbounds函数返回一个数组,包含所有直接引用字符串对象 s 的其他对象。
String |characters |inbounds(s)| inbounds(s).@length
--------------------------------------------------------------------------------------------------------------
java.lang.String [id=0x22e58820]|char[] [id=0x22e60f50;length=16;size=48] |[I@620f7a39| 1
java.lang.String [id=0x22e59150]|char[] [id=0x22e62ff0;length=6;size=24] |[I@1f7b8d59| 1
java.lang.String [id=0x22e5b560]|char[] [id=0x22e6b730;length=537;size=1088]|[I@28551755| 1
--------------------------------------------------------------------------------------------------------------
在OQL中有两类对象:IObject(快照中的Java对象)和OQL处理生成的常规Java对象。
java.lang.String [id=0x22e58820]
就是一个代表了String快照的 「I 实例」
char[] [id=0x22e60f50;length=16;size=48]
是一个代表了字符数组快照的「I 数组」
[I@620f7a39
则是由OQL生成的常规Java List 对象,其中包含了多个表示 I 实例的int类型变量
OQL加强版:MAT/Calcite
MAT/Calcite是一个插件,为MAT提供增强能力。比如在OQL中不支持count、group by、sum等等非常常用的功能。但MAT/Calcite支持。MAT/Calcite使用SQL语句,与OQL略有不同。
插件地址:https://github.com/vlsi/mat-calcite-plugin
功能点 | OQL | Calcite |
---|---|---|
基础语法 | SELECT s FROM java.lang.String s | SELECT s.this FROM “java.lang.String” s |
内置函数 | SELECT toString(s), classof(s), s.@objectAddress, s.@usedHeapSize, s.@retainedHeapSize FROM java.lang.String s | SELECT toString(s.this),getType(s.this), getAddress(s.this),shallowSize(s.this),retainedSize(s.this) FROM “java.lang.String” s |
更多函数 | SELECT h, h[0:-1].size(), h.table, h.table.@length, h.modCount, h.getField(“modCount”) FROM java.util.HashMap h | SELECT h.this,getSize(h.this),h.this[‘table’], LENGTH(h.this[‘table’]), h.this[‘modCount’], getField(h.this,‘modCount’) FROM “java.util.HashMap” h |
多行注释 | /* multi-line comment */ | /* multi-line comment */ |
单行注释 | // comment | – comment |
JOIN | 原生不支持,但可通过一定的逻辑模拟 | 支持 |
LIMIT and OFFSET | 原生不支持,但可通过一定的逻辑模拟 | 支持 |
ORDER BY | 语句本身不支持,但可通过点击查询结果的表头来排序 | 支持 |
GROUP BY | 原生不支持,但可通过一定的逻辑模拟。也可以通过Java Basics > Group by Value 进行查询,此外,如果行的背后是一个对象(from子句返回一个对象列表),那么Group by 菜单栏选项允许Group by classloader 和Group by package 。 | 支持 |
COUNT | 原生不支持,但可通过一定的逻辑模拟 | 支持 |
MAX,MIN | 语句本身不支持,但可通过点击查询结果的表头来排序后,手动复制第一个值 | 支持 |
AVG,SUM | 不支持 | 支持 |
Calcite提供高级SQL功能,如JOIN (INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN)、CROSS JOIN、GROUP BY、ORDER BY。其中的一部分如果花点心思,也可以用OQL来模拟。
关于2019年11月的增强
在 Issue 552879 下对 OQL 进行了各种更新和增强:针对子查询、映射、上下文提供者、DISTINCT的OQL增强。这些可从快照构建中获得,用于测试,并且可能会更改。请在bug、论坛或开发邮件列表上发表评论。
SELECT DISTINCT
DISTINCT
曾经只对返回对象的查询结果进行操作,而不是一般的select项。
SELECT DISTINCT OBJECTS classof(s) FROM "java.lang\.S.*" s
而现在,DISTINCT
也可以作用于选择项。它使用整行作为不同的项。
SELECT DISTINCT classof(s) FROM "java.lang\.S.*" s
子查询
以前,子查询只允许返回一个对象列表
SELECT v, v.@length FROM OBJECTS ( SELECT OBJECTS s.value FROM java.lang.String s ) v
现在,可以返回任意元素
SELECT v,v.s,v.val FROM OBJECTS ( SELECT s,s.value as val FROM java.lang.String s ) v
Row |Object |Array
--------------------------------------------------------------------------------------------------------------------------------------------------------------
{s=java.lang.String [id=0x26ba8a30], val=char[] [id=0x26ba8a48;length=14;size=40]} |java.lang.String [id=0x26ba8a30]|char[] [id=0x26ba8a48;length=14;size=40]
{s=java.lang.String [id=0x26ba8998], val=char[] [id=0x26ba89b0;length=56;size=128]}|java.lang.String [id=0x26ba8998]|char[] [id=0x26ba89b0;length=56;size=128]
{s=java.lang.String [id=0x26ba8160], val=char[] [id=0x26ba8178;length=56;size=128]}|java.lang.String [id=0x26ba8160]|char[] [id=0x26ba8178;length=56;size=128]
{s=java.lang.String [id=0x26b9d390], val=char[] [id=0x26b9d3a8;length=15;size=48]} |java.lang.String [id=0x26b9d390]|char[] [id=0x26b9d3a8;length=15;size=48]
{s=java.lang.String [id=0x26b9d358], val=char[] [id=0x26b9d370;length=8;size=32]} |java.lang.String [id=0x26b9d358]|char[] [id=0x26b9d370;length=8;size=32]
--------------------------------------------------------------------------------------------------------------------------------------------------------------
外层查询一行一行的处理子查询的返回结果,使用单个RowMap Map
对象表示行,Map 的键值对就是子查询的结果。如果键是标准的标识符,即通常是字母-数字,那么属性处理可以使用v.s
而不是v.get("s2")
,虽然它仍然可以被使用。
整个子查询仍会返回CustomTableResultSet
,一个IResultTable
实例。但现在已经被增强为一个List<RowMap>
。在OQL中很难对整个结果进行操作,如果它被提供给外部select,那么Iterable
的性质意味着它将逐行处理。
SELECT * FROM OBJECTS ( SELECT s, s.value AS val FROM java.lang.String s ) v
[{s=java.lang.String [id=0x26ba8a30], val=char[] [id=0x26ba8a48;length=14;size=40]}, {s=java.lang.String [id=0x26ba8998], val=char[] [id=0x26ba89b0;length=56;size=128]}, {s=java.lang.String [id=0x26ba8160], val=char[] [id=0x26ba8178;length=56;size=128]}, {s=java.lang.String [id=0x26b9d390], val=char[] [id=0x26b9d3a8;length=15;size=48]}, {s=java.lang.String [id=0x26b9d358], val=char[] [id=0x26b9d370;length=8;size=32]}, {s=java.lang.String [id=0x26b9d318], val=char[] [id=0x26b9d330;length=11;size=40]}, {s=java.lang.String [id=0x26b9d2e8], val=char[] [id=0x26b9d300;length=4;size=24]}, {s=java.lang.String [id=0x26b9c758], val=char[] [id=0x26b9c770;length=21;size=56]}, {s=java.lang.String [id=0x26b9c6c8], val=char[] [id=0x26b9c6e0;length=13;size=40]}, {s=java.lang.String [id=0x26b9c690], val=char[] [id=0x26b9c6a8;length=8;size=32]}, ...
将整个表显示为一个列表
LIMIT 和 OFFSET
可通过以下语句模拟:
SELECT eval((SELECT * FROM OBJECTS ( SELECT s, s.value AS val FROM java.lang.String s ) v))[3] FROM OBJECTS 0
其中:
eval((SELECT * FROM OBJECTS ( SELECT s, s.value AS val FROM java.lang.String s ) v ))[3]
-----------------------------------------------------------------------------------------
{s=java.lang.String [id=0x26b9d390], val=char[] [id=0x26b9d3a8;length=15;size=48]}
-----------------------------------------------------------------------------------------
将整个表作为一个select项,可以模拟SQL的limit和offset。
SELECT z.s FROM OBJECTS ( eval((SELECT s FROM "java.lang.String" s ))[10:29] ) z
上述语句提取了20个条目,跳过了前10个。注意数组切片处理时,起始和结束偏移量都是基于0但包含0的。
当然,如果用Calcite实现就简单的多:
SELECT s.this FROM "java.lang.String" s LIMIT 10 offset 20
object列的上下文菜单
如果有一列是用来表示堆内对象或者堆内对象的数组/列表,则上下文菜单提供了一个选项来处理所选行的列的项目。
SELECT s AS String, s.value AS "Char array", inbounds(s) AS Inbounds FROM java.lang.String s
结果如下:
String |Char array |Inbounds
--------------------------------------------------------------------------------------
java.lang.String [id=0x26ba8a30]|char[] [id=0x26ba8a48;length=14;size=40] |[I@6ad112de
java.lang.String [id=0x26ba8998]|char[] [id=0x26ba89b0;length=56;size=128]|[I@18a0721b
java.lang.String [id=0x26ba8160]|char[] [id=0x26ba8178;length=56;size=128]|[I@2ae2fa13
--------------------------------------------------------------------------------------
上下文菜单:
SELECT … s
整行——基于底层对象。对应OQL:
SELECT s AS String, s.value AS "Char array", inbounds(s) AS Inbounds FROM OBJECTS 20798,20796,20793 s
String
仅指查询出来的String字段。对应OQL:
SELECT s AS String FROM OBJECTS 20798,20796,20793 s
Char array
仅指查询出来的Char array字段。对应OQL:
SELECT s.value AS "Char array" FROM OBJECTS 20798,20796,20793 s
Inbounds
所有直接引用了该对象的对象。对应OQL:
SELECT inbounds(s) AS Inbounds FROM OBJECTS 20798,20796,20793 s
上下文菜单中有一个Copy > OQL Query
选项,可以拷贝出代表查询该行数据的OQL。
将结果表转换为HTML的报告插件现在使用上下文菜单名称来匹配表列,从而将HTML链接放在表中各列的正确位置,而不是总是放在第一列。这也适用于其他查询,因此当在报表中使用system properties查询时,它有键和值的链接。
提出一个问题(Wiki作者提出):如果一列有一个堆对象,但不是第一列,那么上下文菜单是否应该出现在所有列中。对于保存字符串或数值的非对象列,就会出现上下文菜单。当没有任何查询(除了复制选择)可以做任何事情时,为这些提供上下文菜单是否令人困惑。
Map处理
Map堆对象现在可以通过数组标记访问了,返回Map.Entry
对象。以前对该数组访问会返回Map.Entry对象,再通过key和value来访问键值对。但不是所有的Map都有Entry,所以现在系统用getKey()
和getValue()
来访问键值对。
SELECT h AS map, (SELECT e.getKey() AS key, e.getValue() AS value FROM OBJECTS ${h}[0:-1] e ) AS kv FROM java.util.HashMap h WHERE (h[0:-1].size() > 0)
map |kv
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
java.util.HashMap [id=0x22f49970]|[{key=java.lang.String [id=0x22f44658], value=java.lang.String [id=0x22f44670]}, {key=java.lang.String [id=0x22f44688], value=java.lang.String [id=0x22f446a0]}]
java.util.HashMap [id=0x22f49948]|[{key=java.lang.String [id=0x22f44628], value=java.lang.String [id=0x22f44640]}]
java.util.HashMap [id=0x22f49920]|[{key=java.lang.String [id=0x22f445c8], value=java.lang.String [id=0x22f445e0]}, {key=java.lang.String [id=0x22f445f8], value=java.lang.String [id=0x22f44610]}]
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
将Map解析为键值对的列表。
展平
第二列是键值对的列表。如果能进一步处理这些就太好了,自动展平是实现这一目标的一种方法。如果子查询返回一个包含列表或数组值的RowMap,那么自动展平将该RowMap拆分为多个RowMap,每个RowMap对应于列表或数组的一个条目。其他对象只是在RowMap中重复出现。如果有多个长度不同的列表或数组,则列表或数组末尾以外的元素将被替换为null。
这是扁平化的另一个例子——查看父节点的任何子节点是否没有对父节点的反向引用。
SELECT group AS Group, thread AS Thread
FROM OBJECTS ( SELECT t AS group, t.threads[0:-1] AS thread FROM java.lang.ThreadGroup t )
WHERE ((thread != null) and (thread.group != group))
这将选择所有java.lang.ThreadGroup对象,然后生成包含两列的行,即线程组和子线程列表。然后将其平展为组的行和单个子线程,其中select然后检查非空子线程和不指向线程组的子线程。请注意,在WHERE
前省略别名是不必要的——外部select中的group
和thread
不需要用别名限定。
GROUP BY
可通过以下方式模拟:
SELECT s.sz AS Size,
(SELECT OBJECTS m FROM java.util.HashMap m WHERE (m[0:-1].size() = s.sz)) AS Maps
FROM OBJECTS ( SELECT DISTINCT h[0:-1].size() AS sz FROM java.util.HashMap h ) s
- 这首先获取要分组的东西的列表,这里是size列表。
- 然后将size作为子选择返回到下一阶段。
- 然后,select items子句只选择与当前组值匹配的对象。
- 然后,结果被转换为一个对象列表,它以int[]数组的形式出现在列中,然后可以用作上下文菜单。
另一个例子是根据被直接引用的数量分组:
SELECT s.sz AS Size,
(SELECT OBJECTS m FROM INSTANCEOF java.lang.Object m WHERE (inbounds(m).@length = s.sz)) AS Objects
FROM OBJECTS ( SELECT DISTINCT inbounds(h).@length AS sz FROM INSTANCEOF java.lang.Object h ) s
COUNT
可以通过以下方式模拟:
SELECT z.size AS Size,
z.maps AS Maps,
z.maps.@length AS "Count",
z.maps[0:-1].size() AS "Count (another way)"
FROM OBJECTS ( eval((
SELECT
s.sz AS size,
(SELECT OBJECTS m FROM java.util.HashMap m WHERE (m[0:-1].size() = s.sz)) AS maps
FROM OBJECTS ( SELECT DISTINCT h[0:-1].size() AS sz FROM java.util.HashMap h ) s
)) ) z
- 首先获取要分组的东西的列表,这里是size列表。
- 然后将size作为子查询返回到下一阶段。
- 然后,select items子句只选择与当前组值匹配的对象。
- 然后,结果被转换为一个对象列表,在列中以int[]数组的形式出现
- 然后用eval()包裹select对象,这样外部的select对象就不会把它拉平
- 然后,外层select会生成结果,包括size、映射以及两种计算map数组中元素数量的方法,一种是使用@length,另一种是使用size()。
另一个例子:
SELECT z.size AS Size,
z.objects AS Objects,
z.objects.@length AS "Count",
z.objects[0:-1].size() AS "Count (another way)"
FROM OBJECTS ( eval((
SELECT s.sz AS size,
(SELECT OBJECTS m FROM INSTANCEOF java.lang.Object m WHERE (inbounds(m).@length = s.sz)) AS objects
FROM OBJECTS ( SELECT DISTINCT inbounds(h).@length AS sz FROM INSTANCEOF java.lang.Object h ) s
)) ) z
JOIN
除了 UNION 之外,OQL 没有 SQL 风格的连接操作。通过展平可以模拟其中一些操作,但所需的语句也更复杂。
考虑一个场景,基于java.lang.Integer
和java.lang.Long
都有的 value 字段进行 JOIN 操作
CROSS JOIN
以下 SQL :
SELECT i.this,i.this['value'] AS "Integer value", l.this,l.this['value'] AS "Long value"
FROM "java.lang.Integer" i CROSS JOIN "java.lang.Long" l
可以被等价转换为 OQL:
SELECT z.i AS Integer, z.i.value AS "Integer value", z.lv.l AS Long, z.lv.l.value as "Long value"
FROM OBJECTS ( SELECT i, (SELECT l FROM java.lang.Long l ) AS lv FROM java.lang.Integer i ) z
这会选择所有的java.lang.Integer
对象,遍历对象,并选择出对应的java.lang.Long
对象,然后展平行。同一行中的Integer
和Long
对象不必值匹配。
LEFT JOIN / LEFT OUTER JOIN
以下 SQL:
SELECT i.this,i.this['value'] AS "Integer value", l.this,l.this['value'] AS "Long value"
FROM "java.lang.Integer" i LEFT JOIN "java.lang.Long" l ON i.this['value']+0 = l.this['value']+0
可以被等价转换为 OQL:
SELECT z.i AS Integer, z.i.value AS "Integer value", z.lv.l AS Long, z.lv.l.value as "Long value"
FROM OBJECTS ( SELECT i, (SELECT l FROM java.lang.Long l WHERE (l.value = i.value)) AS lv FROM java.lang.Integer i ) z
INNER JOIN
此操作生成左表(对象集)中的每一行,右表(对象集)中有匹配行。结果表不大于左右表中较小的表。
SELECT z.i AS Integer, z.i.value AS "Integer value", z.lv.l AS Long, z.lv.l.value as "Long value"
FROM OBJECTS ( SELECT i, (SELECT l FROM java.lang.Long l WHERE (l.value = i.value)) AS lv FROM java.lang.Integer i ) z
WHERE (z.lv != null)
这会选择所有的java.lang.Integer
对象,遍历 Integer 然后生成一行 Object,以及所有具有相同值的java.lang.Long
Object 列表,然后展平并且排除掉没有Long
值的行。
SELECT z.iv.i AS Integer, z.iv.i.value AS "Integer value", z.l AS Long, z.l.value as "Long value"
FROM OBJECTS ( SELECT (SELECT i FROM java.lang.Integer i WHERE (i.value = l.value)) AS iv, l FROM java.lang.Long l ) z
WHERE (z.iv != null)
这会选择所有的java.lang.Long
对象,遍历 Long 然后生成 Object,以及对应的具有相同值的java.lang.Integer
对象列表,然后展平并且排除掉没有Integer
值的行。每一个展平行都包含一个Long
值和一个Integer
值,两个值符合Where条件WHERE (i.value = l.value)
。
以上OQL都等价于SQL:
SELECT i.this,i.this['value'] AS "Integer value", l.this,l.this['value'] AS "Long value"
FROM "java.lang.Integer" i INNER JOIN "java.lang.Long" l ON i.this['value']+0 = l.this['value']+0
RIGHT JOIN / RIGHT OUTER JOIN
SELECT i.this,i.this['value'] AS "Integer value", l.this,l.this['value'] AS "Long value"
FROM "java.lang.Integer" i RIGHT JOIN "java.lang.Long" l ON i.this['value']+0 = l.this['value']+0
以上SQL等价于以下OQL:
SELECT z.iv.i AS Integer, z.iv.i.value AS "Integer value", z.l AS Long, z.l.value as "Long value"
FROM OBJECTS ( SELECT (SELECT i FROM java.lang.Integer i WHERE (i.value = l.value)) AS iv, l FROM java.lang.Long l ) z
这会查询所有的Long
对象,遍历 Long 对象并且生成具有相同值的Integer
对象列表,然后展平行。每一个展平行都包含一个Long
对象和一个可能存在的相同值的Integer
对象,或者是null
FULL OUTER JOIN
此操作为左表(对象集)的每一行和右表(对象集)的每一行生成一行,但当左表的一行与右表的一行具有相同值时,它们将被包括在同一输出行中。结果表至少与左表和右表中较大的表一样大。
SELECT z.i AS Integer, z.i.value AS "Integer value", z.lv.l AS Long, z.lv.l.value as "Long value"
FROM OBJECTS ( SELECT i, (SELECT l FROM java.lang.Long l WHERE (l.value = i.value)) AS lv FROM java.lang.Integer i ) z
UNION (
SELECT z.iv.i AS Integer, z.iv.i.value AS "Integer value", z.l AS Long, z.l.value as "Long value"
FROM OBJECTS ( SELECT (SELECT i FROM java.lang.Integer i WHERE (i.value = l.value)) AS iv, l FROM java.lang.Long l ) z
WHERE (z.iv = null)
)
这会执行LEFT JOIN / LEFT OUTER JOIN 操作,然后将这些行与所有没有相应的java.lang.Long
对象的列表结合起来。每一行包含一个Integer
对象或Long
对象或两者都有。
等价以下SQL:
SELECT i.this,i.this['value'] AS "Integer value", l.this,l.this['value'] AS "Long value"
FROM "java.lang.Integer" i FULL OUTER JOIN "java.lang.Long" l ON i.this['value']+0 = l.this['value']+0
OQL的编写思路
OQL语法有点绕,又缺失一些SQL的常见功能,再加上平时使用频率不高。编写起来是需要一个不断尝试的过程的。例如,考虑这个问题:
我想找到特定类型的所有不可达的对象。
通常,MAT会丢弃不可达对象,唯一可以看到的是对象图表中的不可达对象占内存的总数。这对于OQL没有用,因此我们需要-keep_unreachable_objects
选项。不可达的对象不会被丢弃,而是由一些人为插入的UNREACHABLE_OBJECT
垃圾收集根保留。
让我们来看一些例子:
snapshot
有一些有趣的方法,包括getGCRoots()
SELECT * FROM OBJECTS ${snapshot}.getGCRoots() r
因为它是一个以get开头的无参数方法,我们可以使用bean属性来访问它。
SELECT * FROM OBJECTS ${snapshot}.@GCRoots r
[21800, 21801, 21802, 21803, 21804, 21805,
返回的这些Integer值是MAT对象的ID。
但这给出了所有的GC根,而我们只想要不可达的GC根。
试试这个方式:
SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(21800) t
[org.eclipse.mat.parser.model.XGCRootInfo@2b963bbc]
它返回XGCRootInfo的列表,遍历一下列表中的元素:
SELECT t FROM OBJECTS ${snapshot}.getGCRootInfo(21800) t
r
-------------------------------------------------
org.eclipse.mat.parser.model.XGCRootInfo@2b963bbc
-------------------------------------------------
通过查 MAT 的 API 文档可以看到它有一个getType()方法,所以:
SELECT t,t.@type FROM OBJECTS ${snapshot}.getGCRootInfo(21800) t
t | t.@type
-----------------------------------------------------------
org.eclipse.mat.parser.model.XGCRootInfo@2b963bbc| 2
-----------------------------------------------------------
而GCRootInfo.Type.UNREACHABLE
的值为2048,所以:
SELECT t,t.@type FROM OBJECTS ${snapshot}.getGCRootInfo(21800) t WHERE t.@type = 2048
这不会返回任何内容,因为对象ID 21800是SYSTEM根。但我们可以使用它来从所有GC根中进行选择,如果没有找到UNREACHABLE根,则该子SELECT条款为空。我们可以将选择项简化为 *,因为它并不重要。
SELECT OBJECTS r FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null
Class Name | Shallow Heap | Retained Heap
-----------------------------------------------------------------------------------
int[11] @ 0x22ef4c58 Unreachable | 56 | 56
int[9] @ 0x22ed2ad8 Unreachable | 48 | 48
int[7] @ 0x22ed2b28 Unreachable | 40 | 40
int[17] @ 0x22ed3150 Unreachable | 80 | 80
java.lang.ref.SoftReference @ 0x22f52cd8 Unreachable| 32 | 400
-----------------------------------------------------------------------------------
看起来有希望——我们现在有了一个对象列表,里面都是UNREACHABLE GC根。
SELECT AS RETAINED SET r FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null
r
--------------------------------------------------------------------
java.lang.Class [id=0x26970198;name=java.lang.Throwable[]]
java.lang.Class [id=0x26970370;name=java.lang.Error[]]
java.lang.Class [id=0x26970558;name=java.lang.VirtualMachineError[]]
--------------------------------------------------------------------
或许可以将它们视为数,而不是Table。
SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null
Class Name | Shallow Heap | Retained Heap
-------------------------------------------------------------------------------------------------
int[33] @ 0x22e5be20 Unreachable | 144 | 144
int[27] @ 0x22e5bed0 Unreachable | 120 | 120
int[17] @ 0x22e5bf60 Unreachable | 80 | 80
sun.reflect.NativeConstructorAccessorImpl @ 0x22e5c5d8 Unreachable| 24 | 184
sun.reflect.NativeConstructorAccessorImpl @ 0x22e5c5f0 Unreachable| 24 | 184
-------------------------------------------------------------------------------------------------
如果我们对这些类型很感兴趣,那么工具栏右上角的“Show as Histogram”按钮将按类别显示总数。如果我们对子类感兴趣,那么‘ Group by superclass’选项可以做到这一点。
但是,我们可能希望完全通过 OQL 完成这项工作——例如,作为另一个查询的一部分或以批处理模式完成。现在我们只需要选择我们感兴趣的类型的对象。有几种方法。
- 我们可以列出我们可能感兴趣的类型的所有对象,并将这些对象保留在retained set 中
- 我们可以查看每个保留的对象,看看每个对象是否都在我们感兴趣的对象列表中
- 我们可以查看每个保留的对象,找到它的类,然后找到它的类名称,看看类名称是否与我们感兴趣的对象匹配
SELECT * FROM java.util.ArrayList o
SELECT * FROM java.util.ArrayList o WHERE o in
(SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null)
-- 或者
SELECT * FROM OBJECTS (SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null) u
WHERE u in (SELECT * FROM java.util.ArrayList o)
-- 或者
SELECT * FROM OBJECTS (SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null) u
WHERE u.@clazz.@name = "java.util.ArrayList"
结果如下:
Class Name | Shallow Heap | Retained Heap
---------------------------------------------------------------
java.util.ArrayList @ 0x22efe620| 24 | 320
java.util.ArrayList @ 0x22efe608| 24 | 320
java.util.ArrayList @ 0x22efe5a8| 24 | 320
java.util.ArrayList @ 0x22efcfd8| 24 | 384
java.util.ArrayList @ 0x22efb998| 24 | 320
java.util.ArrayList @ 0x22efb928| 24 | 320
---------------------------------------------------------------
如果我们想查询继承了java.util.AbstractCollection
的对象,可以用:
SELECT * FROM INSTANCEOF java.util.AbstractCollection o WHERE o in
(SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null)
-- 或者
SELECT * FROM OBJECTS (SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null) u
WHERE u in (SELECT * FROM INSTANCEOF java.util.AbstractCollection o)
-- 或者
SELECT * FROM OBJECTS (SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null) u
WHERE u.@clazz.doesExtend("java.util.AbstractCollection")
返回值如下:
Class Name | Shallow Heap | Retained Heap
---------------------------------------------------------------
java.util.ArrayList @ 0x22efb928| 24 | 320
java.util.ArrayList @ 0x22efb998| 24 | 320
java.util.ArrayList @ 0x22efcfd8| 24 | 384
java.util.ArrayList @ 0x22efe5a8| 24 | 320
java.util.ArrayList @ 0x22efe608| 24 | 320
java.util.ArrayList @ 0x22efe620| 24 | 320
java.util.Vector @ 0x22f2b5e8 | 24 | 80
java.util.HashSet @ 0x22f2b600 | 16 | 136
java.util.Vector @ 0x22f2b638 | 24 | 80
---------------------------------------------------------------
提取线程信息
SELECT u.Thread AS Thread, u.Frame.@text AS Frame
FROM OBJECTS (
SELECT t AS Thread, ${snapshot}.getThreadStack(t.@objectId).@stackFrames AS Frame
FROM java.lang.Thread t ) u
-- 上面查询中的子查询
SELECT t AS Thread, ${snapshot}.getThreadStack(t.@objectId).@stackFrames AS Frame
FROM java.lang.Thread t
-- 提取每个线程和堆栈帧数组。然后,外部选择使用其每个堆栈帧的相同线程引用来初始化该数组
执行结果如下:
Thread |Frame
--------------------------------------------------------------------------------------------------------------------------------------------------
java.lang.Thread [id=0x7b6a1e7f0]|at java.lang.Object.wait(J)V (Native Method)
java.lang.Thread [id=0x7b6a1e7f0]|at java.lang.Object.wait(JI)V (Unknown Source)
java.lang.Thread [id=0x7b6a1e7f0]|at com.squareup.okhttp.ConnectionPool.performCleanup()Z (ConnectionPool.java:305)
java.lang.Thread [id=0x7b6a1e7f0]|at com.squareup.okhttp.ConnectionPool.runCleanupUntilPoolIsEmpty()V (ConnectionPool.java:242)
java.lang.Thread [id=0x7b6a1e7f0]|at com.squareup.okhttp.ConnectionPool.access$000(Lcom/squareup/okhttp/ConnectionPool;)V (ConnectionPool.java:54)
java.lang.Thread [id=0x7b6a1e7f0]|at com.squareup.okhttp.ConnectionPool$1.run()V (ConnectionPool.java:97)
--------------------------------------------------------------------------------------------------------------------------------------------------
你甚至可以从每一个帧里提取出Local
SELECT v.Thread as Thread, toString(v.Thread) AS Name, v.Frame AS Frame, ${snapshot}.getObject(v.Objs) AS Local
FROM OBJECTS (
SELECT u.Thread AS Thread, u.Frame.@text AS Frame, u.Frame.@localObjectsIds AS Objs
FROM OBJECTS (
SELECT t AS Thread, ${snapshot}.getThreadStack(t.@objectId).@stackFrames AS Frame
FROM java.lang.Thread t
) u
) v
WHERE (v.Objs != null)
v.Thread |Name |Frame |Local
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
java.lang.Thread [id=0x7b6a1e7f0]|OkHttp ConnectionPool|at com.squareup.okhttp.ConnectionPool.performCleanup()Z (ConnectionPool.java:305) |java.util.ArrayList [id=0x7bb402eb8]
java.lang.Thread [id=0x7b6a1e7f0]|OkHttp ConnectionPool|at com.squareup.okhttp.ConnectionPool.performCleanup()Z (ConnectionPool.java:305) |com.squareup.okhttp.ConnectionPool [id=0x6c556ccb0]
java.lang.Thread [id=0x7b6a1e7f0]|OkHttp ConnectionPool|at com.squareup.okhttp.ConnectionPool.runCleanupUntilPoolIsEmpty()V (ConnectionPool.java:242) |com.squareup.okhttp.ConnectionPool [id=0x6c556ccb0]
java.lang.Thread [id=0x7b6a1e7f0]|OkHttp ConnectionPool|at com.squareup.okhttp.ConnectionPool.access$000(Lcom/squareup/okhttp/ConnectionPool;)V (ConnectionPool.java:54)|com.squareup.okhttp.ConnectionPool [id=0x6c556ccb0]
java.lang.Thread [id=0x7b6a1e7f0]|OkHttp ConnectionPool|at com.squareup.okhttp.ConnectionPool$1.run()V (ConnectionPool.java:97) |com.squareup.okhttp.ConnectionPool$1 [id=0x6c556ccd8]
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
展示对象的全部字段
SELECT t.s AS "Object", toHex(t.s.@objectAddress) AS "Object address", t.f.@name AS "Field name", t.f.@value AS "Field value"
FROM OBJECTS ( SELECT s, s.@fields AS f FROM "java.util.*" s
WHERE (s implements org.eclipse.mat.snapshot.model.IInstance) ) t
结果如下:
Object |Object address|Field name |Field value
--------------------------------|--------------|---------------|-----------
java.util.TreeMap [id=0x3acd178]|0x3acd178 |comparator |
java.util.TreeMap [id=0x3acd178]|0x3acd178 |root |0x3c32f60
java.util.TreeMap [id=0x3acd178]|0x3acd178 |size |2
java.util.TreeMap [id=0x3acd178]|0x3acd178 |modCount |2
java.util.TreeMap [id=0x3acd178]|0x3acd178 |entrySet |
java.util.TreeMap [id=0x3acd178]|0x3acd178 |navigableKeySet|
java.util.TreeMap [id=0x3acd178]|0x3acd178 |descendingMap |
java.util.TreeMap [id=0x3acd178]|0x3acd178 |keySet |
java.util.TreeMap [id=0x3acd178]|0x3acd178 |values |
java.util.TreeMap [id=0x3acd1a8]|0x3acd1a8 |comparator |
java.util.TreeMap [id=0x3acd1a8]|0x3acd1a8 |root |0x3c32f80
--------------------------------|--------------|---------------|-----------