Skip to content

Commit 66beb5d

Browse files
committed
Fix #6357: retain sticky class GC roots by ID
Assisted-by: OpenAI GPT-5 Codex
1 parent 4f123fb commit 66beb5d

3 files changed

Lines changed: 73 additions & 21 deletions

File tree

profiler/lib.profiler/src/org/netbeans/lib/profiler/heap/HprofGCRoots.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@ class HprofGCRoots {
3939
private final Object lastThreadObjGCLock = new Object();
4040
private Map<Long,GCRoot> gcRoots;
4141
private final Object gcRootLock = new Object();
42-
private List gcRootsList;
42+
private List<HprofGCRoot> gcRootsList;
4343

4444
HprofGCRoots(HprofHeap h) {
4545
heap = h;
4646
}
4747

48-
Collection<GCRoot> getGCRoots() {
48+
Collection<HprofGCRoot> getGCRoots() {
4949
synchronized (gcRootLock) {
5050
if (gcRoots == null) {
51-
List<GCRoot> rootList = new ArrayList<>();
51+
List<HprofGCRoot> rootList = new ArrayList<>();
5252
computeGCRootsFor(heap.getHeapTagBound(HprofHeap.ROOT_UNKNOWN), rootList);
5353
computeGCRootsFor(heap.getHeapTagBound(HprofHeap.ROOT_JNI_GLOBAL), rootList);
5454
computeGCRootsFor(heap.getHeapTagBound(HprofHeap.ROOT_JNI_LOCAL), rootList);
@@ -67,10 +67,8 @@ Collection<GCRoot> getGCRoots() {
6767
computeGCRootsFor(heap.getHeapTagBound(HprofHeap.ROOT_VM_INTERNAL), rootList);
6868
computeGCRootsFor(heap.getHeapTagBound(HprofHeap.ROOT_JNI_MONITOR), rootList);
6969

70-
rootList.sort(new Comparator() {
71-
public int compare(Object o1, Object o2) {
72-
HprofGCRoot r1 = (HprofGCRoot) o1;
73-
HprofGCRoot r2 = (HprofGCRoot) o2;
70+
rootList.sort(new Comparator<HprofGCRoot>() {
71+
public int compare(HprofGCRoot r1, HprofGCRoot r2) {
7472
int kind = r1.getKind().compareTo(r2.getKind());
7573

7674
if (kind != 0) {
@@ -92,11 +90,10 @@ GCRoot getGCRoot(Long instanceId) {
9290
if (gcRoots == null) {
9391
heap.getGCRoots();
9492
roots = new HashMap<>();
95-
for (GCRoot r : getGCRoots()) {
96-
Instance instance = r.getInstance();
97-
if (instance != null) {
98-
roots.put(instance.getInstanceId(), r);
99-
}
93+
for (HprofGCRoot root : getGCRoots()) {
94+
// A GC root is identified by its HPROF ID. Some valid root
95+
// records do not have a corresponding heap Instance.
96+
roots.put(root.getInstanceId(), root);
10097
}
10198
gcRoots = roots;
10299
} else {
@@ -129,7 +126,7 @@ ThreadObjectGCRoot getThreadGCRoot(int threadSerialNumber) {
129126
return map.get(threadSerialNumber);
130127
}
131128

132-
private void computeGCRootsFor(TagBounds tagBounds, Collection<GCRoot> roots) {
129+
private void computeGCRootsFor(TagBounds tagBounds, Collection<HprofGCRoot> roots) {
133130
if (tagBounds != null) {
134131
int rootTag = tagBounds.tag;
135132
long[] offset = new long[]{tagBounds.startOffset};

profiler/lib.profiler/test/unit/src/org/netbeans/lib/profiler/heap/HeapSegmentTest.java

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import static org.junit.Assert.assertFalse;
3333
import static org.junit.Assert.assertNotNull;
3434
import static org.junit.Assert.assertNull;
35+
import static org.junit.Assert.assertTrue;
3536
import org.junit.Test;
3637
import org.netbeans.lib.profiler.heap.HeapUtils.HprofGenerator;
3738

@@ -47,26 +48,64 @@ public void singleObjectMultipleSegments() throws IOException {
4748
}
4849

4950
@Test
50-
public void unresolvedStickyClassRootDoesNotBreakGCRootLookup() throws IOException {
51+
public void stickyClassRootKeepsStaticReferencesReachable() throws IOException {
5152
File mydump = File.createTempFile("mydump", ".hprof");
53+
final int[] targetId = new int[1];
5254
try (HprofGenerator gen = new HprofGenerator(new FileOutputStream(mydump))) {
5355
gen.writeHeapSegment(new HprofGenerator.Generator<HprofGenerator.HeapSegment>() {
5456
@Override
5557
public void generate(HprofGenerator.HeapSegment seg) throws IOException {
58+
seg.newClass("java.lang.Class").dumpClass();
5659
seg.newClass("com.oracle.svm.core.heap.heapImpl.DiscoverableReference")
5760
.addField("rawReferent", Object.class)
5861
.dumpClass();
59-
HprofGenerator.ClassInstance clazz = seg.newClass("text.HelloWorld").dumpClass();
60-
seg.dumpStickyClassRoot(clazz);
61-
seg.dumpInstance(clazz);
62+
HprofGenerator.ClassInstance targetClass = seg.newClass("text.Target").dumpClass();
63+
targetId[0] = seg.dumpInstance(targetClass);
64+
HprofGenerator.ClassInstance rootClass = seg.newClass("text.Root")
65+
.addStaticObjectField("target", targetId[0])
66+
.dumpClass();
67+
seg.dumpStickyClassRoot(rootClass);
68+
}
69+
}, true);
70+
}
71+
72+
Heap heap = HeapFactory.createHeap(mydump);
73+
assertEquals("One sticky class root", 1, heap.getGCRoots().size());
74+
GCRoot root = (GCRoot) heap.getGCRoots().iterator().next();
75+
Instance rootInstance = root.getInstance();
76+
assertTrue("Class object instance", rootInstance instanceof ClassDumpInstance);
77+
assertEquals("Root found by class object ID", root, heap.getGCRoot(rootInstance));
78+
79+
Instance target = heap.getInstanceByID(targetId[0]);
80+
assertNotNull("Static field target", target);
81+
heap.getBiggestObjectsByRetainedSize(1);
82+
assertEquals("Target reached from sticky class", rootInstance, target.getNearestGCRootPointer());
83+
assertTrue("Sticky class retains its static target", rootInstance.getRetainedSize() > rootInstance.getSize());
84+
}
85+
86+
@Test
87+
public void unresolvedStickyClassRootDoesNotBreakRetainedSize() throws IOException {
88+
File mydump = File.createTempFile("mydump", ".hprof");
89+
try (HprofGenerator gen = new HprofGenerator(new FileOutputStream(mydump))) {
90+
gen.writeHeapSegment(new HprofGenerator.Generator<HprofGenerator.HeapSegment>() {
91+
@Override
92+
public void generate(HprofGenerator.HeapSegment seg) throws IOException {
93+
seg.newClass("java.lang.Class").dumpClass();
94+
seg.newClass("com.oracle.svm.core.heap.heapImpl.DiscoverableReference")
95+
.addField("rawReferent", Object.class)
96+
.dumpClass();
97+
HprofGenerator.ClassInstance ordinaryClass = seg.newClass("text.Ordinary").dumpClass();
98+
seg.dumpInstance(ordinaryClass);
99+
seg.dumpStickyClassRoot(Integer.MAX_VALUE);
62100
}
63101
}, true);
64102
}
65103

66104
Heap heap = HeapFactory.createHeap(mydump);
67105
assertEquals("One unresolved sticky class root", 1, heap.getGCRoots().size());
68-
Instance instance = (Instance) heap.getJavaClassByName("text.HelloWorld").getInstances().iterator().next();
69-
assertNull("Non-root instance", heap.getGCRoot(instance));
106+
GCRoot root = (GCRoot) heap.getGCRoots().iterator().next();
107+
assertNull("No heap object for unresolved root", root.getInstance());
108+
assertNotNull("Retained-size computation completes", heap.getBiggestObjectsByRetainedSize(1));
70109
}
71110

72111
private static void singleObject(boolean flush) throws IOException {

profiler/lib.profiler/test/unit/src/org/netbeans/lib/profiler/heap/HeapUtils.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,12 @@ public int dumpPrimitive(Object obj) throws IOException {
171171
}
172172

173173
public void dumpStickyClassRoot(ClassInstance clazz) throws IOException {
174+
dumpStickyClassRoot(clazz.id);
175+
}
176+
177+
public void dumpStickyClassRoot(int classId) throws IOException {
174178
heap.writeByte(0x05);
175-
heap.writeInt(clazz.id);
179+
heap.writeInt(classId);
176180
}
177181

178182
public final class ThreadBuilder {
@@ -240,6 +244,7 @@ public final class ClassBuilder {
240244

241245
private final int classId;
242246
private TreeMap<String, Class<?>> fieldNamesAndTypes = new TreeMap<>();
247+
private TreeMap<String, Integer> staticObjectFields = new TreeMap<>();
243248

244249
private ClassBuilder(int id) {
245250
this.classId = id;
@@ -250,6 +255,11 @@ public ClassBuilder addField(String name, Class<?> type) {
250255
return this;
251256
}
252257

258+
public ClassBuilder addStaticObjectField(String name, int instanceId) {
259+
staticObjectFields.put(name, instanceId);
260+
return this;
261+
}
262+
253263
public ClassInstance dumpClass() throws IOException {
254264
heap.writeByte(0x20);
255265
heap.writeInt(classId); // class ID
@@ -262,7 +272,12 @@ public ClassInstance dumpClass() throws IOException {
262272
heap.writeInt(0); // reserved 2
263273
heap.writeInt(0); // instance size
264274
heap.writeShort(0); // # of constant pool entries
265-
heap.writeShort(0); // # of static fields
275+
heap.writeShort(staticObjectFields.size()); // # of static fields
276+
for (Map.Entry<String, Integer> entry : staticObjectFields.entrySet()) {
277+
heap.writeInt(writeString(entry.getKey()));
278+
heap.writeByte(0x02); // object
279+
heap.writeInt(entry.getValue());
280+
}
266281
heap.writeShort(fieldNamesAndTypes.size()); // # of instance fields
267282
int fieldBytes = 0;
268283
for (Map.Entry<String, Class<?>> entry : fieldNamesAndTypes.entrySet()) {
@@ -304,6 +319,7 @@ public ClassInstance dumpClass() throws IOException {
304319
}
305320
ClassInstance inst = new ClassInstance(classId, fieldNamesAndTypes, fieldBytes);
306321
fieldNamesAndTypes = new TreeMap<>();
322+
staticObjectFields = new TreeMap<>();
307323
return inst;
308324
}
309325
}

0 commit comments

Comments
 (0)