LeafiWan
LeafiWan

Reputation: 185

How to dump Java objects came from JVM heap old generation?

Are there any tools to dump old generation of JVM heap?

In other words, how can I tell if an object is came from young generation or old generation?

Upvotes: 5

Views: 2136

Answers (2)

apangin
apangin

Reputation: 98330

If you run Oracle JDK or OpenJDK, you can do this with HotSpot Serviceability Agent sa-jdi.jar. It can discover boundaries of old generation. Here is an example that collects heap histogram among objects within OldGen boundaries.

It is also possible to find the addresses of old generation from within Java process, see the related question.

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");
        }
    }
}

UPDATE

A similar tool for use with JDK 11/17 and 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);
    }
}

Upvotes: 6

AlBlue
AlBlue

Reputation: 24040

Generally speaking, the answer is no, there aren't. That's because although the JVM organises the heap into different sections, there isn't a dumping mechanism which only looks at the old region. And in fact, on newer JVMs there are several different types of region, including eden, survivor (one and two) and old generations which can include newly generated humungous objects.

You can use jmap or jcmd to perform a heap dump, and these have an option to generate just the live objects or everything. If you really needed to know it's possible that you might be able to analyze the heap dumps and determine which regions it came from at any point, but in general, you really don't need to know. If you select 'live' objects then it will (in effect) perform a GC on the heapdump to remove any objects that aren't considered live.

A better question is to try and understand what you're trying to achieve, and determine if there are tools (like the various PrintGC* flags) that will show you an answer, such as how often objects are promoted.

Upvotes: 0

Related Questions