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

问题描述 投票:0回答:2

有什么工具可以转储老年代的 JVM 堆吗?

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

java garbage-collection jvm mat
2个回答
3
投票

如果您运行 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);
    }
}

0
投票

一般来说,答案是否定的,没有。这是因为尽管 JVM 将堆组织成不同的部分,但没有只查看旧区域的转储机制。事实上,在较新的 JVM 上有几种不同类型的区域,包括伊甸园、幸存者(一个和两个)和老年代,其中可以包含新生成的巨大对象。

您可以使用

jmap
jcmd
执行堆转储,这些可以选择只生成活动对象或所有内容。如果你真的需要知道,你可能能够分析堆转储并确定它在任何时候来自哪些区域,但一般来说,你真的不需要知道。如果您选择“活动”对象,那么它将(实际上)在堆转储上执行 GC 以删除任何不被视为活动的对象。

更好的问题是尝试了解您想要实现的目标,并确定是否有工具(如各种 PrintGC* 标志)可以向您显示答案,例如提升对象的频率。

© www.soinside.com 2019 - 2024. All rights reserved.