Skip to content

Commit 092044f

Browse files
committed
Fix #6357: retain sticky class GC roots by ID
Assisted-by: OpenAI GPT-5 Codex
1 parent 26a988b commit 092044f

3 files changed

Lines changed: 95 additions & 11 deletions

File tree

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

Lines changed: 10 additions & 10 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,8 +90,10 @@ GCRoot getGCRoot(Long instanceId) {
9290
if (gcRoots == null) {
9391
heap.getGCRoots();
9492
roots = new HashMap<>();
95-
for (GCRoot r : getGCRoots()) {
96-
roots.put(r.getInstance().getInstanceId(), r);
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);
9797
}
9898
gcRoots = roots;
9999
} else {
@@ -126,7 +126,7 @@ ThreadObjectGCRoot getThreadGCRoot(int threadSerialNumber) {
126126
return map.get(threadSerialNumber);
127127
}
128128

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

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import static org.junit.Assert.assertEquals;
3232
import static org.junit.Assert.assertFalse;
3333
import static org.junit.Assert.assertNotNull;
34+
import static org.junit.Assert.assertNull;
35+
import static org.junit.Assert.assertTrue;
3436
import org.junit.Test;
3537
import org.netbeans.lib.profiler.heap.HeapUtils.HprofGenerator;
3638

@@ -45,6 +47,67 @@ public void singleObjectMultipleSegments() throws IOException {
4547
singleObject(true);
4648
}
4749

50+
@Test
51+
public void stickyClassRootKeepsStaticReferencesReachable() throws IOException {
52+
File mydump = File.createTempFile("mydump", ".hprof");
53+
final int[] targetId = new int[1];
54+
try (HprofGenerator gen = new HprofGenerator(new FileOutputStream(mydump))) {
55+
gen.writeHeapSegment(new HprofGenerator.Generator<HprofGenerator.HeapSegment>() {
56+
@Override
57+
public void generate(HprofGenerator.HeapSegment seg) throws IOException {
58+
seg.newClass("java.lang.Class").dumpClass();
59+
seg.newClass("com.oracle.svm.core.heap.heapImpl.DiscoverableReference")
60+
.addField("rawReferent", Object.class)
61+
.dumpClass();
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);
100+
}
101+
}, true);
102+
}
103+
104+
Heap heap = HeapFactory.createHeap(mydump);
105+
assertEquals("One unresolved sticky class root", 1, heap.getGCRoots().size());
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));
109+
}
110+
48111
private static void singleObject(boolean flush) throws IOException {
49112
File mydump = File.createTempFile("mydump", ".hprof");
50113
Heap heap = generateSampleDump(mydump, flush);

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,15 @@ public int dumpPrimitive(Object obj) throws IOException {
170170
return instanceId;
171171
}
172172

173+
public void dumpStickyClassRoot(ClassInstance clazz) throws IOException {
174+
dumpStickyClassRoot(clazz.id);
175+
}
176+
177+
public void dumpStickyClassRoot(int classId) throws IOException {
178+
heap.writeByte(0x05);
179+
heap.writeInt(classId);
180+
}
181+
173182
public final class ThreadBuilder {
174183

175184
private String groupName;
@@ -235,6 +244,7 @@ public final class ClassBuilder {
235244

236245
private final int classId;
237246
private TreeMap<String, Class<?>> fieldNamesAndTypes = new TreeMap<>();
247+
private TreeMap<String, Integer> staticObjectFields = new TreeMap<>();
238248

239249
private ClassBuilder(int id) {
240250
this.classId = id;
@@ -245,6 +255,11 @@ public ClassBuilder addField(String name, Class<?> type) {
245255
return this;
246256
}
247257

258+
public ClassBuilder addStaticObjectField(String name, int instanceId) {
259+
staticObjectFields.put(name, instanceId);
260+
return this;
261+
}
262+
248263
public ClassInstance dumpClass() throws IOException {
249264
heap.writeByte(0x20);
250265
heap.writeInt(classId); // class ID
@@ -257,7 +272,12 @@ public ClassInstance dumpClass() throws IOException {
257272
heap.writeInt(0); // reserved 2
258273
heap.writeInt(0); // instance size
259274
heap.writeShort(0); // # of constant pool entries
260-
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+
}
261281
heap.writeShort(fieldNamesAndTypes.size()); // # of instance fields
262282
int fieldBytes = 0;
263283
for (Map.Entry<String, Class<?>> entry : fieldNamesAndTypes.entrySet()) {
@@ -299,6 +319,7 @@ public ClassInstance dumpClass() throws IOException {
299319
}
300320
ClassInstance inst = new ClassInstance(classId, fieldNamesAndTypes, fieldBytes);
301321
fieldNamesAndTypes = new TreeMap<>();
322+
staticObjectFields = new TreeMap<>();
302323
return inst;
303324
}
304325
}

0 commit comments

Comments
 (0)