diff --git a/profiler/lib.profiler/src/org/netbeans/lib/profiler/heap/HprofGCRoots.java b/profiler/lib.profiler/src/org/netbeans/lib/profiler/heap/HprofGCRoots.java index 9da02c8bec32..dba19d9cc68a 100644 --- a/profiler/lib.profiler/src/org/netbeans/lib/profiler/heap/HprofGCRoots.java +++ b/profiler/lib.profiler/src/org/netbeans/lib/profiler/heap/HprofGCRoots.java @@ -39,16 +39,16 @@ class HprofGCRoots { private final Object lastThreadObjGCLock = new Object(); private Map gcRoots; private final Object gcRootLock = new Object(); - private List gcRootsList; + private List gcRootsList; HprofGCRoots(HprofHeap h) { heap = h; } - Collection getGCRoots() { + Collection getGCRoots() { synchronized (gcRootLock) { if (gcRoots == null) { - List rootList = new ArrayList<>(); + List rootList = new ArrayList<>(); computeGCRootsFor(heap.getHeapTagBound(HprofHeap.ROOT_UNKNOWN), rootList); computeGCRootsFor(heap.getHeapTagBound(HprofHeap.ROOT_JNI_GLOBAL), rootList); computeGCRootsFor(heap.getHeapTagBound(HprofHeap.ROOT_JNI_LOCAL), rootList); @@ -67,10 +67,8 @@ Collection getGCRoots() { computeGCRootsFor(heap.getHeapTagBound(HprofHeap.ROOT_VM_INTERNAL), rootList); computeGCRootsFor(heap.getHeapTagBound(HprofHeap.ROOT_JNI_MONITOR), rootList); - rootList.sort(new Comparator() { - public int compare(Object o1, Object o2) { - HprofGCRoot r1 = (HprofGCRoot) o1; - HprofGCRoot r2 = (HprofGCRoot) o2; + rootList.sort(new Comparator() { + public int compare(HprofGCRoot r1, HprofGCRoot r2) { int kind = r1.getKind().compareTo(r2.getKind()); if (kind != 0) { @@ -92,8 +90,10 @@ GCRoot getGCRoot(Long instanceId) { if (gcRoots == null) { heap.getGCRoots(); roots = new HashMap<>(); - for (GCRoot r : getGCRoots()) { - roots.put(r.getInstance().getInstanceId(), r); + for (HprofGCRoot root : getGCRoots()) { + // A GC root is identified by its HPROF ID. Some valid root + // records do not have a corresponding heap Instance. + roots.put(root.getInstanceId(), root); } gcRoots = roots; } else { @@ -126,7 +126,7 @@ ThreadObjectGCRoot getThreadGCRoot(int threadSerialNumber) { return map.get(threadSerialNumber); } - private void computeGCRootsFor(TagBounds tagBounds, Collection roots) { + private void computeGCRootsFor(TagBounds tagBounds, Collection roots) { if (tagBounds != null) { int rootTag = tagBounds.tag; long[] offset = new long[]{tagBounds.startOffset}; diff --git a/profiler/lib.profiler/test/unit/src/org/netbeans/lib/profiler/heap/HeapSegmentTest.java b/profiler/lib.profiler/test/unit/src/org/netbeans/lib/profiler/heap/HeapSegmentTest.java index 87c01062424f..28a96062843c 100644 --- a/profiler/lib.profiler/test/unit/src/org/netbeans/lib/profiler/heap/HeapSegmentTest.java +++ b/profiler/lib.profiler/test/unit/src/org/netbeans/lib/profiler/heap/HeapSegmentTest.java @@ -31,6 +31,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import org.junit.Test; import org.netbeans.lib.profiler.heap.HeapUtils.HprofGenerator; @@ -45,6 +47,67 @@ public void singleObjectMultipleSegments() throws IOException { singleObject(true); } + @Test + public void stickyClassRootKeepsStaticReferencesReachable() throws IOException { + File mydump = File.createTempFile("mydump", ".hprof"); + final int[] targetId = new int[1]; + try (HprofGenerator gen = new HprofGenerator(new FileOutputStream(mydump))) { + gen.writeHeapSegment(new HprofGenerator.Generator() { + @Override + public void generate(HprofGenerator.HeapSegment seg) throws IOException { + seg.newClass("java.lang.Class").dumpClass(); + seg.newClass("com.oracle.svm.core.heap.heapImpl.DiscoverableReference") + .addField("rawReferent", Object.class) + .dumpClass(); + HprofGenerator.ClassInstance targetClass = seg.newClass("text.Target").dumpClass(); + targetId[0] = seg.dumpInstance(targetClass); + HprofGenerator.ClassInstance rootClass = seg.newClass("text.Root") + .addStaticObjectField("target", targetId[0]) + .dumpClass(); + seg.dumpStickyClassRoot(rootClass); + } + }, true); + } + + Heap heap = HeapFactory.createHeap(mydump); + assertEquals("One sticky class root", 1, heap.getGCRoots().size()); + GCRoot root = (GCRoot) heap.getGCRoots().iterator().next(); + Instance rootInstance = root.getInstance(); + assertTrue("Class object instance", rootInstance instanceof ClassDumpInstance); + assertEquals("Root found by class object ID", root, heap.getGCRoot(rootInstance)); + + Instance target = heap.getInstanceByID(targetId[0]); + assertNotNull("Static field target", target); + heap.getBiggestObjectsByRetainedSize(1); + assertEquals("Target reached from sticky class", rootInstance, target.getNearestGCRootPointer()); + assertTrue("Sticky class retains its static target", rootInstance.getRetainedSize() > rootInstance.getSize()); + } + + @Test + public void unresolvedStickyClassRootDoesNotBreakRetainedSize() throws IOException { + File mydump = File.createTempFile("mydump", ".hprof"); + try (HprofGenerator gen = new HprofGenerator(new FileOutputStream(mydump))) { + gen.writeHeapSegment(new HprofGenerator.Generator() { + @Override + public void generate(HprofGenerator.HeapSegment seg) throws IOException { + seg.newClass("java.lang.Class").dumpClass(); + seg.newClass("com.oracle.svm.core.heap.heapImpl.DiscoverableReference") + .addField("rawReferent", Object.class) + .dumpClass(); + HprofGenerator.ClassInstance ordinaryClass = seg.newClass("text.Ordinary").dumpClass(); + seg.dumpInstance(ordinaryClass); + seg.dumpStickyClassRoot(Integer.MAX_VALUE); + } + }, true); + } + + Heap heap = HeapFactory.createHeap(mydump); + assertEquals("One unresolved sticky class root", 1, heap.getGCRoots().size()); + GCRoot root = (GCRoot) heap.getGCRoots().iterator().next(); + assertNull("No heap object for unresolved root", root.getInstance()); + assertNotNull("Retained-size computation completes", heap.getBiggestObjectsByRetainedSize(1)); + } + private static void singleObject(boolean flush) throws IOException { File mydump = File.createTempFile("mydump", ".hprof"); Heap heap = generateSampleDump(mydump, flush); diff --git a/profiler/lib.profiler/test/unit/src/org/netbeans/lib/profiler/heap/HeapUtils.java b/profiler/lib.profiler/test/unit/src/org/netbeans/lib/profiler/heap/HeapUtils.java index 115c99c63832..047cd78ed79e 100644 --- a/profiler/lib.profiler/test/unit/src/org/netbeans/lib/profiler/heap/HeapUtils.java +++ b/profiler/lib.profiler/test/unit/src/org/netbeans/lib/profiler/heap/HeapUtils.java @@ -170,6 +170,15 @@ public int dumpPrimitive(Object obj) throws IOException { return instanceId; } + public void dumpStickyClassRoot(ClassInstance clazz) throws IOException { + dumpStickyClassRoot(clazz.id); + } + + public void dumpStickyClassRoot(int classId) throws IOException { + heap.writeByte(0x05); + heap.writeInt(classId); + } + public final class ThreadBuilder { private String groupName; @@ -235,6 +244,7 @@ public final class ClassBuilder { private final int classId; private TreeMap> fieldNamesAndTypes = new TreeMap<>(); + private TreeMap staticObjectFields = new TreeMap<>(); private ClassBuilder(int id) { this.classId = id; @@ -245,6 +255,11 @@ public ClassBuilder addField(String name, Class type) { return this; } + public ClassBuilder addStaticObjectField(String name, int instanceId) { + staticObjectFields.put(name, instanceId); + return this; + } + public ClassInstance dumpClass() throws IOException { heap.writeByte(0x20); heap.writeInt(classId); // class ID @@ -257,7 +272,12 @@ public ClassInstance dumpClass() throws IOException { heap.writeInt(0); // reserved 2 heap.writeInt(0); // instance size heap.writeShort(0); // # of constant pool entries - heap.writeShort(0); // # of static fields + heap.writeShort(staticObjectFields.size()); // # of static fields + for (Map.Entry entry : staticObjectFields.entrySet()) { + heap.writeInt(writeString(entry.getKey())); + heap.writeByte(0x02); // object + heap.writeInt(entry.getValue()); + } heap.writeShort(fieldNamesAndTypes.size()); // # of instance fields int fieldBytes = 0; for (Map.Entry> entry : fieldNamesAndTypes.entrySet()) { @@ -299,6 +319,7 @@ public ClassInstance dumpClass() throws IOException { } ClassInstance inst = new ClassInstance(classId, fieldNamesAndTypes, fieldBytes); fieldNamesAndTypes = new TreeMap<>(); + staticObjectFields = new TreeMap<>(); return inst; } }