JDI でオブジェクトの世代(Young・Old)を判別する2

前回 の処理を sun.jvm.hotspot.oops.ObjectHeap を使って高速化してみたいと思います。(世代の判別方法などは前回と同じ)

使用した環境は前回と同じです。

ソースは http://github.com/fits/try_samples/tree/master/blog/20160506/

ObjectHeap で Oop を取得

ObjectReference の代わりに、sun.jvm.hotspot.oops.ObjectHeapiterate(HeapVisitor) メソッドを使えば Oop を取得できます。

今回のような方法では、以下の理由で iterate メソッドの引数へ SAJDIClassLoader がロードした sun.jvm.hotspot.oops.HeapVisitor インターフェースの実装オブジェクトを与える必要があります。

  • JDI の内部で管理している Serviceability Agent APIsun.jvm.hotspot.jdi.SAJDIClassLoader によってロードされている

下記サンプルでは SAJDIClassLoader がロードした HeapVisitor を入手し、asType を使って実装オブジェクトを作成しています。

また、HeapVisitor の doObj で false を返すと処理を継続し、true を返すと中止 ※ するようです。

 ※ 厳密には、
    対象としている Address 範囲の while ループを break するだけで、
    その外側の(liveRegions に対する)for ループは継続するようです
    (ObjectHeap の iterateLiveRegions メソッドのソース参照)

なお、ObjectHeap は sun.jvm.hotspot.jdi.VirtualMachineImpl から saObjectHeap() で取得するか、sun.jvm.hotspot.runtime.VM から取得します。

check_gen2.groovy
import com.sun.jdi.Bootstrap

def pid = args[0]
def prefix = (args.length > 1)? 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)

// 世代の判定処理を返す
generation = { heap ->
    def hasYoungGen = heap.metaClass.getMetaMethod('youngGen') != null

    [
        young: hasYoungGen? heap.youngGen(): heap.getGen(0),
        old: hasYoungGen? heap.oldGen(): heap.getGen(1)
    ]
}

try {
    def uv = vm.saVM.universe

    def gen = generation(uv.heap())

    def youngGen = gen.young
    def oldGen = gen.old

    println "*** youngGen=${youngGen}, oldGen=${oldGen}"
    println ''

    def objHeap = vm.saObjectHeap()
    // 以下でも可
    //def objHeap = vm.saVM.objectHeap

    // SAJDIClassLoader がロードした HeapVisitor インターフェースを取得
    def heapVisitorCls = uv.class.classLoader.loadClass('sun.jvm.hotspot.oops.HeapVisitor')

    // SAJDIClassLoader がロードした HeapVisitor インターフェースを実装
    def heapVisitor = [
        prologue: { size -> },
        epilogue: {},
        doObj: { oop ->
            def clsName = oop.klass.name.asString()

            if (clsName.startsWith(prefix)) {
                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 "class=${clsName}, hash=${identityHash}, handle=${oop.handle}, age=${age}, inYoung=${inYoung}, inOld=${inOld}"
            }

            // 処理を継続する場合は false を返す
            false
        }
    ].asType(heapVisitorCls)

    objHeap.iterate(heapVisitor)

} finally {
    vm.dispose()
}

動作確認

前回と同じように、実行中の apache-tomcat-9.0.0.M4 へ適用してみました。

前回と異なり、クラス名が '/' で区切られている点に注意

実行例1 (Windows の場合)
> jps

3604 Bootstrap
4516 Jps
> groovy -cp %JAVA_HOME%/lib/sa-jdi.jar check_gen2.groovy 3604 org/apache/catalina/core/StandardContext

*** youngGen=sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen@0x0000000002149ab0, oldGen=sun.jvm.hotspot.gc_implementation.parallelScavenge.PSOldGen@0x0000000002149b40

class=org/apache/catalina/core/StandardContextValve, hash=0, handle=0x00000000c3a577d0, age=1, inYoung=false, inOld=true
class=org/apache/catalina/core/StandardContext$NoPluggabilityServletContext, hash=0, handle=0x00000000c3a633d8, age=0, inYoung=false, inOld=true
class=org/apache/catalina/core/StandardContext$ContextFilterMaps, hash=0, handle=0x00000000c3a63ef0, age=1, inYoung=false, inOld=true
class=org/apache/catalina/core/StandardContext$NoPluggabilityServletContext, hash=0, handle=0x00000000ebc46da0, age=0, inYoung=true, inOld=false
class=org/apache/catalina/core/StandardContext, hash=6f2d2815, handle=0x00000000eddfeaa0, age=1, inYoung=true, inOld=false
class=org/apache/catalina/core/StandardContext, hash=21f2e66b, handle=0x00000000eddff238, age=3, inYoung=true, inOld=false
・・・
実行例2 (Linux の場合)
$ jps

2778 Jps
2766 Bootstrap
$ groovy -cp $JAVA_HOME/lib/sa-jdi.jar check_gen2.groovy 2766 org/apache/catalina/core/StandardContext

*** youngGen=sun.jvm.hotspot.memory.DefNewGeneration@0x00007f0760019cb0, oldGen=sun.jvm.hotspot.memory.TenuredGeneration@0x00007f076001bfc0

class=org/apache/catalina/core/StandardContext, hash=497fe2c4, handle=0x00000000f821bf90, age=0, inYoung=true, inOld=false
class=org/apache/catalina/core/StandardContext$ContextFilterMaps, hash=0, handle=0x00000000f821c5d8, age=0, inYoung=true, inOld=false
class=org/apache/catalina/core/StandardContextValve, hash=0, handle=0x00000000f821ca60, age=0, inYoung=true, inOld=false
・・・
class=org/apache/catalina/core/StandardContext, hash=5478de1a, handle=0x00000000fb12b310, age=1, inYoung=false, inOld=true
class=org/apache/catalina/core/StandardContext$NoPluggabilityServletContext, hash=0, handle=0x00000000fb12f6b0, age=0, inYoung=false, inOld=true
class=org/apache/catalina/core/StandardContext$ContextFilterMaps, hash=0, handle=0x00000000fb131a80, age=0, inYoung=false, inOld=true
class=org/apache/catalina/core/StandardContextValve, hash=0, handle=0x00000000fb1398b0, age=0, inYoung=false, inOld=true