Skip to content

Commit

Permalink
clean up api for alternate tracker implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
jbellis committed Feb 20, 2011
1 parent 876aee7 commit b9b29ec
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 20 deletions.
20 changes: 19 additions & 1 deletion README.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Overview
========

Jamm provides MemoryMeter, a java agent to measure actual object
memory use including JVM overhead.

Expand All @@ -10,7 +13,9 @@ You can then use MemoryMeter in your code like this:
meter.measureDeep(object);
meter.countChildren(object);

The fine print:

The fine print
==============

MemoryMeter is as accurate as
java.lang.instrument.Instrumentation.getObjectSize, which only claims
Expand All @@ -21,3 +26,16 @@ MemoryMeter uses reflection to crawl the object graph for measureDeep.
Reflection is slow: measuring a one-million object Cassandra Memtable
(that is, 1 million children from MemoryMeter.countChildren) took
about 5 seconds wall clock time.

By default, MemoryMeter keeps track of descendents visited by
measureDeep with an IdentityHashMap. This prevents both overcounting
and infinite loops due to cycles in the object graph. Of course, this
tracking imposes a memory cost of its own. You can override this by
passing a different tracker provider to the MemoryMeter constructor.
Jamm provides AlwaysEmptySet, which allows add() calls but never
remembers anything, as one alternative. (Obviously this will break
painfully if there actually are cycles present!) A more useful
alternative, but out of Jamm's scope, would be a tracker using a Bloom
filter to implement a probabilistic set interface -- this would have
the potential of _undercounting_ due to false positives, but it would
guarantee not to loop over cycles.
9 changes: 9 additions & 0 deletions src/org/github/jamm/AlwaysEmptySet.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Callable;

public class AlwaysEmptySet<T> implements Set<T> {
public static final Set EMPTY_SET = new AlwaysEmptySet();
Expand All @@ -15,6 +16,14 @@ public static <T> Set<T> create() {
return (Set<T>) EMPTY_SET;
}

public static <T> Callable<Set<T>> provider() {
return new Callable<Set<T>>() {
public Set<T> call() throws Exception {
return create();
}
};
}

public int size() {
return 0;
}
Expand Down
55 changes: 36 additions & 19 deletions src/org/github/jamm/MemoryMeter.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.IdentityHashMap;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.Callable;

public class MemoryMeter {
private static Instrumentation inst;
Expand All @@ -15,6 +16,26 @@ public static void premain(String options, Instrumentation inst) {
MemoryMeter.inst = inst;
}

private final Callable<Set<Object>> trackerProvider;

public MemoryMeter() {
this(new Callable<Set<Object>>() {
public Set<Object> call() throws Exception {
// using a normal HashSet to track seen objects screws things up in two ways:
// - it can undercount objects that are "equal"
// - calling equals() can actually change object state (e.g. creating entrySet in HashMap)
return Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>());
}
});
}

/**
* @param trackerProvider returns a Set with which to track seen objects and avoid cycles
*/
public MemoryMeter(Callable<Set<Object>> trackerProvider) {
this.trackerProvider = trackerProvider;
}

/**
* @return the shallow memory usage of @param object
* @throws NullPointerException if object is null
Expand All @@ -31,22 +52,18 @@ public long measure(Object object) {
* @throws NullPointerException if object is null
*/
public long measureDeep(Object object) {
// using a normal HashSet to track seen objects screws things up in two ways:
// - it can undercount objects that are "equal"
// - calling equals() can actually change object state (e.g. creating entrySet in HashMap)
return measureDeep(object, Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>()));
}

/**
* @param tracker a Set with which to track objects already counted
* @return the memory usage of @param object including referenced objects
* @throws NullPointerException if object is null
*/
public long measureDeep(Object object, Set<Object> tracker) {
if (object == null) {
throw new NullPointerException(); // match getObjectSize behavior
}

Set<Object> tracker;
try {
tracker = trackerProvider.call();
}
catch (Exception e) {
throw new RuntimeException(e);
}

tracker.add(object);

// track stack manually so we can handle deeper heirarchies than recursion
Expand Down Expand Up @@ -79,8 +96,8 @@ public long countChildren(Object object) {
throw new NullPointerException();
}

Set<Object> seen = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>());
seen.add(object);
Set<Object> tracker = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>());
tracker.add(object);
Stack<Object> stack = new Stack<Object>();
stack.push(object);

Expand All @@ -91,10 +108,10 @@ public long countChildren(Object object) {
total++;

if (current instanceof Object[]) {
addArrayChildren((Object[]) current, stack, seen);
addArrayChildren((Object[]) current, stack, tracker);
}
else {
addFieldChildren(current, stack, seen);
addFieldChildren(current, stack, tracker);
}
}

Expand Down Expand Up @@ -131,11 +148,11 @@ private void addFieldChildren(Object current, Stack<Object> stack, Set<Object> s
}
}

private void addArrayChildren(Object[] current, Stack<Object> stack, Set<Object> seen) {
private void addArrayChildren(Object[] current, Stack<Object> stack, Set<Object> tracker) {
for (Object child : current) {
if (child != null && !seen.contains(child)) {
if (child != null && !tracker.contains(child)) {
stack.push(child);
seen.add(child);
tracker.add(child);
}
}
}
Expand Down

0 comments on commit b9b29ec

Please sign in to comment.