package com.joe.utilities.core.util;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import com.joe.utilities.core.data.Node;
import com.joe.utilities.core.data.Tree;
/**
* The Class MemoryCounter.
*
* @link http://www.javaspecialists.co.za/archive/Issue078.html
* Enhanced/modified by Dave Ousey
* This class can estimate how much memory an Object uses. It is fairly accurate
* for JDK 1.4.2. It is based on the newsletter #29.
*/
public final class MemoryCounter
{
/** The Constant sizes. */
private static final MemorySizes sizes = new MemorySizes();
/** The visited. */
private final Map visited = new IdentityHashMap();
/** The report stack level. */
private int reportStackLevel = 10;
/** The stack level. */
private int stackLevel = 0;
/** The report memory minimum. */
private long reportMemoryMinimum = 1024;
/** The skip known static classes. */
private boolean skipKnownStaticClasses = false;
/** The generate memory tree. */
private boolean generateMemoryTree = false;
/** The memory tree. */
private Tree<MemoryNode> memoryTree;
/** The known static classes to skip. */
private static Set<String> knownStaticClassesToSkip;
/** The known static classes to always skip. */
private static Set<String> knownStaticClassesToAlwaysSkip;
static
{
// Setup known, major static objects: Spring Framework
knownStaticClassesToSkip = new HashSet<String>(8);
// Spring/Hibernate
knownStaticClassesToSkip.add("org.springframework.beans.factory.support.DefaultListableBeanFactory");
knownStaticClassesToSkip.add("org.hibernate.impl.SessionFactoryImpl");
knownStaticClassesToSkip.add("org.springframework.orm.hibernate3.HibernateTemplate");
knownStaticClassesToSkip.add("org.springframework.orm.hibernate3.HibernateTransactionManager");
knownStaticClassesToSkip.add("org.springframework.aop.framework.JdkDynamicAopProxy");
// Setup known, major static objects: Spring Framework
knownStaticClassesToAlwaysSkip = new HashSet<String>(7);
//Application level medecision view handler.
knownStaticClassesToAlwaysSkip.add("com.med.jsf.viewHandlers.classViewHandler.ClassViewHandler");
// Used by Log4J
knownStaticClassesToAlwaysSkip.add("org.apache.log4j.spi.RootLogger");
// JBoss server core objects
knownStaticClassesToAlwaysSkip.add("org.jboss.mx");
knownStaticClassesToAlwaysSkip.add("org.jboss.mq");
knownStaticClassesToAlwaysSkip.add("org.jboss.web.tomcat.service.WebAppClassLoader");
// App server
knownStaticClassesToAlwaysSkip.add("org.apache.catalina.core");
// Any thread groups
knownStaticClassesToAlwaysSkip.add("java.lang.ThreadGroup");
}
/**
* Instantiates a new memory counter.
*
* @param skipKnownStaticClasses the skip known static classes
* @param reportStackLevel the report stack level
* @param reportMemoryMinimum the report memory minimum
* @param generateMemoryTree the generate memory tree
*/
public MemoryCounter(boolean skipKnownStaticClasses, int reportStackLevel, long reportMemoryMinimum, boolean generateMemoryTree)
{
super();
this.skipKnownStaticClasses = skipKnownStaticClasses;
this.reportStackLevel = reportStackLevel;
this.reportMemoryMinimum = reportMemoryMinimum;
this.generateMemoryTree = generateMemoryTree;
if (generateMemoryTree)
memoryTree = new Tree<MemoryNode>();
}
/**
* Estimate.
*
* @param obj the obj
*
* @return the long
*/
public synchronized long estimate(Object obj)
{
if (generateMemoryTree)
memoryTree.clear();
long result = _estimate(obj, null, null);
visited.clear();
return result;
}
/**
* Skip object.
*
* @param obj the obj
*
* @return true, if successful
*/
private boolean skipObject(Object obj)
{
if (obj instanceof String)
{
// this will not cause a memory leak since
// unused interned Strings will be thrown away
if (obj == ((String) obj).intern())
return true;
}
if (obj == null)
return true;
if (visited.containsKey(obj))
return true;
// Skip static classes. Some are always skipped, some are skipped if requested
String className = obj.getClass().getName();
for (String filteredClass : knownStaticClassesToAlwaysSkip)
{
if (className.startsWith(filteredClass))
return true;
}
if (skipKnownStaticClasses && knownStaticClassesToSkip.contains(className))
return true;
return false;
}
/**
* _estimate.
*
* @param obj the obj
* @param loggingPrefix the logging prefix
*
* @return the long
*/
private long _estimate(Object obj, Node<MemoryNode> parentNode, String fieldName)
{
// Skip known static objects or objects already encountered recursively
if (skipObject(obj))
return 0;
// Mark object as processed to avoid repeated estimated if discovered later in object graph
visited.put(obj, null);
long result = 0;
Class clazz = obj.getClass();
if (clazz.isArray())
return _estimateArray(obj, parentNode, fieldName);
// Create empty node, will populate once we have estimated memory
Node<MemoryNode> objectMemoryNode = null;
if (generateMemoryTree)
objectMemoryNode = new Node<MemoryNode>(null);
// Note that we're down a stack level
stackLevel++;
while (clazz != null)
{
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
if (!Modifier.isStatic(fields[i].getModifiers()))
{
if (fields[i].getType().isPrimitive())
{
result += sizes.getPrimitiveFieldSize(fields[i].getType());
}
else
{
result += sizes.getPointerSize();
fields[i].setAccessible(true);
try
{
Object child = fields[i].get(obj);
if (child != null)
{
result += _estimate(child, objectMemoryNode, fields[i].getName());
}
}
catch (IllegalAccessException ex)
{
assert false;
}
}
}
}
clazz = clazz.getSuperclass();
}
result += sizes.getClassSize();
long memory = roundUpToNearestEightBytes(result);
if (generateMemoryTree && stackLevel <= reportStackLevel)
{
// Call toString. Hibernate objects may explode here.
String objectString = "";
try
{
objectString = obj.toString();
}
catch (Throwable t)
{
objectString = t.getMessage();
}
// Always record root node. Record child nodes only if they exceed minimal interesting memory threshold
objectMemoryNode.setData(new MemoryNode(obj.getClass().getName(), memory/1024L, objectString, fieldName));
if (parentNode != null && memory > reportMemoryMinimum)
parentNode.addChildNode(objectMemoryNode);
else if (parentNode == null)
memoryTree.setRootElement(objectMemoryNode);
}
stackLevel--;
return memory;
}
/**
* Round up to nearest eight bytes.
*
* @param result the result
*
* @return the long
*/
private long roundUpToNearestEightBytes(long result)
{
if ((result % 8) != 0)
{
result += 8 - (result % 8);
}
return result;
}
/**
* _estimate array.
*
* @param obj the obj
*
* @return the long
*/
protected long _estimateArray(Object obj, Node<MemoryNode> parentNode, String fieldName)
{
// Create empty node, will populate once we have estimated memory
Node<MemoryNode> objectMemoryNode = null;
if (generateMemoryTree)
objectMemoryNode = new Node<MemoryNode>(null);
long memory = 16;
int length = Array.getLength(obj);
if (length != 0)
{
Class arrayElementClazz = obj.getClass().getComponentType();
if (arrayElementClazz.isPrimitive())
{
long value = length * sizes.getPrimitiveArrayElementSize(arrayElementClazz);
memory += value;
}
else
{
for (int i = 0; i < length; i++)
{
memory += sizes.getPointerSize() + _estimate(Array.get(obj, i), objectMemoryNode, fieldName);
}
}
}
// Record memory
if (generateMemoryTree && stackLevel <= reportStackLevel)
{
objectMemoryNode.setData(new MemoryNode(obj.getClass().getComponentType().getName() + "[]", memory/1024L, "Array", fieldName));
if (parentNode != null && memory > reportMemoryMinimum)
parentNode.addChildNode(objectMemoryNode);
else if (parentNode == null)
memoryTree.setRootElement(objectMemoryNode);
}
return memory;
}
/**
* Gets the memory tree.
*
* @return the memory tree
*/
public Tree<MemoryNode> getMemoryTree()
{
return memoryTree;
}
}