OQL 入门

分析 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

功能点OQLCalcite
基础语法SELECT s FROM java.lang.String sSELECT 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 classloaderGroup 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中的groupthread不需要用别名限定。

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
  1. 这首先获取要分组的东西的列表,这里是size列表。
  2. 然后将size作为子选择返回到下一阶段。
  3. 然后,select items子句只选择与当前组值匹配的对象。
  4. 然后,结果被转换为一个对象列表,它以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
  1. 首先获取要分组的东西的列表,这里是size列表。
  2. 然后将size作为子查询返回到下一阶段。
  3. 然后,select items子句只选择与当前组值匹配的对象。
  4. 然后,结果被转换为一个对象列表,在列中以int[]数组的形式出现
  5. 然后用eval()包裹select对象,这样外部的select对象就不会把它拉平
  6. 然后,外层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.Integerjava.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对象,然后展平行。同一行中的IntegerLong对象不必值匹配。

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.LongObject 列表,然后展平并且排除掉没有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 完成这项工作——例如,作为另一个查询的一部分或以批处理模式完成。现在我们只需要选择我们感兴趣的类型的对象。有几种方法。

  1. 我们可以列出我们可能感兴趣的类型的所有对象,并将这些对象保留在retained set 中
  2. 我们可以查看每个保留的对象,看看每个对象是否都在我们感兴趣的对象列表中
  3. 我们可以查看每个保留的对象,找到它的类,然后找到它的类名称,看看类名称是否与我们感兴趣的对象匹配
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
--------------------------------|--------------|---------------|-----------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值