Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.ibm.wala.util.heapTrace.HeapTracer Maven / Gradle / Ivy
/*
* Copyright (c) 2002 - 2006 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*/
package com.ibm.wala.util.heapTrace;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.debug.Assertions;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
/** Simple utility that uses reflection to trace memory */
public class HeapTracer {
private static final boolean DEBUG = false;
/** Names of all classes considered for analysis */
private static final String[] rootClasses = generateRootClassesFromWorkspace();
/** Object instances that should be considered roots of the heap trace */
private final Collection> rootInstances;
/** Stack of instance objects discovered but not yet traced. */
private final ArrayDeque scalarWorkList = new ArrayDeque<>();
/** Stack of array objects discovered but not yet traced. */
private final ArrayDeque arrayWorkList = new ArrayDeque<>();
/** How many bytes do we assume the JVM wastes on each instance? */
private static final int BYTES_IN_HEADER = 12;
/** Should all static fields be considered roots of the heap traversal? */
private final boolean traceStatics;
/** Map: Class -> Integer, the size of each class */
private final HashMap, Integer> sizeMap = HashMapFactory.make();
private static final Object DUMMY = new Object();
/** Classes that we should understand because they're internal to a container object */
private static final HashSet> internalClasses = HashSetFactory.make();
static {
try {
internalClasses.add(Class.forName("java.lang.String"));
internalClasses.add(Class.forName("java.util.HashMap$Entry"));
internalClasses.add(Class.forName("java.util.HashMap"));
internalClasses.add(Class.forName("java.util.HashSet"));
internalClasses.add(Class.forName("java.util.Vector"));
internalClasses.add(Class.forName("com.ibm.wala.util.collections.SmallMap"));
internalClasses.add(Class.forName("com.ibm.wala.util.collections.SimpleVector"));
internalClasses.add(Class.forName("com.ibm.wala.util.intset.SimpleIntVector"));
internalClasses.add(Class.forName("com.ibm.wala.util.intset.BasicNaturalRelation"));
internalClasses.add(Class.forName("com.ibm.wala.util.intset.SparseIntSet"));
internalClasses.add(Class.forName("com.ibm.wala.util.collections.SparseVector"));
internalClasses.add(Class.forName("com.ibm.wala.util.intset.MutableSharedBitVectorIntSet"));
internalClasses.add(Class.forName("com.ibm.wala.util.intset.MutableSparseIntSet"));
internalClasses.add(Class.forName("com.ibm.wala.util.collections.TwoLevelVector"));
internalClasses.add(
Class.forName("com.ibm.wala.util.graph.impl.DelegatingNumberedNodeManager"));
internalClasses.add(Class.forName("com.ibm.wala.util.graph.impl.SparseNumberedEdgeManager"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
Assertions.UNREACHABLE();
}
}
/**
* @param traceStatics Should all static fields be considered roots of the heap traversal?
*/
HeapTracer(boolean traceStatics) {
rootInstances = Collections.emptySet();
this.traceStatics = traceStatics;
}
public HeapTracer(Collection> c, boolean traceStatics) {
rootInstances = c;
this.traceStatics = traceStatics;
}
public static void main(String[] args) {
try {
Result r = new HeapTracer(true).perform();
System.err.println(r.toString());
} catch (Throwable t) {
t.printStackTrace();
}
}
/**
* @return the name of each class that's in the classpath
*/
private static String[] generateRootClassesFromWorkspace() {
String classpath = System.getProperty("java.class.path");
Object[] binDirectories = extractBinDirectories(classpath);
HashSet classFileNames = HashSetFactory.make();
for (Object binDirectorie : binDirectories) {
String dir = (String) binDirectorie;
File fdir = new File(dir);
classFileNames.addAll(findClassNames(dir, fdir));
}
return classFileNames.toArray(new String[0]);
}
/**
* @param rootDir root of the classpath governing file f
* @param f a File or directory
* @return {@link Collection}<{@link String}> representing the class names in f
*/
private static Collection findClassNames(String rootDir, File f) {
HashSet result = HashSetFactory.make();
if (f.isDirectory()) {
File[] files = f.listFiles();
for (File file : files) {
result.addAll(findClassNames(rootDir, file));
}
} else {
if (f.getName().indexOf(".class") > 0) {
String p = f.getAbsolutePath();
p = p.substring(rootDir.length() + 1);
// trim the last 6 characters which hold ".class"
p = p.substring(0, p.length() - 6);
p = p.replace('\\', '.');
return Collections.singleton(p);
}
}
return result;
}
/**
* @return duplicate-free array of strings that are names of directories that contain "bin"
*/
private static Object[] extractBinDirectories(String classpath) {
StringTokenizer t = new StringTokenizer(classpath, ";");
HashSet result = HashSetFactory.make();
while (t.hasMoreTokens()) {
String n = t.nextToken();
if (n.indexOf("bin") > 0) {
result.add(n);
}
}
return result.toArray();
}
/** Trace the heap and return the results */
public Result perform()
throws ClassNotFoundException, IllegalArgumentException, IllegalAccessException {
Result result = new Result();
IdentityHashMap objectsVisited = new IdentityHashMap<>();
if (traceStatics) {
for (String rootClasse : rootClasses) {
Class> c = Class.forName(rootClasse);
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
if (isStatic(field)) {
traverse(field, result, objectsVisited);
}
}
}
}
for (Object instance : rootInstances) {
Class> c = instance.getClass();
Set fields = getAllInstanceFields(c);
for (Field f : fields) {
traverse(f, instance, result, objectsVisited);
}
}
return result;
}
/**
* @return the estimated size of the object
*/
private static int computeSizeOf(Object o) {
int result = BYTES_IN_HEADER;
Class> c = o.getClass();
if (c.isArray()) {
Class> elementType = c.getComponentType();
int length = Array.getLength(o);
result += length * sizeOfSlot(elementType);
} else if (c.isPrimitive()) {
throw new Error();
} else {
Collection fields = getAllInstanceFields(c);
for (Field f : fields) {
result += sizeOfSlot(f.getType());
}
}
return result;
}
/**
* @return the estimated size of the object
*/
private int sizeOf(Object o) {
Class> c = o.getClass();
if (c.isArray()) {
return computeSizeOf(o);
} else {
Integer S = sizeMap.get(c);
if (S == null) {
S = computeSizeOf(o);
sizeMap.put(c, S);
}
return S;
}
}
/**
* @return size of a field of type c, in bytes
*/
private static int sizeOfSlot(Class> c) {
if (!c.isPrimitive()) {
return 4;
} else {
if (c.equals(Boolean.TYPE) || c.equals(Character.TYPE) || c.equals(Byte.TYPE)) {
return 1;
} else if (c.equals(Short.TYPE)) {
return 2;
} else if (c.equals(Integer.TYPE) || c.equals(Float.TYPE)) {
return 4;
} else if (c.equals(Long.TYPE) || c.equals(Double.TYPE)) {
return 8;
} else {
throw new Error();
}
}
}
/** Traverse the heap starting at a static field */
private void traverse(Field root, Result result, IdentityHashMap objectsVisited)
throws IllegalArgumentException, IllegalAccessException {
if (DEBUG) {
System.err.println(("traverse root " + root));
}
root.setAccessible(true);
Object contents = root.get(null);
if (contents != null && (objectsVisited.get(contents) == null)) {
objectsVisited.put(contents, DUMMY);
Class> c = contents.getClass();
if (c.isArray()) {
result.registerReachedFrom(root, root, contents);
arrayWorkList.push(Pair.make(root, contents));
} else {
result.registerReachedFrom(root, root, contents);
if (internalClasses.contains(c)) {
contents = Pair.make(root, contents);
}
scalarWorkList.push(contents);
}
}
drainWorkLists(root, result, objectsVisited);
}
private void traverse(
Field root, Object instance, Result result, IdentityHashMap objectsVisited)
throws IllegalArgumentException, IllegalAccessException {
traverseFieldOfScalar(root, root, instance, null, objectsVisited, result);
drainWorkLists(root, result, objectsVisited);
}
@SuppressWarnings("rawtypes")
private void drainWorkLists(
Field root, Result result, IdentityHashMap objectsVisited)
throws IllegalAccessException {
while (!scalarWorkList.isEmpty() || !arrayWorkList.isEmpty()) {
if (!scalarWorkList.isEmpty()) {
Object scalar = scalarWorkList.pop();
if (scalar instanceof Pair) {
Pair p = (Pair) scalar;
traverseScalar(root, p.snd, p.fst, result, objectsVisited);
} else {
traverseScalar(root, scalar, null, result, objectsVisited);
}
}
if (!arrayWorkList.isEmpty()) {
Pair p = (Pair) arrayWorkList.pop();
traverseArray(root, p.snd, p.fst, result, objectsVisited);
}
}
}
private void traverseArray(
Field root,
Object array,
Object container,
Result result,
IdentityHashMap objectsVisited)
throws IllegalArgumentException {
if (DEBUG) {
System.err.println(("traverse array " + array.getClass()));
}
Class> elementKlass = array.getClass().getComponentType();
if (elementKlass.isPrimitive()) {
return;
}
assert container != null;
int length = Array.getLength(array);
for (int i = 0; i < length; i++) {
Object contents = Array.get(array, i);
if (contents != null && (objectsVisited.get(contents) == null)) {
objectsVisited.put(contents, DUMMY);
Class> klass = contents.getClass();
if (klass.isArray()) {
result.registerReachedFrom(root, container, contents);
contents = Pair.make(container, contents);
arrayWorkList.push(contents);
} else {
result.registerReachedFrom(root, container, contents);
contents = Pair.make(container, contents);
scalarWorkList.push(contents);
}
}
}
}
private void traverseScalar(
Field root,
Object scalar,
@Nullable Object container,
Result result,
IdentityHashMap objectsVisited)
throws IllegalArgumentException, IllegalAccessException {
Class> c = scalar.getClass();
if (DEBUG) {
System.err.println(("traverse scalar " + c));
}
Field[] fields = getAllReferenceInstanceFields(c);
for (Field field : fields) {
traverseFieldOfScalar(root, field, scalar, container, objectsVisited, result);
}
}
private final Object OK = new Object();
private final Object BAD = new Object();
private final HashMap packageStatus = HashMapFactory.make();
private final boolean isInBadPackage(Class> c) {
Package p = c.getPackage();
if (p == null) {
return false;
}
Object status = packageStatus.get(p);
if (status == OK) {
return false;
} else if (status == BAD) {
return true;
} else {
if (p.getName() != null && p.getName().contains("sun.reflect")) {
if (DEBUG) {
System.err.println(("making " + p + " a BAD package"));
}
packageStatus.put(p, BAD);
return true;
} else {
if (DEBUG) {
System.err.println(("making " + p + " an OK package"));
}
packageStatus.put(p, OK);
return false;
}
}
}
private void traverseFieldOfScalar(
Field root,
Field f,
Object scalar,
@Nullable Object container,
IdentityHashMap objectsVisited,
Result result)
throws IllegalArgumentException, IllegalAccessException {
// The following atrocious hack is needed to prevent the IBM 141 VM
// from crashing
if (isInBadPackage(f.getType())) {
return;
}
if (container == null) {
container = f;
}
f.setAccessible(true);
Object contents = f.get(scalar);
if (contents != null && (objectsVisited.get(contents) == null)) {
try {
objectsVisited.put(contents, DUMMY);
} catch (Exception e) {
e.printStackTrace();
return;
}
Class> klass = contents.getClass();
if (klass.isArray()) {
result.registerReachedFrom(root, container, contents);
contents = Pair.make(container, contents);
arrayWorkList.push(contents);
} else {
result.registerReachedFrom(root, container, contents);
if (internalClasses.contains(klass)) {
contents = Pair.make(container, contents);
}
scalarWorkList.push(contents);
}
}
}
private static HashSet getAllInstanceFields(Class> c) {
HashSet result = HashSetFactory.make();
Class> klass = c;
while (klass != null) {
Field[] fields = klass.getDeclaredFields();
for (Field field : fields) {
if (!isStatic(field)) {
result.add(field);
}
}
klass = klass.getSuperclass();
}
return result;
}
private final HashMap, Field[]> allReferenceFieldsCache = HashMapFactory.make();
/**
* @return Field[] representing reference instance fields of a class
*/
private Field[] getAllReferenceInstanceFields(Class> c) {
if (allReferenceFieldsCache.containsKey(c)) return allReferenceFieldsCache.get(c);
else {
HashSet s = HashSetFactory.make();
Class> klass = c;
while (klass != null) {
Field[] fields = klass.getDeclaredFields();
for (Field field : fields) {
if (!isStatic(field)) {
Class> fc = field.getType();
if (!fc.isPrimitive()) {
s.add(field);
}
}
}
klass = klass.getSuperclass();
}
Field[] result = s.toArray(new Field[0]);
allReferenceFieldsCache.put(c, result);
return result;
}
}
private static boolean isStatic(Field field) {
return Modifier.isStatic(field.getModifiers());
}
public static void analyzeLeaks() {
analyzeLeaks(true);
}
/**
* Trace the heap and dump the output to the tracefile
*
* @param traceStatics should all static fields be considered roots?
*/
public static void analyzeLeaks(boolean traceStatics) {
try {
System.gc();
System.gc();
System.gc();
System.gc();
System.gc();
long t = Runtime.getRuntime().totalMemory();
long f = Runtime.getRuntime().freeMemory();
System.err.println(("Total Memory: " + t));
System.err.println(("Occupied Memory: " + (t - f)));
HeapTracer.Result r = new HeapTracer(traceStatics).perform();
System.err.println("HeapTracer Analysis:");
System.err.println(r.toString());
} catch (IllegalArgumentException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* Trace the heap and dump the output to the tracefile
*
* @param instances instances to be considered roots of the heap traversal
* @param traceStatics should all static fields be considered roots?
*/
@NullUnmarked
public static HeapTracer.Result traceHeap(Collection> instances, boolean traceStatics) {
try {
System.gc();
System.gc();
System.gc();
System.gc();
System.gc();
long t = Runtime.getRuntime().totalMemory();
long f = Runtime.getRuntime().freeMemory();
System.err.println(("Total Memory: " + t));
System.err.println(("Occupied Memory: " + (t - f)));
HeapTracer.Result r = new HeapTracer(instances, traceStatics).perform();
System.err.println("HeapTracer Analysis:");
System.err.println(r.toString());
return r;
} catch (IllegalArgumentException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* @author sfink
* Statistics about objects reached from a single root
*/
class Demographics {
/** mapping: Object (key) -> Integer (number of instances in a partition) */
private final HashMap instanceCount = HashMapFactory.make();
/** mapping: Object (key) -> Integer (bytes) */
private final HashMap sizeCount = HashMapFactory.make();
/** Total number of instances discovered */
private int totalInstances = 0;
/** Total number of bytes discovered */
private int totalSize = 0;
/**
* @param key a name for the heap partition to which o belongs
* @param o the object to register
*/
public void registerObject(Object key, Object o) {
Integer I = instanceCount.get(key);
int newCount = (I == null) ? 1 : I + 1;
instanceCount.put(key, newCount);
totalInstances++;
I = sizeCount.get(key);
int s = sizeOf(o);
int newSizeCount = (I == null) ? s : I + s;
sizeCount.put(key, newSizeCount);
totalSize += s;
}
@NullUnmarked
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("Totals: ").append(totalInstances).append(' ').append(totalSize).append('\n');
TreeSet sorted = new TreeSet<>(new SizeComparator());
sorted.addAll(instanceCount.keySet());
for (Object key : sorted) {
Integer I = instanceCount.get(key);
Integer bytes = sizeCount.get(key);
result.append(" ").append(I).append(" ").append(bytes).append(" ");
result.append(bytes / I).append(" ");
result.append(key);
result.append('\n');
}
return result.toString();
}
/** compares two keys based on the total size of the heap traced from that key */
private class SizeComparator implements Comparator {
/*
* @see java.util.Comparator#compare(java.lang.Object,
* java.lang.Object)
*/
@NullUnmarked
@Override
public int compare(Object o1, Object o2) {
Integer i1 = sizeCount.get(o1);
Integer i2 = sizeCount.get(o2);
return i2 - i1;
}
}
/**
* @return Returns the totalSize.
*/
public int getTotalSize() {
return totalSize;
}
/**
* @return Returns the totalInstances.
*/
public int getTotalInstances() {
return totalInstances;
}
}
/**
* @author sfink
* Results of the heap trace
*/
public class Result {
/** a mapping from Field (static field roots) -> Demographics object */
private final HashMap roots = HashMapFactory.make();
/**
* @return the Demographics object tracking objects traced from that root
*/
private Demographics findOrCreateDemographics(Field root) {
Demographics d = roots.get(root);
if (d == null) {
d = new Demographics();
roots.put(root, d);
}
return d;
}
public void registerReachedFrom(Field root, Object predecessor, Object contents) {
Demographics d = findOrCreateDemographics(root);
d.registerObject(Pair.make(predecessor, contents.getClass()), contents);
}
public int getTotalSize() {
int totalSize = 0;
for (Demographics d : roots.values()) {
totalSize += d.getTotalSize();
}
return totalSize;
}
@NullUnmarked
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("Assuming " + BYTES_IN_HEADER + " header bytes per object\n");
int totalInstances = 0;
int totalSize = 0;
for (Demographics d : roots.values()) {
totalInstances += d.getTotalInstances();
totalSize += d.getTotalSize();
}
result.append("Total instances: ").append(totalInstances).append('\n');
result.append("Total size(bytes): ").append(totalSize).append('\n');
TreeSet sortedDemo = new TreeSet<>(new SizeComparator());
sortedDemo.addAll(roots.keySet());
for (Field field : sortedDemo) {
Object root = field;
Demographics d = roots.get(root);
if (d.getTotalSize() > 10000) {
result.append(" root: ").append(root).append('\n');
result.append(d);
}
}
return result.toString();
}
/** compares two keys based on the total size of the heap traced from that key */
private class SizeComparator implements Comparator {
/*
* @see java.util.Comparator#compare(java.lang.Object,
* java.lang.Object)
*/
@NullUnmarked
@Override
public int compare(Field o1, Field o2) {
Demographics d1 = roots.get(o1);
Demographics d2 = roots.get(o2);
return d2.getTotalSize() - d1.getTotalSize();
}
}
}
}