Skip to content

jacek-lewandowski/heap-analyzer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Heap Analyzer

This agent and its accompanying Java library provides functionality which may be helpful to track heap memory usage of Java programs.

Installation

In order to install you need MacOS or Linux operation system. Windows and other OSes are currently not supported.

You will also need CMake 3.13+ and Gradle 6. Make sure JAVA_HOME environment variable points to the JDK properly.

gradle jar --rerun-tasks
cmake CMakeLists.txt
make
gradle test

All examples can be run by executing gradle run

Tagging objects

Each Java object can have a tag which is a 64 bit number. Tags can be used to store extra information about objects without changing or explicitly referencing those objects. Test Toolkit internally uses tags for heap memory usage scanning and for exploring referents paths. In particular, calling those methods may change tags you previously set on some objects to some meaningless values. However, some bits which have special meaning will survive (for example MARKER bit or SKIP_REFS_FROM bit, they will be explained below, also see utils.h for more information).

Example - raw tag manipulation

Object o = new Object();
System.out.printf("tag of o = %d%n", HeapAnalyzer.instance.getTag(o));

HeapAnalyzer.instance.setTag(o, 100);
System.out.printf("tag of o after setTag(o, 100) = %d%n", HeapAnalyzer.instance.getTag(o));

// set MARKER bit on the object
HeapAnalyzer.instance.markObject(o);
System.out.printf("tag of o after marking it = %d%n", HeapAnalyzer.instance.getTag(o));

// set SKIP_REFS_FROM bit on class objects matching the provided signature

Object o1 = new Object(), o2 = new Object(), o3 = new Object();
HeapAnalyzer.instance.setTag(o1, 1);
HeapAnalyzer.instance.setTag(o2, 2);
HeapAnalyzer.instance.setTag(o3, 2);
System.out.printf("tags of o1, o2, o3 = %d, %d, %d%n",
        HeapAnalyzer.instance.getTag(o1), HeapAnalyzer.instance.getTag(o2), HeapAnalyzer.instance.getTag(o3));

// set tag to 3 for all objects which have tag 2
HeapAnalyzer.instance.setTag(2, 3);
System.out.printf("tags of o1, o2, o3 after setTag(2, 3) = %d, %d, %d%n",
        HeapAnalyzer.instance.getTag(o1), HeapAnalyzer.instance.getTag(o2), HeapAnalyzer.instance.getTag(o3));

// set tag 10 for all objects
HeapAnalyzer.instance.setTag(10);
System.out.printf("tags of o1, o2, o3 after setTag(0) = %d, %d, %d%n",
        HeapAnalyzer.instance.getTag(o1), HeapAnalyzer.instance.getTag(o2), HeapAnalyzer.instance.getTag(o3));

System.out.printf("tags of String.class, StringBuilder.class, StringBuffer = %d, %d, %d%n",
        HeapAnalyzer.instance.getTag(String.class),
        HeapAnalyzer.instance.getTag(StringBuilder.class),
        HeapAnalyzer.instance.getTag(StringBuffer.class));

// set SKIP_REFS_FROM bit on all classes whose signatures contain string '/StringBu'
HeapAnalyzer.instance.skipRefsFromClassesBySubstring("/StringBu");
System.out.printf("tags of String.class, StringBuilder.class, StringBuffer after skipRefsFromClassesBySubstring('/StringBu') = %d, %d, %d%n",
        HeapAnalyzer.instance.getTag(String.class),
        HeapAnalyzer.instance.getTag(StringBuilder.class),
        HeapAnalyzer.instance.getTag(StringBuffer.class));

produces output similar to:

tag of o = 0
tag of o after setTag(o, 100) = 100
tag of o after marking it = 4294967396
tags of o1, o2, o3 = 1, 2, 2
tags of o1, o2, o3 after setTag(2, 3) = 1, 3, 3
tags of o1, o2, o3 after setTag(0) = 10, 10, 10
tags of String.class, StringBuilder.class, StringBuffer = 10, 10, 10
tags of String.class, StringBuilder.class, StringBuffer after skipRefsFromClassesBySubstring('/StringBu') = 10, 68719476746, 68719476746

Heap memory usage

A method which scans heap returns information about the number of total traversed objects and total size they use on heap. Additionally, it returns similar summary of objects which were previously marked. Note that these summaries are done for objects which are reachable from heap roots, that is, which are not subject to garbage collection. There are however some exceptions - phantom, weak and soft references are treated the same as any other reference and even if some object is references only from such a wrapper, it will be counted in the summary. Continue reading to learn how to overcome that problem.

The example below shows the heap summaries with and without marked objects.

Example - heap memory usage

HeapTraversalSummary hs1 = HeapAnalyzer.instance.traverseHeap();
System.out.println(hs1);

byte[] array = new byte[10000]; // now we have a new object on heap

HeapTraversalSummary hs2 = HeapAnalyzer.instance.traverseHeap();
System.out.println("after creating array of 10000 bytes " + hs2);

HeapAnalyzer.instance.markObject(array); // marking interesting object
HeapTraversalSummary hs3 = HeapAnalyzer.instance.traverseHeap();
System.out.println("after marking that array " + hs3);

array = null;
HeapTraversalSummary hs4 = HeapAnalyzer.instance.traverseHeap();
System.out.println("after dereferencing that array " + hs4);

produces output similar to:

HeapTraversalSummary[totalSize=441088, totalCount=8557, markedSize=0, markedCount=0]
after creating array of 10000 bytes HeapTraversalSummary[totalSize=452472, totalCount=8593, markedSize=0, markedCount=0]
after marking that array HeapTraversalSummary[totalSize=452632, totalCount=8596, markedSize=10016, markedCount=1]
after dereferencing that array HeapTraversalSummary[totalSize=442760, totalCount=8598, markedSize=0, markedCount=0]

We can prune some paths in the reference graph by skipping traversals from objects of classes flagged with SKIP_REFS_FROM bit. This bit can be set either explicitly by calling one of setTag methods, or with use of handy method skipRefsFromClassesBySubstring. All classes whose signatures contain the provided string will be automatically flagged with SKIP_REFS_FROM bit. This method can be called multiple times for different patterns.

The example below shows that after excluding AtomicReference class, objects references by its instances are not counted in the summaries. Note that this can be used to exclude references from WeakReference or SoftReference if needed.

Example - excluding classes

HeapTraversalSummary hs1 = HeapAnalyzer.instance.traverseHeap();
System.out.println(hs1);

AtomicReference<List<byte[]>> ref = new AtomicReference<>(Collections.singletonList(new byte[10000]));
HeapAnalyzer.instance.markObject(ref.get().get(0));
HeapTraversalSummary hs2 = HeapAnalyzer.instance.traverseHeap();
System.out.println("after creating AtomicReference which indirectly references array of 10000 bytes " + hs2);

HeapAnalyzer.instance.skipRefsFromClassesBySubstring("AtomicReference");
HeapTraversalSummary hs3 = HeapAnalyzer.instance.traverseHeap();
System.out.println("after marking skipping all references from all AtomicReference instances " + hs3);

produces output similar to:

HeapTraversalSummary[totalSize=442880, totalCount=8599, markedSize=0, markedCount=0]
after creating AtomicReference which indirectly references array of 10000 bytes HeapTraversalSummary[totalSize=453976, totalCount=8625, markedSize=10016, markedCount=1]
after marking skipping all references from all AtomicReference instances HeapTraversalSummary[totalSize=444256, totalCount=8628, markedSize=0, markedCount=0]

Dumping references

Another method is designed to dump references leading to the marked objects (referents of marked object). It also applies only to the live objects, reachable from heap roots. The references are printed in a tree-like structure to standard error stream.

Example - dump references

byte[] array = new byte[10000];
AtomicReference<List<byte[]>> ref = new AtomicReference<>(Collections.singletonList(array));
Thread t = new Thread("example thread") {
    @Override
    public void run() {
        int i =0;
        while (true) {
            i = i + array.length;
            try { Thread.sleep(100); } catch (InterruptedException e) { return; }
        }
    }
};
t.setDaemon(true);
t.start();

HeapAnalyzer.instance.markObject(array);
HeapAnalyzer.instance.dumpReferencess(0, 3);

produces output similar to:

Object (10016 bytes) of [B, referenced from:
 ├── Lnet/enigma/test/toolkit/HeapTraversalSummary;, stack local: thread: 1/2, Lnet/enigma/test/toolkit/HeapAnalyzerExamples;.dumpReferencessExample(HeapAnalyzerExamples.java:109)
 ├── Lnet/enigma/test/toolkit/HeapAnalyzerExamples$1;, field: val$array  (HeapAnalyzerExamples.java:95)
 │   ├── Lnet/enigma/test/toolkit/HeapTraversalSummary;, thread
 │   ├── Lnet/enigma/test/toolkit/HeapTraversalSummary;, stack local: thread: 10/1, Lnet/enigma/test/toolkit/HeapAnalyzerExamples$1;.run(HeapAnalyzerExamples.java:101)
 │   ├── Lnet/enigma/test/toolkit/HeapTraversalSummary;, stack local: thread: 1/2, Lnet/enigma/test/toolkit/HeapAnalyzerExamples;.dumpReferencessExample(HeapAnalyzerExamples.java:109)
 │   ├── [Ljava/lang/Thread;, array element: [1]
 │   │   ├── Ljava/lang/ThreadGroup;, field: threads 
 ├── Ljava/util/Collections$SingletonList;, field: element  (Collections.java:4803)
 │   ├── Ljava/util/concurrent/atomic/AtomicReference;, field: value 
 │   │   ├── Lnet/enigma/test/toolkit/HeapTraversalSummary;, stack local: thread: 1/2, Lnet/enigma/test/toolkit/HeapAnalyzerExamples;.dumpReferencessExample(HeapAnalyzerExamples.java:109)

The two parameters of this method can be used to limit the amount of data which is printed. If there are many marked objects, we may consider printing references to only those objects whose size is greater than some threshold.

About

A bunch of utilities to analyse heap memory usage in Java

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published