/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.test.classloader.leak.clstore;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.jboss.logging.Logger;
import org.jboss.profiler.jvmti.JVMTICallBack;
import org.jboss.profiler.jvmti.JVMTIInterface;
import org.jboss.profiler.jvmti.ReferenceDataPoint;
/**
* A LeakAnalyzer.
*
* @author Brian Stansberry
* @version $Revision: 1.1 $
*/
public class LeakAnalyzer extends JVMTIInterface
{
WeakHashMap whm;
private static final Logger log = Logger.getLogger(LeakAnalyzer.class);
/**
* Create a new LeakAnalyzer.
*
*/
public LeakAnalyzer()
{
super();
}
public boolean isActive()
{
// System.loadLibrary fails if it is called twice, which it will
// be if this class is redeployed. So, the first time we get a positive
// result, store it in a system property, and thereafter return
// the system property
String existing = System.getProperty("jboss.test.jbossAgent.avail");
if (existing != null)
return Boolean.parseBoolean(existing);
boolean active = super.isActive();
System.setProperty("jboss.test.jbossAgent.avail", Boolean.toString(active));
return active;
}
/**
* Show the reference holders tree of an object. This returns a report you
* can visualize through MBean.
*/
public String exploreObjectReferences(HashMap referencesMap, Object thatObject, int maxLevel, boolean useToString,
boolean condensed)
{
ReferenceReportNode root = new ReferenceReportNode(callToString(thatObject, useToString));
Set prunableLeaves = new HashSet();
CharArrayWriter charArray = new CharArrayWriter();
PrintWriter out = new PrintWriter(charArray);
try
{
exploreObject(root, thatObject, 0, maxLevel, useToString, false, referencesMap, new HashSet(), prunableLeaves);
for (Iterator it = prunableLeaves.iterator(); it.hasNext();)
{
ReferenceReportNode nonCrit = it.next();
nonCrit.markNonCritical();
if (condensed)
nonCrit.removeBranch();
}
writeReport(root, 0, out);
}
catch (Exception e)
{
charArray = new CharArrayWriter();
out = new PrintWriter(charArray);
e.printStackTrace(out);
}
return charArray.toString();
}
/** Explore references recursively */
private void exploreObject(ReferenceReportNode node, Object source, int currentLevel, final int maxLevel,
boolean useToString, boolean weakAndSoft, Map mapDataPoints, Set alreadyExplored, Set prunableLeaves)
{
if (maxLevel >= 0 && currentLevel >= maxLevel)
{
node.setMessage("MaxLevel)");
return;
}
Integer index = new Integer(System.identityHashCode(source));
if (alreadyExplored.contains(index))
{
String message = null;
if (source instanceof Class)
{
message = " object instanceOf " + source + "@" + index
+ " was already described before on this report";
}
else
{
message = " object instanceOf " + source.getClass() + "@" + index
+ " was already described before on this report";
}
node.setMessage(message);
prunableLeaves.add(node);
return;
}
alreadyExplored.add(index);
log.info("resolving references of " + callToString(source, useToString) + "...");
Long sourceTag = new Long(this.getTagOnObject(source));
ArrayList listPoints = (ArrayList) mapDataPoints.get(sourceTag);
if (listPoints == null)
{
log.info("didn't find references");
return;
}
log.info("References found");
for (Iterator iter = listPoints.iterator(); iter.hasNext();)
{
ReferenceDataPoint point = (ReferenceDataPoint) iter.next();
ReferenceReportNode child = new ReferenceReportNode();
Object nextReference = treatReference(child, point, useToString);
if (nextReference != null && !weakAndSoft)
{
if (nextReference instanceof WeakReference || nextReference instanceof SoftReference)
{
// WeakHashMap$Entry and ThreadLocal$ThreadLocalMap$Entry are
// special cases, where the Entry key is a weak ref, but the
// value is strong. We don't want to ignore similar cases. So
// only mark as prunable if the ref is the standard
// java.lang.ref.Referent.referent -- all others are potential
// strong references
String msg = child.getMessage();
if (msg.indexOf("java.lang.ref.Reference.referent=") >= 0)
{
prunableLeaves.add(child);
}
nextReference = null;
}
}
if (nextReference != null)
{
exploreObject(child, nextReference, currentLevel + 1, maxLevel, useToString, weakAndSoft, mapDataPoints,
alreadyExplored, prunableLeaves);
}
if (child.getMessage() != null || child.getChildren().size() > 0)
node.addChild(child);
}
}
private void writeReport(ReferenceReportNode node, int level, PrintWriter out)
{
out.print("
");
out.print(writeLevel(level));
if (node.isCritical())
{
out.print("");
if (node.isLeaf())
{
out.print("");
}
out.print(node.getMessage());
if (node.isLeaf())
{
out.print("");
}
out.println("");
}
else
{
out.println(node.getMessage());
}
for (Iterator it = node.getChildren().iterator(); it.hasNext();)
{
writeReport(it.next(), level + 1, out);
}
}
private String callToString(Object obj, boolean callToString)
{
try
{
if (obj == null)
{
return "null";
}
else
{
if (callToString)
{
return obj.toString();
}
else
{
if (obj instanceof Class)
{
return obj.toString();
}
else
{
return obj.getClass().getName() + "@" + System.identityHashCode(obj);
}
}
}
}
catch (Throwable e)
{
return obj.getClass().getName() + " toString had an Exception ";
}
}
private Object treatReference(ReferenceReportNode node, ReferenceDataPoint point, boolean useToString)
{
Object referenceHolder = null;
if (point.getReferenceHolder() == 0 || point.getReferenceHolder() == -1)
{
referenceHolder = null;
}
else
{
referenceHolder = this.getObjectOnTag(point.getReferenceHolder());
}
Object nextReference = null;
CharArrayWriter charArray = new CharArrayWriter();
PrintWriter out = new PrintWriter(charArray);
switch (point.getReferenceType())
{
case JVMTICallBack.JVMTI_REFERENCE_CLASS :
// Reference from an object to its class.
out.print("InstanceOfReference:");
out.println("ToString=" + callToString(referenceHolder, useToString));
nextReference = referenceHolder;
break;
case JVMTICallBack.JVMTI_REFERENCE_FIELD :
// Reference from an objectb to the value of one of its
// instance fields. For references of this kind
// the referrer_index parameter to the
// jvmtiObjectReferenceCallback is the index of the the
// instance field. The index is based on the order of
// all the object's fields. This includes all fields
// of the directly declared static and instance fields
// in the class, and includes all fields (both public
// and private) fields declared in superclasses
// and superinterfaces. The index is thus calculated
// by summing the index of field in the directly
// declared class (see GetClassFields), with the
// total number of fields (both public and private)
// declared in all superclasses and superinterfaces.
// The index starts at zero.
{
String fieldName = null;
if (referenceHolder == null)
{
fieldName = "Reference GONE";
}
else
{
Class clazz = referenceHolder.getClass();
Field field = this.getObjectField(clazz, (int) point.getIndex());
if (field == null)
{
fieldName = "UndefinedField@" + referenceHolder;
}
else
{
fieldName = field.toString();
}
}
out.print("FieldReference " + fieldName + "=" + callToString(referenceHolder, useToString));
nextReference = referenceHolder;
break;
}
case JVMTICallBack.JVMTI_REFERENCE_ARRAY_ELEMENT :
// Reference from an array to one of its elements. For
// references of this kind the referrer_index parameter to the
// jvmtiObjectReferenceCallback is the array index.
if (referenceHolder == null)
{
out.println("arrayRef Position " + point.getIndex() + " is gone");
}
else
{
out.println("arrayRef " + referenceHolder.getClass().getName() + "[" + point.getIndex()
+ "] id=@" + System.identityHashCode(referenceHolder));
}
nextReference = referenceHolder;
break;
case JVMTICallBack.JVMTI_REFERENCE_CLASS_LOADER :
// Reference from a class to its class loader.
out.println("ClassLoaderReference @ " + callToString(referenceHolder, useToString));
nextReference = referenceHolder;
break;
case JVMTICallBack.JVMTI_REFERENCE_SIGNERS :
// Reference from a class to its signers array.
out.println("ReferenceSigner@" + callToString(referenceHolder, useToString));
nextReference = referenceHolder;
break;
case JVMTICallBack.JVMTI_REFERENCE_PROTECTION_DOMAIN :
// Reference from a class to its protection domain.
out.println("ProtectionDomain@" + callToString(referenceHolder, useToString));
nextReference = referenceHolder;
break;
case JVMTICallBack.JVMTI_REFERENCE_INTERFACE :
// Reference from a class to one of its interfaces.
out.println("ReferenceInterface@" + callToString(referenceHolder, useToString));
nextReference = referenceHolder;
break;
case JVMTICallBack.JVMTI_REFERENCE_STATIC_FIELD :// Reference from a
// class to the
// value of one of
// its static
// fields. For
// references of
// this kind the
// referrer_index
// parameter to the
// jvmtiObjectReferenceCallback
// is the index of
// the static field.
// The index is
// based on the
// order of the
// directly declared
// static and
// instance fields
// in the class (not
// inherited
// fields), starting
// at zero. See
// GetClassFields.
{
Class clazz = (Class) referenceHolder;
Field field = this.getObjectField(clazz, (int) point.getIndex());
String fieldName = null;
if (field == null)
{
fieldName = "UndefinedField@" + referenceHolder;
}
else
{
fieldName = field.toString();
}
out.println("StaticFieldReference " + fieldName);
nextReference = null;
break;
}
case JVMTICallBack.JVMTI_REFERENCE_CONSTANT_POOL :
// Reference from a class to a resolved entry in
// the constant pool. For references of this kind the
// referrer_index parameter to the jvmtiObjectReferenceCallback
// is the index into constant pool table of the class, starting
// at 1. See The Constant Pool in the Java Virtual Machine
// Specification.
out.println(" ReferenceInterface@" + callToString(referenceHolder, useToString));
nextReference = referenceHolder;
break;
case JVMTICallBack.ROOT_REFERENCE :
out.println("Root");
nextReference = null;
break;
case JVMTICallBack.THREAD_REFERENCE :
Class methodClass = this.getMethodClass(point.getMethod());
if (methodClass != null)
{
String className = null;
if (methodClass != null)
{
className = methodClass.getName();
}
Thread.yield();
// this is weird but without this sleep here, the JVM crashes.
/*
* try { Thread.sleep(10); } catch (InterruptedException e) {
* e.printStackTrace(); }
*/
String methodName = this.getMethodName(point.getMethod());
out.println("Reference inside a method - " + className + "::" + methodName);
}
nextReference = null;
break;
default :
log.warn("unexpected reference " + point);
}
String msg = charArray.toString();
if (msg.trim().length() > 0)
node.setMessage(msg);
return nextReference;
}
private static String writeLevel(int level)
{
StringBuffer levelSb = new StringBuffer();
for (int i = 0; i <= level; i++)
{
levelSb.append("!--");
}
return levelSb.toString();
}
}