Basic Concepts of Java Heap Dump Analysis with MAT

Isuru Perera
Jul 2 · 14 min read
git clone https://github.com/chrishantha/sample-java-programs
cd sample-java-programs
mvn clean install
cd memoryref

The sample application

Class Diagram
Object Diagram
public abstract class Base {    private final byte[] bytes;
private static final Random random = new Random();
Base() {
bytes = new byte[(10 * (1 + random.nextInt(10))) * 1024 * 1024];
}
public long getSize() {
return JavaAgent.getObjectSize(this) + JavaAgent.getObjectSize(bytes);
}
}
public class A extends Base {    final B b;
final C c;
A() {
b = new B();
c = new C(b);
}
@Override
public long getSize() {
return super.getSize() + b.getSize() + c.getSize();
}
}
public class B extends Base {    final D d;
final E e;
B() {
d = new D();
e = new E();
}
@Override
public int getSize() {
return super.getSize() + d.getSize() + e.getSize();
}
}
public class C extends Base {    final E e;
final F f;
C(B b) {
this.e = b.e;
f = new F();
}
@Override
public int getSize() {
return super.getSize() + f.getSize();
}
}
public class D extends Base {}public class E extends Base {}public class F extends Base {}public class G extends Base {}
public class MemoryRefApplication implements SampleApplication {    @Override
public void start() {
A a = new A();
long size = a.getSize();
System.out.format("The retained heap size of object A is %d bytes (~%d MiB).%n",
size, (size / (1024 * 1024)));
long objectSize = JavaAgent.getObjectSize(a);
if (objectSize > 0) {
System.out.format("The shallow heap size of object A is %d bytes.%n", objectSize);
} else {
System.out.println("WARNING: Java Agent is not initialized properly.");
}
Thread backgroundThread = new Thread() {
private long getSize() {
G g = new G();
return g.getSize();
}
@Override
public void run() {
long size = getSize();
System.out.format("The size of object allocated within the background thread was %d bytes (~%d MiB).%n",
size, (size / (1024 * 1024)));
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
backgroundThread.setName("Background Thread");
backgroundThread.setDaemon(true);
backgroundThread.start();
try {
System.out.println("Press Enter to exit.");
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "MemoryRefApplication";
}
}
public class JavaAgent {
private static volatile Instrumentation instrumentation;
public static void premain(final String agentArgs, final Instrumentation instrumentation) {
JavaAgent.instrumentation = instrumentation;
}
public static long getObjectSize(final Object object) {
if (instrumentation == null) {
return -1L;
}
return instrumentation.getObjectSize(object);
}
}

Running the sample application

java -Xmn1g -Xmx2g -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -javaagent:target/memoryref.jar -jar target/memoryref.jar
MemoryRefApplication
The retained heap size of object A is 304087256 bytes (~290 MiB).
The shallow heap size of object A is 24 bytes.
Press Enter to exit.
The size of object allocated within the background thread was 31457312 bytes (~30 MiB).

Getting a memory snapshot (heap dump)

$ jmap 
Usage:
jmap [option] <pid>
(to connect to running process)
jmap [option] <executable <core>
(to connect to a core file)
jmap [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)
where <option> is one of:
<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system
mkdir heap1
jmap -dump:file=heap1/heap.hprof $(pgrep -f memoryref)

Analyzing the heap dump

Leak Suspects Report

stat -c "%s %n" heap1/heap.hprof
echo $(($(stat -c "%s" heap1/heap.hprof) / 1024 / 1024 )) MiB

Reachability

Reachability and Garbage Collection roots

mkdir heap2
jmap -dump:live,file=heap2/heap.hprof $(pgrep -f memoryref)
2019-06-30T22:25:37.803+0530: [GC (Heap Dump Initiated GC) [PSYoungGen: 390595K->92802K(917504K)] 390595K->297610K(1129984K), 0.0993590 secs] [Times: user=0.21 sys=0.10, real=0.10 secs] 
2019-06-30T22:25:37.902+0530: [Full GC (Heap Dump Initiated GC) [PSYoungGen: 92802K->92699K(917504K)] [ParOldGen: 204808K->204801K(416256K)] 297610K->297501K(1333760K), [Metaspace: 3703K->3703K(1056768K)], 0.0387960 secs] [Times: user=0.09 sys=0.00, real=0.04 secs]

Heap Dump Overview

Overview pane

Shallow vs Retained Heap

Object diagram

The Dominator Tree

Dominator Tree
Calculating Retained Size
Expanded Tree View
MemoryRefApplication
The retained heap size of object A is 304087256 bytes (~290 MiB).
The shallow heap size of object A is 24 bytes.
Press Enter to exit.
The size of object allocated within the background thread was 31457312 bytes (~30 MiB).
Unreachable object

Incoming and Outgoing References

Object outgoing references
Outgoing references for class “A”
Incoming references for class “A”
Outgoing references for class “E”
Incoming references for class “E”

Finding Paths to the GC Roots

The Histogram

Creating a histogram
Histogram

Conclusion

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade