OQL

ReZero lol

JVM Object Query Language

参考链接

概述

简单说就是 java heap 信息的查询语言。
原本的小工具 hat 其实已经够用了,但是为了增加点灵活性,所以引入了 oql 方便查询。

1
2
3
select <JavaScript expression to select>
[ from [instanceof] <class name> <identifier>
[ where <JavaScript boolean expression to filter> ] ]

上面的表达式大致介绍下:

  • <class name> 标准Java类名(例如:java.net.URL)或数组类名。
    [C是字符数组名称,[Ljava.io.File;java.io.File []的名称,依此类推。
    要注意全限定描述的类名在运行时并不总是唯一地标识Java类,毕竟可能用着不一样的 classloader
    因此,只是允许将类名作为类对象的 identifier 字符串。

  • [instanceof] 可选,选了就连子类一同查,否则只查对应的类

  • selectwhere 后跟进的都是 js 表达式作为子句。
    这么搞是为了方便,js 作为动态语言用起来算是比较爽的了,比如 object.filed_name, arr[index],用个正则啊之类的。
    然后跟 sql 差不多,就是 from 里给个别名,然后 select 里用。通过这种方式把 from 里的 id 对应的 object 绑到 js 变量上。

整活

  • select all Strings of length 100 or more // 查询超过 100 长度的字符串

    select s from java.lang.String s where s.value.length >= 100

  • select all int arrays of length 256 or more // 查询超过 256 长度的数组

    select a from [I a where a.length >= 256

  • show content of Strings that match a regular expression // 字符串对象匹配正则

    select s.value.toString() from java.lang.String s where /java/.test(s.value.toString())

  • show path value of all File objects // 是不是开始 get 爽点了?

    select file.path.value.toString() from java.io.File file

  • show names of all ClassLoader classes // 这个应该算是真正的唯一标识了吧

    select classof(cl).name from instanceof java.lang.ClassLoader cl

  • show instances of the Class identified by given id string // 这个 id 就是 jhat 的id(如下图), 或者后文中的objectId函数也可以拿到

    select o from instanceof 0xd404b198 o

Note that 0xd404b198 is id of a Class (in a session). This is found by looking at the id shown in that class’s page.

OQL 内置 对象,函数

全局性的 heap 对象

遍历

  1. heap.forEachClass - 为每个Java类调用一个回调函数
    heap.forEachClass(callback)

  2. heap.forEachObject - 为每个Java对象调用回调函数 clazz 就是选中的对象对应的类,includeSubtypes 就是指要不要包含子类
    heap.forEachObject(callback, [clazz = java.lang.Object], [includeSubtypes = true])

上面俩基本就简单的循环,没找到啥好应用,差不多好点的就是用来做个特殊计数啊之类的


查找

  1. heap.findClass(className) 根据 className 查出对应的 class 对象
  • 对象有以下的属性

    • name - 类的名称。

    • superclass - 超类的类对象(如果是java.lang.Object,则为null)。

    • statics - 类的静态字段的 <名称,值> 键值对集合。

    • fields - 字段对象的数组。field对象具有名称,签名属性。

    • loader - 加载此类的 ClassLoader 对象。

    • signers - 签署此类的签名者。

    • protectionDomain - 此类所属的保护域

  • 对象有以下的方法
    -isSubclassOf - 测试给定的类是否是此类的直接或间接子类。

    • isSuperclassOf - 测试给定的Class是否是此类的直接或间接超类。

    • subclasses - 返回直接和间接子类的数组。

    • superclasses - 返回直接和间接超类的数组。

  1. heap.findObject(stringIdOfObject) – 找实例

集合操作

  1. heap.classes – returns an enumeration of all Java classes

  2. heap.objects – 这个 filter 参数指的是在结果集的基础上用 filter 表达式做层过滤

  • heap.objects(clazz, [includeSubtypes=true], [filter])
  1. heap.finalizables – 返回等待 finalized 对象的枚举.

  2. heap.livepaths – 返回数组,数组里包含了存活的对象. 同时接一个次要参数,表明是否返回持有对象弱引用的。

  • heap.livepaths(id, [weakRef=false])
  • 返回对象的每个数组元素都是一个数组,这个子数组包含了在这个 引用链 路径上的对象
  1. heap.roots – 返回根对象
    • id 此根引用的对象的字符串id
    • type 描述类型的 Root (JNI Global, JNI Local, Java Static etc)
    • description Root的字符串描述
    • referrer - 负责此根或null的Thread Object或Class对象

举两个例子

access static field ‘props’ of class java.lang.System // 找出 System 类 static 字段 props 的相关信息

select heap.findClass("java.lang.System").statics.props

get number of fields of java.lang.String class // 找出 String 类的 字段信息

select heap.findClass("java.lang.String").fields.length

find the object whose object id is given // 楞找

select heap.findObject("0xf3800b58")

select all classes that have name pattern java.net.* // 过滤,看到这感觉 oql 的 api 设计的不够简练,似乎有些冗余 api

select filter(heap.classes(), "/java.net./.test(it.name)")

单个对象上的函数

  1. allocTrace 如果给定的Java对象可用,则返回分配栈跟踪。 具体是返回 帧对象的数组。每个帧对象具有以下属性:
  • className - 其方法在 frame 中运行的Java类的名称。
  • methodName - 运行的Java方法的名称。
  • methodSignature - frame 中运行的Java方法的签名。
  • sourceFileName - frame 中运行的Java类的源文件的名称。
  • lineNumber - 方法中的源行号。
  1. classof 返回给定Java对象的Class对象。结果对象支持以下属性,和 heap.object 的那个差不多
  • 属性

    • name - 类的名称。
    • superclass - 超类的类对象(如果是java.lang.Object,则为null)。
    • statics - 类的静态字段的名称,值对。
    • fields - 字段对象的数组。字段对象具有名称,签名属性。
    • loader - 加载此类的ClassLoader对象。
    • signers - 签署此类的签名者。
    • protectionDomain - 此类所属的保护域。
  • 方法

    • isSubclassOf - 测试给定的类是否是此类的直接或间接子类。
    • isSuperclassOf - 测试给定的Class是否是此类的直接或间接超类。
    • subclasses - 返回直接和间接子类的数组。
    • superclasses - 返回直接和间接超类的数组。

样例

  • show class name of each Reference type object //

    select classof(o).name from instanceof java.lang.ref.Reference o

  • show all subclasses of java.io.InputStream // inpustream 的子类

    select heap.findClass("java.io.InputStream").subclasses()

  • show all superclasses of java.io.BufferedInputStream // bufferedInputStream 的父类

    select heap.findClass("java.io.BufferedInputStream").superclasses()

其他常用函数

  1. forEachReferrer 为给定Java对象的每个引用者调用一个回调函数。

  2. identical 返回两个给定的Java对象是否相同。

    • select identical(heap.findClass("Foo").statics.bar, heap.findClass("AnotherClass").statics.bar)
  3. objectid 返回给定Java对象的String id。此id可以传递给 heap.findObject,也可以用于比较对象以进行标识

    • select objectid(o) from java.lang.Object o
  4. reachables 返回从给定Java对象传递引用的Java对象数组。(可选)接受第二个参数,该参数是逗号分隔的字段名称,以从可达性计算中排除。字段以class_name.field_name模式编写。

    • select reachables(u, 'java.net.URL.handler') from java.net.URL u 打印每个java.net.URL中的所有可访问内容,但省略可通过指定字段访问的对象。
  5. referrers 返回引用了给定Java对象的所有对象

    • select u from java.net.URL u where count(referrers(u)) > 2 查询持有URL引用次数超过2的对象
  6. refers function, root function, sizeof function, toHtml function 这些听个名就了解大概了,不介绍了,感兴趣的话可以看文章开头的链接

多值筛选

展示每个线程的名称和线程信息

1
2
select { name: t.name? t.name.toString() : "null", thread: t } 
from instanceof java.lang.Thread t

迭代操作方法

这些方法接受 array/iterator/enumeration 和 一个表达式,用来对 集合对象迭代应用这些表达式

1
2
3
4
5
6
7
8
9
10
11
12
concat(array1/enumeration1, array2/enumeration2)
contains(array/enumeration, expression)
count(array/enumeration, expression)
filter(array/enumeration, expression)
length(array/enumeration)
map(array/enumeration, expression)
max(array/enumeration, [expression])
min(array/enumeration, [expression])
sort(array/enumeration, [expression])
sum(array/enumeration, [expression])
toArray(array/enumeration)
unique(array/enumeration, [expression])

三个迭代时使用的内置变量

  • it -> currently visited element
  • index -> index of the current element
  • array -> array/enumeration that is being iterated

contains 举个例子: 选择所有被某类静态字段引用的 Properties 对象。

1
2
select p from java.util.Properties p
where contains(referrers(p), "classof(it).name == 'java.lang.Class'")

用两个复杂的例子收个尾

1
2
3
select map(sort(map(heap.objects('java.lang.ClassLoader'), 
'{ loader: it, count: it.classes.elementCount }'), 'lhs.count < rhs.count'),
'toHtml(it) + "<br>"')
  1. java.lang.ClassLoader有一个名为classes的私有字段,类型为java.util.Vector。
  2. Vector有一个名为 elementCount的私有字段,是矢量中元素的数量。
  3. 我们使用JavaScript对象标识符 和map函数选择多个值(loader,count)。
  4. 然后带有比较表达式的排序函数对结果按计数(即加载的类数)进行排序。

Printing value of all System properties

1
2
3
4
5
6
7
8
9
10
select map(filter(heap.findClass('java.lang.System').statics.props.table, 'it != null'), 
function (it) {
var res = "";
while (it != null) {
res += it.key.value.toString() + '=' +
it.value.value.toString() + '<br>';
it = it.next;
}
return res;
});
  1. java.lang.System具有类型为java.util.Properties的名称为’props’的静态字段。
  2. java.util.Properties的字段为’table’,类型为java.util.Hashtable $ Entry(此字段继承自java.util.Hashtable)。这是hashtable桶数组。
  3. java.util.Hashtable $ Entry包含’key’,’value’和’next’字段。每个条目指向同一哈希表桶中的下一个条目(或null)。
  4. java.lang.String类具有char []类型的’value’字段。

tips

OQL 可能并不稳定–因为Java平台类的私有字段可能会在没有任何通知的情况下被修改/删除! (实现细节)。
但是,在用户类上使用这种查询可能是安全的–因为用户对类有控制权。

  • Post title:OQL
  • Post author:ReZero
  • Create time:2020-09-26 10:10:00
  • Post link:https://rezeros.github.io/2020/09/26/oql/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
 Comments