WALA/com.ibm.wala.core/src/com/ibm/wala/util/heapTrace/HeapTracer.java

775 lines
24 KiB
Java

/*******************************************************************************
* 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 java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
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.Iterator;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.TreeSet;
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;
/**
*
* Simple utility that uses reflection to trace memory
*
* @author sfink
*/
public class HeapTracer {
private static final boolean DEBUG = false;
/**
* Names of all classes considered for analysis
*/
final private static 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 Stack<Object> scalarWorkList = new Stack<Object>();
/**
* Stack of array objects discovered but not yet traced.
*/
private final Stack<Object> arrayWorkList = new Stack<Object>();
/**
* 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<Class<?>, 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<Class<?>> 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;
}
/**
* @param c
* @param traceStatics
*/
public HeapTracer(Collection<?> c, boolean traceStatics) {
rootInstances = c;
this.traceStatics = traceStatics;
}
/**
* @param args
*/
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<String> classFileNames = HashSetFactory.make();
for (int i = 0; i < binDirectories.length; i++) {
String dir = (String) binDirectories[i];
File fdir = new File(dir);
classFileNames.addAll(findClassNames(dir, fdir));
}
String[] result = new String[classFileNames.size()];
Iterator<String> it = classFileNames.iterator();
for (int i = 0; i < result.length; i++) {
result[i] = it.next();
}
return result;
}
/**
* @param fdir
* @return Collection <String>representing the class names in a particular
* file
*/
/**
* @param rootDir
* root of the classpath governing file f
* @param f
* a File or directory
* @return Collection <String>representing the class names in f
*/
private static Collection<String> findClassNames(String rootDir, File f) {
HashSet<String> result = HashSetFactory.make();
if (f.isDirectory()) {
File[] files = f.listFiles();
for (int i = 0; i < files.length; i++) {
result.addAll(findClassNames(rootDir, files[i]));
}
} 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;
}
/**
* @param classpath
* @return set of strings that are names of directories that contain "bin"
*/
private static Object[] extractBinDirectories(String classpath) {
StringTokenizer t = new StringTokenizer(classpath, ";");
HashSet<String> 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<Object, Object> objectsVisited = new IdentityHashMap<Object, Object>();
if (traceStatics) {
for (int i = 0; i < rootClasses.length; i++) {
Class c = Class.forName(rootClasses[i]);
Field[] fields = c.getDeclaredFields();
for (int j = 0; j < fields.length; j++) {
if (isStatic(fields[j])) {
traverse(fields[j], result, objectsVisited);
}
}
}
}
for (Iterator it = rootInstances.iterator(); it.hasNext();) {
Object instance = it.next();
Class c = instance.getClass();
Set<Field> fields = getAllInstanceFields(c);
for (Iterator<Field> it2 = fields.iterator(); it2.hasNext();) {
Field f = it2.next();
traverse(f, instance, result, objectsVisited);
}
}
return result;
}
/**
* @param o
* @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<Field> fields = getAllInstanceFields(c);
for (Iterator<Field> it = fields.iterator(); it.hasNext();) {
Field f = it.next();
result += sizeOfSlot(f.getType());
}
}
return result;
}
/**
* @param o
* @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 = new Integer(computeSizeOf(o));
sizeMap.put(c, S);
}
return S.intValue();
}
}
/**
* @param c
* @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
*
* @param result
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
private void traverse(Field root, Result result, IdentityHashMap<Object, Object> 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<Object, Object> objectsVisited)
throws IllegalArgumentException, IllegalAccessException {
traverseFieldOfScalar(root, root, instance, null, objectsVisited, result);
drainWorkLists(root, result, objectsVisited);
}
private void drainWorkLists(Field root, Result result, IdentityHashMap<Object, Object> 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<Object, Object> objectsVisited)
throws IllegalArgumentException {
if (DEBUG) {
System.err.println(("traverse array " + array.getClass()));
}
Class elementKlass = array.getClass().getComponentType();
if (elementKlass.isPrimitive()) {
return;
}
Assertions._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, Object container, Result result, IdentityHashMap<Object, Object> objectsVisited)
throws IllegalArgumentException, IllegalAccessException {
Class c = scalar.getClass();
if (DEBUG) {
System.err.println(("traverse scalar " + c));
}
Field[] fields = getAllReferenceInstanceFields(c);
for (int i = 0; i < fields.length; i++) {
traverseFieldOfScalar(root, fields[i], scalar, container, objectsVisited, result);
}
}
private final Object OK = new Object();
private final Object BAD = new Object();
private final HashMap<Package, Object> 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().indexOf("sun.reflect") != -1) {
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;
}
}
}
/**
* @param root
* @param f
* @param scalar
* @param container
* @param objectsVisited
* @param result
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
private void traverseFieldOfScalar(Field root, Field f, Object scalar, Object container, IdentityHashMap<Object, Object> 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<Field> getAllInstanceFields(Class c) {
HashSet<Field> result = HashSetFactory.make();
Class klass = c;
while (klass != null) {
Field[] fields = klass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (!isStatic(fields[i])) {
result.add(fields[i]);
}
}
klass = klass.getSuperclass();
}
return result;
}
final private HashMap<Class, 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<Field> s = HashSetFactory.make();
Class klass = c;
while (klass != null) {
Field[] fields = klass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (!isStatic(fields[i])) {
Class fc = fields[i].getType();
if (!fc.isPrimitive()) {
s.add(fields[i]);
}
}
}
klass = klass.getSuperclass();
}
Field[] result = new Field[s.size()];
Object[] temp = s.toArray();
for (int i = 0; i < result.length; i++) {
result[i] = (Field) temp[i];
}
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 e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException 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?
*/
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 e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException 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<Object, Integer> instanceCount = HashMapFactory.make();
/**
* mapping: Object (key) -> Integer (bytes)
*/
private final HashMap<Object, Integer> 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.intValue() + 1;
instanceCount.put(key, new Integer(newCount));
totalInstances++;
I = sizeCount.get(key);
int s = sizeOf(o);
int newSizeCount = (I == null) ? s : I.intValue() + s;
sizeCount.put(key, new Integer(newSizeCount));
totalSize += s;
}
@Override
public String toString() {
StringBuffer result = new StringBuffer();
result.append("Totals: " + totalInstances + " " + totalSize + "\n");
TreeSet<Object> sorted = new TreeSet<Object>(new SizeComparator());
sorted.addAll(instanceCount.keySet());
for (Iterator<Object> it = sorted.iterator(); it.hasNext();) {
Object key = it.next();
Integer I = instanceCount.get(key);
Integer bytes = sizeCount.get(key);
result.append(" ").append(I).append(" ").append(bytes).append(" ");
result.append(bytes.intValue() / I.intValue()).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<Object> {
/*
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Object o1, Object o2) {
Integer i1 = sizeCount.get(o1);
Integer i2 = sizeCount.get(o2);
return i2.intValue() - i1.intValue();
}
}
/**
* @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<Field, Demographics> roots = HashMapFactory.make();
/**
* @param root
* @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 (Iterator<Demographics> it = roots.values().iterator(); it.hasNext();) {
Demographics d = it.next();
totalSize += d.getTotalSize();
}
return totalSize;
}
@Override
public String toString() {
StringBuffer result = new StringBuffer();
result.append("Assuming " + BYTES_IN_HEADER + " header bytes per object\n");
int totalInstances = 0;
int totalSize = 0;
for (Iterator<Demographics> it = roots.values().iterator(); it.hasNext();) {
Demographics d = it.next();
totalInstances += d.getTotalInstances();
totalSize += d.getTotalSize();
}
result.append("Total instances: " + totalInstances + "\n");
result.append("Total size(bytes): " + totalSize + "\n");
TreeSet<Field> sortedDemo = new TreeSet<Field>(new SizeComparator());
sortedDemo.addAll(roots.keySet());
for (Iterator<Field> it = sortedDemo.iterator(); it.hasNext();) {
Object root = it.next();
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<Field> {
/*
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Field o1, Field o2) {
Demographics d1 = roots.get(o1);
Demographics d2 = roots.get(o2);
return d2.getTotalSize() - d1.getTotalSize();
}
}
}
}