package com.carrotsearch.sizeof; /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.*; import java.util.*; import com.carrotsearch.sizeof.RamUsageEstimator; /** * Dumps retained and shallow memory taken by an object and its reference * hierarchy. This could be improved in a number of ways but works for debugging * and "live" object monitoring. For static heap analyses a real performance * monitor like YourKit is much better (and accurate?). * * @see #dump(PrintWriter, Object) * @see #dump(Object) */ public class ObjectTree { /** * Dump the object tree to a {@link PrintWriter}. */ public static void dump(PrintWriter pw, Object root) { Node nodeTree = Node.create(root); printTree(new StringBuilder(), new StringBuilder(), pw, nodeTree); } /** * Dump the object tree to a {@link String}. */ public static String dump(Object root) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); dump(pw, root); pw.flush(); return sw.toString(); } private static class Node { private String name; private List<Node> children; private long shallowSize; private long deepSize; public Node(String name, Object delegate) { this.name = name; if (delegate != null) { shallowSize = RamUsageEstimator.shallowSizeOf(delegate); deepSize = shallowSize; } } private void addChild(Node node) { if (children == null) { children = new ArrayList<Node>(); } children.add(node); deepSize += node.deepSize; } /** Factory create method. */ public static Node create(Object delegate) { return create("root", delegate, new IdentityHashMap<Object,Integer>()); } /** * Factory create method. */ public static Node create(String prefix, Object delegate, IdentityHashMap<Object,Integer> seen) { if (delegate == null) throw new IllegalArgumentException(); if (seen.containsKey(delegate)) { return new Node("[seen " + uniqueName(delegate, seen) + "]", null); } seen.put(delegate, seen.size()); Class<?> clazz = delegate.getClass(); if (clazz.isArray()) { Node parent = new Node(prefix + " => " + clazz.getSimpleName(), delegate); if (clazz.getComponentType().isPrimitive()) { return parent; } else { final int length = Array.getLength(delegate); for (int i = 0; i < length; i++) { Object value = Array.get(delegate, i); if (value != null) { parent.addChild(create("[" + i + "]", value, seen)); } } return parent; } } else { List<Field> declaredFields = new ArrayList<Field>(); for (Class<?> c = clazz; c != null; c = c.getSuperclass()) { Field[] fields = c.getDeclaredFields(); AccessibleObject.setAccessible(fields, true); declaredFields.addAll(Arrays.asList(fields)); } Collections.sort(declaredFields, new Comparator<Field>() { @Override public int compare(Field o1, Field o2) { return o1.getName().compareTo(o2.getName()); } }); Node parent = new Node(prefix + " => " + uniqueName(delegate, seen), delegate); for (Field f : declaredFields) { try { if (!Modifier.isStatic(f.getModifiers()) && !f.getType().isPrimitive()) { Object fValue = f.get(delegate); if (fValue != null) { parent.addChild(create( f.getType().getSimpleName() + " " + f.getName(), fValue, seen)); } else { parent.addChild(new Node(f.getType().getSimpleName() + " " + f.getName() + " => null", null)); } } } catch (Exception e) { throw new RuntimeException(e); } } return parent; } } private static String uniqueName(Object t, IdentityHashMap<Object,Integer> seen) { return "<" + t.getClass().getSimpleName() + "#" + seen.get(t) + ">"; } public String getName() { return name; } public boolean hasChildren() { return children != null && !children.isEmpty(); } public List<Node> getChildren() { return children; } } private static void printTree(StringBuilder prefix, StringBuilder line, PrintWriter pw, Node node) { line.append(node.getName()); pw.println(String.format(Locale.ENGLISH, "%,8d %,8d %s", node.deepSize, node.shallowSize, line.toString())); line.setLength(0); if (node.hasChildren()) { int pLen = prefix.length(); for (Iterator<Node> i = node.getChildren().iterator(); i.hasNext();) { Node next = i.next(); line.append(prefix.toString()); line.append("+- "); prefix.append(i.hasNext() ? "| " : " "); printTree(prefix, line, pw, next); prefix.setLength(pLen); } } } }