如何转储来自JVM堆老年代的Java对象?

5

是否有工具可以转储旧的JVM堆?

换句话说,我如何知道一个对象是来自年轻代还是老年代?

2个回答

6
如果您使用Oracle JDK或OpenJDK,可以使用HotSpot Serviceability Agent sa-jdi.jar来执行此操作。它可以发现旧一代的边界。以下是一个示例,收集OldGen边界内对象的堆直方图。
还可以从Java进程内部找到旧一代的地址,请参见相关问题
import sun.jvm.hotspot.gc_implementation.parallelScavenge.ParallelScavengeHeap;
import sun.jvm.hotspot.gc_interface.CollectedHeap;
import sun.jvm.hotspot.memory.GenCollectedHeap;
import sun.jvm.hotspot.memory.MemRegion;
import sun.jvm.hotspot.oops.ObjectHistogram;
import sun.jvm.hotspot.oops.Oop;
import sun.jvm.hotspot.runtime.VM;
import sun.jvm.hotspot.tools.Tool;

public class OldGen extends Tool {

    public static void main(String[] args) {
        new OldGen().execute(args);
    }

    @Override
    public void run() {
        MemRegion oldRegion = getOldRegion(VM.getVM().getUniverse().heap());

        ObjectHistogram histogram = new ObjectHistogram() {
            @Override
            public boolean doObj(Oop obj) {
                return oldRegion.contains(obj.getHandle()) && super.doObj(obj);
            }
        };

        VM.getVM().getObjectHeap().iterate(histogram);
        histogram.print();
    }

    private MemRegion getOldRegion(CollectedHeap heap) {
        if (heap instanceof ParallelScavengeHeap) {
            return ((ParallelScavengeHeap) heap).oldGen().objectSpace().usedRegion();
        } else if (heap instanceof GenCollectedHeap) {
            return ((GenCollectedHeap) heap).getGen(1).usedRegion();
        } else {
            throw new UnsupportedOperationException(heap.kind() + " is not supported");
        }
    }
}

更新

一款适用于JDK 11/17和G1GC的类似工具:

// Add the following JVM options to run
// --add-modules jdk.hotspot.agent
// --add-exports jdk.hotspot.agent/sun.jvm.hotspot.debugger=ALL-UNNAMED
// --add-exports jdk.hotspot.agent/sun.jvm.hotspot.gc.g1=ALL-UNNAMED
// --add-exports jdk.hotspot.agent/sun.jvm.hotspot.oops=ALL-UNNAMED
// --add-exports jdk.hotspot.agent/sun.jvm.hotspot.runtime=ALL-UNNAMED
// --add-exports jdk.hotspot.agent/sun.jvm.hotspot.tools=ALL-UNNAMED
// --add-exports jdk.hotspot.agent/sun.jvm.hotspot.types=ALL-UNNAMED
// --add-exports jdk.hotspot.agent/sun.jvm.hotspot.memory=ALL-UNNAMED

import sun.jvm.hotspot.debugger.Address;
import sun.jvm.hotspot.debugger.AddressException;
import sun.jvm.hotspot.debugger.OopHandle;
import sun.jvm.hotspot.gc.g1.G1CollectedHeap;
import sun.jvm.hotspot.gc.g1.HeapRegion;
import sun.jvm.hotspot.oops.Klass;
import sun.jvm.hotspot.oops.ObjectHeap;
import sun.jvm.hotspot.oops.Oop;
import sun.jvm.hotspot.oops.UnknownOopException;
import sun.jvm.hotspot.runtime.VM;
import sun.jvm.hotspot.tools.Tool;
import sun.jvm.hotspot.types.WrongTypeException;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

public class OldGenHistogram extends Tool {
    final Map<Klass, AtomicLong> histogram = new HashMap<>();

    @Override
    public void run() {
        G1CollectedHeap g1Heap = (G1CollectedHeap) VM.getVM().getUniverse().heap();
        for (Iterator<HeapRegion> it = g1Heap.hrm().heapRegionIterator(); it.hasNext(); ) {
            HeapRegion hr = it.next();
            if (hr.isOld()) {
                iterate(hr);
            }
        }

        histogram.entrySet().stream()
                .sorted((e1, e2) -> Long.compare(e2.getValue().longValue(), e1.getValue().longValue()))
                .forEach(e -> {
                    System.out.print(e.getValue() + "  ");
                    e.getKey().printValueOn(System.out);
                    System.out.println();
                });
    }

    private void iterate(HeapRegion region) {
        ObjectHeap heap = VM.getVM().getObjectHeap();
        Address bottom = region.bottom();
        Address top = region.top();

        try {
            OopHandle handle = bottom.addOffsetToAsOopHandle(0);
            while (handle.lessThan(top)) {
                Oop obj = heap.newOop(handle);
                long size = obj.getObjectSize();
                histogram.computeIfAbsent(obj.getKlass(), k -> new AtomicLong())
                        .addAndGet(size);
                handle = handle.addOffsetToAsOopHandle(size);
            }
        } catch (AddressException | UnknownOopException | WrongTypeException e) {
            // skip
        }
    }

    public static void main(String[] args) {
        new OldGenHistogram().execute(args);
    }
}

不错的工具 :) 然而,将老年代与新生代分开的想法只适用于旧的GC;G1GC以不同的方式管理堆布局,并且不再像以前那样分成两个连续的部分。 - AlBlue

0
一般来说,答案是否定的。这是因为虚拟机(JVM)虽然将堆分成不同的区域,但没有一个转储机制专门只查看旧的区域。实际上,在新版JVM中,有几种不同类型的区域,包括伊甸园、幸存者(一和二)以及包含新生成巨大对象的老年代。
您可以使用jmap或jcmd执行堆转储,并且这些工具有一个选项可以仅生成活动对象或所有对象。如果真的需要知道,可能会分析堆转储并确定它来自哪些区域,但一般情况下,您真的不需要知道。如果选择“活动”对象,则会在堆转储上执行GC以删除未被视为活动的任何对象。
更好的问题是尝试理解您要达到的目标,并确定是否有工具(例如各种PrintGC*标志),可以向您显示答案,例如对象被提升的频率。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接