いざという時のために覚えておきたい OQL - OQL ヘルプ日本語訳

Java ヒープを解析する際、jhat や Memory Analyzer といったツールを使うことになるが、これらのツールの内部では OQL (Object Query Language) という言語が使われ、ヒープを解析している。
SQL ライクに記述できるこの言語を手で打って使用することは滅多に無いが、稀に手動で OQL を実行した場合がある。そのようなとき、日本語版のドキュメントがどうにも見当たらなかったので visualvm の OQL Help(http://visualvm.java.net/oqlhelp.html)の日本語版をここに記述しておくことにする。


OQL は、手動で実行しないだけで、Java ヒープ解析時には必ず使われる言語であるのだがいかんせんまだ完成度が高いとは言えない。というのも、とにかく実行に非常に時間がかかる。少し大きいヒープダンプに手動のクエリを打ったとして、それがヒープ全体を走査するような場合のクエリは実行に数分かかる。それに SQL ライクではあるが、LIMIT 句や ORDER BY 句は存在しない。まだまだ改善の余地はある。とはいえ自前でヒープを解析して高速に調査できるツールを実装するのは非常に手間がかかることなので、使用頻度を考えると OQL を使うしか無いわけである。


そもそも Java ヒープを解析する状況にいる時点でトラブルの可能性が通常よりは高いと見える。そのような時、「この解析ツールにこういう機能があれば・・・」と思ったなら、OQL の出番だ。この時 OQL を知っていて、使える状況にあるのなら、ヒープ解析に役立てることができるだろう。

いざというときのために覚えておきたい OQL。


それでは日本語訳、スタート。



OQL 日本語訳

OQL は、Java ヒープに対してクエリを送信できる SQL に似た言語です。OQL はヒープ内のデータをフィルタリングしたり選択したりすることができます。"show all instances of class X" のような事前に定義されたクエリは既に VisualVM でサポートされ、OQL はますます柔軟になっています。OQL は JavaScript 構文をベースにしています。

OQL は次の構造を持ちます。

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

where 句で使うクラス名はパッケージ名を含む完全修飾クラス名(例: java.net.URL)か、クラスの配列名にしてください。char (または [C) は char 配列名で、java.io.File(または [Ljava.io.File;)は java.io.File の配列名です。注意点として、完全修飾クラス名でもクラスを特定できない場合があります。同じ完全修飾クラス名でも異なるクラスローダーによってロードされることで、同じ完全修飾クラス名のクラスが複数存在することがあります。そのため、クラス名はクラスオブジェクトの id 文字列表現で指定することができます。もし instanceof キーワードを使えば、選択したオブジェクトのサブクラスも選択されます。このキーワードを指定しない場合、指定したクラスだけが選択されます。
from 句と where 句は省略可能です。

select 句と where 句では、式に JavaScript 構文の式が使えます。Java のヒープオブジェクトは便利なスクリプトオブジェクトによってラップされ、フィールドには自然な構文でアクセスできるようになります。例えば、Java のフィールドに obj.field_name 構文でアクセスでき、配列の要素には array[index] 構文でアクセスできます。選択された各 Java オブジェクトは、from 句で指定された識別子に束縛されます。

OQL の例

長さが 100 以上の文字列全てを選択

select s from java.lang.String s where s.count >= 100

要素数が 256 以上の int 配列全てを選択

select a from int[] a where a.length >= 256


正規表現にマッチした String オブジェクトの内容を表示

select {instance: s, content: s.toString()} from java.lang.String s where /java/(s.toString())

File オブジェクトのパスを表示

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


全クラスローダーの名前を表示

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

id 文字列表現で指定したクラスのインスタンスを表示

select o from instanceof 0xd404b198 o

0xd404b198 はクラスを特定する値です。この値はこのクラスのページを参照することで見つけることができます。

OQL の組み込み関数、組み込みオブジェクト

heap object

heap は下記メソッドをサポートする組み込みオブジェクトです。

heap.forEachClass -- 各 Java クラスごとにコールバック関数を呼び出します。

heap.forEachClass(callback);

heap.forEachObject -- 各 Java オブジェクトごとにコールバック関数を呼び出します。

heap.forEachObject(callback, clazz, includeSubtypes);

clazz は選択するオブジェクトのクラスです。指定しない場合、デフォルトでは lang.java.Object が指定されます。
includeSubTypes は boolean フラグで、サブクラスのインスタンスを含むかどうかを指定します。デフォルトでは true です。


heap.findClass -- 与えられた名前を持つクラス名を探します。

heap.findClass(className);


これはクラス名が className であるクラスを探します。結果の Class オブジェクトは下記プロパティを持ちます。

  • name - クラス名
  • superclass - スーパクラスの Class オブジェクトです。(java.lang.Object の場合は null)
  • statics - このクラスの static フィールドのキーと値のペア一覧です。
  • fields このクラスのフィールドオブジェクトです。フィールドは name と signature プロパティを持ちます。
  • loader - このクラスをロードしたクラスローダーオブジェクト

クラスオブジェクトは下記メソッドを持ちます。

  • isSubclassOf - 与えられたクラスがこのクラスのサブクラスか否か
  • isSuperclssOf - 与えられたクラスがこのクラスのスーパークラスか否か
  • subclasses - サブクラス一覧を返す
  • superclasses - スーパークラス一覧を返す

heap.findObject - 与えられたオブジェクトIDに対応するオブジェクトを探します。

heap.findObject(stringIdOfObject);

heap.classes -- 全 Java クラスを返します。
heap.objects -- 全 Java オブジェクトを返します。

heap.objects(clazz, [includeSubtyes], [filter])

clazz は選択するインスタンスのクラスです。未定義の場合はデフォルト値 java.lang.Object が使われます。
includeSubTypes は boolean フラグで、サブクラスのインスタンスを含むかどうかを指定します。デフォルトでは true です。省略可能。
filter は、結果セットからフィルタリングを行う式を受け取ります。省略可能。

heap.finalizables -- ファイナライズ待ちの Java オブジェクト一覧を返します。
heap.livepaths - 指定されたオブジェクトが生きている場合、そのオブジェクトへのパスを返します。
このメソッドはオプションで第二引数を受け取ることができます。第二引数は boolean で、これはオブジェクトへのパスに弱参照を含むかどうかです。デフォルトでは弱者んしょうは結果に含まれません。

select heap.livepaths(s) from java.lang.String s

結果配列の各要素もまた配列です。この要素の配列はオブジェクトへのパスの参照チェーンを含んでいます。

heap.roots -- ヒープのルートオブジェクト一覧を返します。ルートオブジェクトは下記プロパティを持ちます。

  • id - オブジェクトID
  • type - ルートオブジェクトの概要 (例: JNI Global, JNI Local, Java Static, 等)
  • description - 説明
  • referrer - このルートオブジェクトのスレッドオブジェクトかクラスオブジェクト。もしくは null。

例: java.lang.System の static な props フィールドにアクセス

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

例: java.lang.String のフィールド数の取得

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

例: オブジェクトIDを元にオブジェクトを探索

select heap.findObject("0xf3800b58")

例: java.net.* にマッチするクラス一覧を取得

select filter(heap.classes(), "/java.net./(it.name)")
オブジェクトに対する関数

allocTrace(jobject) 関数

classof(jobject) 関数
与えられたオブジェクトのクラスオブジェクトを返します。


forEachReferrer(callback, jobject) 関数
指定されたオブジェクトを参照するオブジェクト毎にコールバック関数を呼び出します。

identical(o1, o2) 関数
二つの Java オブジェクトが同じかどうかを調べます。

select identical(heap.findClass("Foo").statics.bar, heap.findClass("AnotherClass").statics.bar)


objectid(jobject) 関数
渡された Java オブジェクトのオブジェクトIDを返します。

select objectid(o) from java.lang.Object o


referrers(jobject) 関数
指定されたJavaオブジェクトを参照するJavaオブジェクト一覧を返します。

例: 各オブジェクトの被参照数を取得

select count(referrers(o)) from java.lang.Object o

例: 各 java.io.File オブジェクトを参照するオブジェクト一覧を取得

select referrers(f) from java.io.File f

例: 被参照数が二つ以上ある URL オブジェクトを表示

select u from java.net.URL u where count(referrers(u)) > 2


referees(jobject) 関数
指定されたJavaオブジェクトが static 参照するJavaオブジェクト一覧を返します。

例: java.io.File クラスが static 参照するオブジェクトの一覧を取得

select referees(heap.findClass("java.io.File"))


root(jobject) 関数
渡されたオブジェクトがルートセットのメンバなら、対応する Root が返されます。それ以外は null が返ります。

sizeof(jobject) 関数
Java オブジェクトのサイズを返します。

select sizeof(o) from int[] o


toHtml(obj) 関数
Java オブジェクトを HTML 文字列として返す関数です。通常はこの関数は自動的に呼び出されます。
次はハイパーリンクを太字で囲った例です。

select "<b>" + toHtml(o) + "</b>" from java.lang.Object o
複数選択

JavaScript のオブジェクトリテラルまたは配列構文を使用して、複数の値を選択することができます。

例: 各スレッドオブジェクトのスレッド名とオブジェクトを表示

select { name: t.name? t.name.toString() : "null", thread: t } 
from instanceof java.lang.Thread t
array/iterator/enumeration 操作関数

下記のうちのいくつかの関数は array/iterator/enumeration と文字列で表現された式(もしくはコールバック関数)を受け取ります。関数は、渡された array/iterator/enumeration の各要素に式(またはコールバック関数)を適用します。JavaScript の配列は連想配列であることに注意してください。したがって、これらの関数は任意の JavaScript オブジェクトと共に使用されるかも知れません。

concat(array1/enumeration1, array2/enumeration2) 関数
二つの配列(またはenumerations)を結合します。

contains(array/enumeration, expression) 関数
配列中に、第二引数で与えられた式にマッチする要素があるかどうかを調べます。この式では次のような組み込み変数が使えます。

  • it -> 現在の要素
  • index -> 現在の要素のインデクス
  • array -> 現在の配列

次は、全ての Properties の中で、 static 参照されている要素を返します。

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


count(array/enumeration,expression) 関数
count 関数は渡された配列の中に、expression で true を返す要素の個数を返します。expression 式は boolean を返さなければなりません。この式では次のような組み込み変数が使えます。

  • it -> 現在の要素
  • index -> 現在の要素のインデクス
  • array -> 現在の配列

例: java.io で始まるクラス名の個数

select count(heap.classes(), "/java.io./(it.name)")

filter(array/enumeration, expression) 関数
filter 関数は渡された配列の各要素に第二引数の式を適用し、true が返された要素のみを返す関数です。この boolean 式では次のような組み込み変数が使えます。

  • it -> 現在の要素
  • index -> 現在の要素のインデクス
  • array -> 現在の配列
  • result -> 結果配列

例: 全クラスのうち、クラス名が java.io. で始まるオブジェクトを返す

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

例: 全 URL オブジェクトに対する各参照のうち、java.net. で始まらないもの一覧を返す

select filter(referrers(u), "! /java.net./(classof(it).name)")
from java.net.URL u


length(array/enumeration) 関数
渡された配列の個数を返す関数です。

map(array/enumeration, expression) 関数
各要素に expression を適用した結果配列を返す、変換関数です。この第二引数の式では次のような組み込み変数が使えます。

  • it -> 現在の要素
  • index -> 現在の要素のインデクス
  • array -> 現在の配列
  • result -> 結果配列

例: java.io.File の全 static フィールドを index = value の形式で表示

select map(heap.findClass("java.io.File").statics, "index + '=' + toHtml(it)")


max(array/enumeration, [expression]) 関数
配列内の最大値を返す関数です。第二引数はオプションで、比較式を渡すことができます。デフォルトでは数値の比較が使われます。この比較式では次の組み込み変数が使えます。

  • lhs -> 比較するペアのうちの右側
  • rhs -> 比較するペアのうちの左側

例: String インスタンスで最大の文字列長を取得します。

select max(map(heap.objects('java.lang.String', false), 'it.count'))

例: 文字列長が最大の String インスタンスを取得します。

select max(map(heap.objects('java.lang.String', false), 'it.count'))


min(array/enumeration, [expression]) 関数
配列内の最小値を返す関数です。第二引数はオプションで、比較式を渡すことができます。デフォルトでは数値比較式が使われます。この比較式では次の組み込み変数が使えます。

  • lhs -> 比較するペアのうちの右側
  • rhs -> 比較するペアのうちの左側

例: 全 Vector インスタンス内について、最小の長さを取得する。

select min(map(heap.objects('java.util.Vector', false), 'it.elementData.length'))

例: 長さが最小の Vector インスタンスを取得

select min(heap.objects('java.util.Vector'), 'lhs.elementData.length < rhs.elementData.length')


sort(array/enumeration, [expression]) 関数
ソート関数は array/enumeration とオプションで比較式を受け取ります。デフォルトでは数値比較式が使われます。この式では次の組み込み変数が使えます。

  • lhs -> 比較するペアのうちの右側
  • rhs -> 比較するペアのうちの左側

例: 全 char 配列をサイズの小さい順に表示

select sort(heap.objects('char[]'), 'sizeof(lhs) - sizeof(rhs)')

例: 全 char 配列をサイズの小さい順に、サイズとオブジェクトを表示

select sort(heap.objects('char[]'), 'sizeof(lhs) - sizeof(rhs)')


top(array/enumeration, [expression], [top]) 関数
配列の上位 N 件を返す関数です。第二引数はオプションで、比較式を受け取れます。デフォルトでは数値比較式が使われます。第三引数もオプションで、上位 N 件の N を受け取ります。デフォルトでは 10 です。
比較式では次の組み込み変数が使えます。

  • lhs -> 比較するペアのうちの右側
  • rhs -> 比較するペアのうちの左側

例: String オブジェクトの長さが長いもの上位5件を表示

select top(heap.objects('java.lang.String'), 'rhs.count - lhs.count', 5)

例: String オブジェクトの長さが長いもの上位5件を、長さとオブジェクトを表示

select map(top(heap.objects('java.lang.String'), 'rhs.count - lhs.count', 5), '{ length: it.count, obj: it }')

sum(array/enumeration, [expression]) 関数

全配列の値を合計する関数です。第二引数はオプションです。これは、入力値を合計する前に各要素に適用されます。

例:各 Properties オブジェクトから到達可能なオブジェクトの合計サイズ

select sum(map(reachables(p), 'sizeof(it)')) 
from java.util.Properties p
// map を省略した形
select sum(reachables(p), 'sizeof(it)') 
from java.util.Properties p


toArray(array/enumeration) 関数
入力した配列/enumeration を配列として返します。


unique(array/enumeration, [expression]) 関数
配列の要素から重複を取り除きます。

例: String から参照される char から 重複を取り除いたものを返します。(注意: 同じ char を複数の String インスタンス間で共有することができます。)

select count(unique(map(heap.objects('java.lang.String'), 'it.value')))
複雑な例

各クラスローダーと、そのローダーによってロードされたクラスの数のヒストグラムを表示

select map(sort(map(heap.objects('java.lang.ClassLoader'), 
'{ loader: it, count: it.classes.elementCount }'), 'lhs.count < rhs.count'),
'toHtml(it) + "<br>"')

java.lang.ClassLoader は private な classes という java.util.Vector フィールドを持っており、Vector は elementCount という private フィールドを持っています。このことを利用して上記クエリが作られています。


各クラスローダーごとの親子関係を表示

select map(heap.objects('java.lang.ClassLoader'),
    function (it) {
        var res = '';
        while (it != null) {
            res += toHtml(it) + "->";
            it = it.parent;
        }
        res += "null";
        return res + "<br>";
})


System.properties をすべて表示

select map(filter(heap.findClass('java.lang.System').props.table, 'it != null && it.key != null && it.value != null'),
    function (it) {
        var res = it.key.toString() + ' = ' + it.value.toString();
        return res;
    });

このクエリは次の事実を利用しています:

  • java.lang.System は static な 'props' という java.util.Properties 型のフィールドを持っています。
  • java.util.Properties は 'table' という java.util.Hashtable$Entry 型のフィールドを持っています。これはハッシュテーブルバケットの配列です。
  • java.util.Hashtable$Entry は 'key', 'value', 'next' というフィールドを持っています。各エントリは、同じハッシュテーブル内の次のエントリへのポインタを持っています。
  • java.lang.String は 'value' という char[] 型のフィールドを持っています。

注意: このクエリは動かなくなる可能性があります。なぜなら、private フィールドは予告なしに変更されたり削除される可能性があるからです。
ただしユーザークラスのクエリは大丈夫でしょう。それらのクラスはあなたのコントロール下にあるからです。