JDI でオブジェクトの世代(Young・Old)を判別する
前回、オブジェクトの age を取得しましたが、同様の方法で今回はオブジェクトが Young 世代(New 領域)と Old 世代(Old 領域) のどちらに割り当てられているかを判別してみたいと思います。 (ただし、結果の正否は確認できていません)
使用した環境は前回と同じです。
- Groovy 2.4.6
- Java SE 8u92 64bit版 (JDK)
ソースは http://github.com/fits/try_samples/tree/master/blog/20160430/
Young・Old 世代の判別
さて、Young・Old の判別方法ですが。
Serviceability Agent API を見てみると sun.jvm.hotspot.gc_implementation.parallelScavenge
パッケージに PSYoungGen
と PSOldGen
というクラスがあり、isIn(Address)
メソッドで判定できそうです。
更に PSYoungGen と PSOldGen は sun.jvm.hotspot.gc_implementation.parallelScavenge.ParallelScavengeHeap
から取得できます。
Address (sun.jvm.hotspot.debugger
パッケージ所属) は sun.jvm.hotspot.oops.Oop
の getHandle()
か getMark().getAddress()
で取得できるので (下記サンプルでは getHandle を使用)、ParallelScavengeHeap を取得すれば何とかなりそうです。
実際に試してみたところ、ParallelScavengeHeap を取得できたのは Windows 環境で、Linux 環境では GenCollectedHeap を使った別の方法 (getGen
メソッドを使う) が必要でした。 (GC の設定等によって更に変わるかもしれません)
世代の判定クラス
実行環境 | ヒープクラス ※ | Young 世代の判定クラス | Old 世代の判定クラス |
---|---|---|---|
Windows | ParallelScavengeHeap | PSYoungGen | PSOldGen |
Linux | GenCollectedHeap | DefNewGeneration | TenuredGeneration |
※ Universe の heap() メソッド戻り値の実際の型 CollectedHeap のサブクラス
上記を踏まえて、前回の処理をベースに以下を追加してみました。
- (1)
sun.jvm.hotspot.jdi.VirtualMachineImpl
からsun.jvm.hotspot.runtime.VM
オブジェクトを取り出す ※1 - (2) VM オブジェクトから
sun.jvm.hotspot.memory.Universe
オブジェクトを取得 - (3) Universe オブジェクトから CollectedHeap (のサブクラス) を取得 ※2
- (4) (3) の結果から世代を判定するオブジェクトをそれぞれ取得
(4) で妥当な条件分岐の仕方が分からなかったので、とりあえず youngGen メソッドが無ければ GenCollectedHeap として処理するようにしました。
※1 private フィールドの saVM か、package メソッドの saVM() で取得 ※2 今回のやり方では、Windows は ParallelScavengeHeap、 Linux は GenCollectedHeap でした
JDI の SAPIDAttachingConnector で attach した結果が VirtualMachineImpl となります。
また、SAPIDAttachingConnector でデバッグ接続した場合 (読み取り専用のデバッグ接続)、デバッグ対象オブジェクトのメソッド (hashCode や toString 等) を呼び出せないようなので、オブジェクトを識別するための情報を得るため identityHash
を使ってみました。 (ただし、戻り値が 0 になるものが多数ありました)
check_gen.groovy
import com.sun.jdi.Bootstrap def pid = args[0] def prefix = args[1] def manager = Bootstrap.virtualMachineManager() def connector = manager.attachingConnectors().find { it.name() == 'sun.jvm.hotspot.jdi.SAPIDAttachingConnector' } def params = connector.defaultArguments() params.get('pid').setValue(pid) def vm = connector.attach(params) // (4) 世代を判定するためのオブジェクトを取得 generation = { heap -> def hasYoungGen = heap.metaClass.getMetaMethod('youngGen') != null [ // Young 世代の判定オブジェクト(PSYoungGen or DefNewGeneration) young: hasYoungGen? heap.youngGen(): heap.getGen(0), // Old 世代の判定オブジェクト(PSOldGen or TenuredGeneration) old: hasYoungGen? heap.oldGen(): heap.getGen(1) ] } try { if (vm.canGetInstanceInfo()) { // (1) (2) def uv = vm.saVM.universe // (3) def gen = generation(uv.heap()) def youngGen = gen.young def oldGen = gen.old println "*** youngGen=${youngGen}, oldGen=${oldGen}" println '' vm.allClasses().findAll { it.name().startsWith(prefix) }.each { cls -> println cls.name() cls.instances(0).each { inst -> def oop = inst.ref() def age = oop.mark.age() // 世代の判別 def inYoung = youngGen.isIn(oop.handle) def inOld = oldGen.isIn(oop.handle) def identityHash = '' try { identityHash = Long.toHexString(oop.identityHash()) } catch (e) { } println " hash=${identityHash}, handle=${oop.handle}, age=${age}, inYoung=${inYoung}, inOld=${inOld}" } } } } finally { vm.dispose() }
動作確認
前回と同じように、実行中の apache-tomcat-9.0.0.M4 へ適用してみました。
実行例1 (Windows の場合)
> jps 2836 Bootstrap 5944 Jps
> groovy -cp %JAVA_HOME%/lib/sa-jdi.jar check_gen.groovy 2836 org.apache.catalina.core.StandardContext *** youngGen=sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen@0x0000000002049ad0, oldGen=sun.jvm.hotspot.gc_implementation.parallelScavenge.PSOldGen@0x0000000002049b60 org.apache.catalina.core.StandardContext hash=66dfd722, handle=0x00000000c394a990, age=0, inYoung=false, inOld=true hash=39504d4e, handle=0x00000000edea7cf8, age=3, inYoung=true, inOld=false hash=194311fa, handle=0x00000000edea8e90, age=1, inYoung=true, inOld=false hash=2b28e016, handle=0x00000000edf0c130, age=2, inYoung=true, inOld=false hash=578787b8, handle=0x00000000edf457c0, age=1, inYoung=true, inOld=false org.apache.catalina.core.StandardContext$ContextFilterMaps hash=0, handle=0x00000000c394e7d0, age=0, inYoung=false, inOld=true hash=0, handle=0x00000000c396ec90, age=2, inYoung=false, inOld=true hash=0, handle=0x00000000c3988eb0, age=1, inYoung=false, inOld=true hash=0, handle=0x00000000edf04320, age=1, inYoung=true, inOld=false hash=0, handle=0x00000000edf70988, age=1, inYoung=true, inOld=false ・・・
> groovy -cp %JAVA_HOME%/lib/sa-jdi.jar check_gen.groovy 2836 org.apache.catalina.LifecycleEvent *** youngGen=sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen@0x0000000002049ad0, oldGen=sun.jvm.hotspot.gc_implementation.parallelScavenge.PSOldGen@0x0000000002049b60 org.apache.catalina.LifecycleEvent hash=0, handle=0x00000000c37459c0, age=0, inYoung=false, inOld=true hash=0, handle=0x00000000c374ed40, age=1, inYoung=false, inOld=true hash=0, handle=0x00000000c39ff950, age=0, inYoung=false, inOld=true hash=0, handle=0x00000000ebb8ef90, age=0, inYoung=true, inOld=false hash=0, handle=0x00000000ebb90490, age=0, inYoung=true, inOld=false hash=0, handle=0x00000000ebb904c0, age=0, inYoung=true, inOld=false ・・・
実行例2 (Linux の場合)
$ jps 2801 Jps 2790 Bootstrap
$ groovy -cp $JAVA_HOME/lib/sa-jdi.jar check_gen.groovy 2790 org.apache.catalina.core.StandardContext *** youngGen=sun.jvm.hotspot.memory.DefNewGeneration@0x00007fca50019cb0, oldGen=sun.jvm.hotspot.memory.TenuredGeneration@0x00007fca5001bfc0 org.apache.catalina.core.StandardContext hash=27055bff, handle=0x00000000fb025d38, age=1, inYoung=false, inOld=true hash=5638a30f, handle=0x00000000fb1270a8, age=1, inYoung=false, inOld=true hash=15fad243, handle=0x00000000fb296730, age=1, inYoung=false, inOld=true hash=36c4d4a0, handle=0x00000000fb2f3cf0, age=1, inYoung=false, inOld=true hash=33309557, handle=0x00000000fb2f3ef8, age=1, inYoung=false, inOld=true org.apache.catalina.core.StandardContextValve hash=0, handle=0x00000000fb045ad8, age=0, inYoung=false, inOld=true hash=0, handle=0x00000000fb135300, age=0, inYoung=false, inOld=true hash=0, handle=0x00000000fb2ad568, age=1, inYoung=false, inOld=true hash=0, handle=0x00000000fb3022b0, age=1, inYoung=false, inOld=true hash=0, handle=0x00000000fb3050e8, age=1, inYoung=false, inOld=true ・・・
$ groovy -cp $JAVA_HOME/lib/sa-jdi.jar check_gen.groovy 2790 org.apache.catalina.LifecycleEvent *** youngGen=sun.jvm.hotspot.memory.DefNewGeneration@0x00007fca50019cb0, oldGen=sun.jvm.hotspot.memory.TenuredGeneration@0x00007fca5001bfc0 org.apache.catalina.LifecycleEvent hash=0, handle=0x00000000f82079a8, age=0, inYoung=true, inOld=false hash=0, handle=0x00000000f8207a00, age=0, inYoung=true, inOld=false hash=0, handle=0x00000000f8210470, age=0, inYoung=true, inOld=false ・・・ hash=0, handle=0x00000000f8506568, age=0, inYoung=true, inOld=false hash=0, handle=0x00000000fb003370, age=0, inYoung=false, inOld=true
この結果の正否はともかく、一応は判別できているように見えます。
ちなみに、前回と同様に処理が遅い(重い)点に関しては、Oop を Serviceability Agent API の sun.jvm.hotspot.oops.ObjectHeap
で取得するように変更すれば改善できます。
注意点
今回のように JDI の内部で管理している Serviceability Agent API を取り出して使う場合の注意点は以下の通りです。
- JDI 内部の Serviceability Agent API のクラス(インターフェースも含む)は
sun.jvm.hotspot.jdi.SAJDIClassLoader
クラスローダーによってロードされる
同じ名称のクラスでもロードするクラスローダーが異なれば別物となりますので、Java で今回のような処理を実装しようとすると、クラスのキャストができずリフレクション等を多用する事になると思います。
また、Groovy でも HeapVisitor 等を使う場合に多少の工夫が必要になります。