Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ class HprofGCRoots {
private final Object lastThreadObjGCLock = new Object();
private Map<Long,GCRoot> gcRoots;
private final Object gcRootLock = new Object();
private List gcRootsList;
private List<HprofGCRoot> gcRootsList;

HprofGCRoots(HprofHeap h) {
heap = h;
}

Collection<GCRoot> getGCRoots() {
Collection<HprofGCRoot> getGCRoots() {
synchronized (gcRootLock) {
if (gcRoots == null) {
List<GCRoot> rootList = new ArrayList<>();
List<HprofGCRoot> 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);
Expand All @@ -67,10 +67,8 @@ Collection<GCRoot> 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<HprofGCRoot>() {
public int compare(HprofGCRoot r1, HprofGCRoot r2) {
int kind = r1.getKind().compareTo(r2.getKind());

if (kind != 0) {
Expand All @@ -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 {
Expand Down Expand Up @@ -126,7 +126,7 @@ ThreadObjectGCRoot getThreadGCRoot(int threadSerialNumber) {
return map.get(threadSerialNumber);
}

private void computeGCRootsFor(TagBounds tagBounds, Collection<GCRoot> roots) {
private void computeGCRootsFor(TagBounds tagBounds, Collection<HprofGCRoot> roots) {
if (tagBounds != null) {
int rootTag = tagBounds.tag;
long[] offset = new long[]{tagBounds.startOffset};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<HprofGenerator.HeapSegment>() {
@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<HprofGenerator.HeapSegment>() {
@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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -235,6 +244,7 @@ public final class ClassBuilder {

private final int classId;
private TreeMap<String, Class<?>> fieldNamesAndTypes = new TreeMap<>();
private TreeMap<String, Integer> staticObjectFields = new TreeMap<>();

private ClassBuilder(int id) {
this.classId = id;
Expand All @@ -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
Expand All @@ -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<String, Integer> 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<String, Class<?>> entry : fieldNamesAndTypes.entrySet()) {
Expand Down Expand Up @@ -299,6 +319,7 @@ public ClassInstance dumpClass() throws IOException {
}
ClassInstance inst = new ClassInstance(classId, fieldNamesAndTypes, fieldBytes);
fieldNamesAndTypes = new TreeMap<>();
staticObjectFields = new TreeMap<>();
return inst;
}
}
Expand Down