/*
* 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.Reference;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
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 <a href="brian.stansberry@jboss.com">Brian Stansberry</a>
* @version $Revision: 88182 $
*/
public class LeakAnalyzer extends JVMTIInterface
{
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<Long, List<ReferenceDataPoint>> referencesMap, Object thatObject, int maxLevel, boolean useToString,
boolean condensed)
{
ReferenceReportNode root = new ReferenceReportNode(callToString(thatObject, useToString));
Set<ReferenceReportNode> prunableLeaves = new HashSet<ReferenceReportNode>();
CharArrayWriter charArray = new CharArrayWriter();
PrintWriter out = new PrintWriter(charArray);
try
{
exploreObject(root, thatObject, 0, maxLevel, useToString, false, referencesMap, new HashSet<String>(), prunableLeaves);
for (Iterator<ReferenceReportNode> 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();
}
public void notifyOnReferences(String temporaryFile, JVMTICallBack callback)
{
// We override the superclass version to pass 'true' for notifyOnClasses
notifyInventory(true,temporaryFile,null,callback);
}
/** Explore references recursively */
private void exploreObject(ReferenceReportNode node, Object source, int currentLevel, final int maxLevel,
boolean useToString, boolean weakAndSoft, Map<Long, List<ReferenceDataPoint>> mapDataPoints, Set<String> alreadyExplored, Set<ReferenceReportNode> prunableLeaves)
{
if (maxLevel >= 0 && currentLevel >= maxLevel)
{
String msg = node.getMessage() == null ? "" : node.getMessage() + " -- ";
node.setMessage(msg + "<i>MaxLevel</i>");
return;
}
String index = source.getClass().getName() + "@" + System.identityHashCode(source);
if (alreadyExplored.contains(index))
{
String message = node.getMessage() == null ? "" : node.getMessage() + " -- ";
message += " object " + 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));
List<ReferenceDataPoint> listPoints = mapDataPoints.get(sourceTag);
if (listPoints == null)
{
log.info("didn't find references");
return;
}
log.info("References found");
for (ReferenceDataPoint point : listPoints)
{
ReferenceReportNode child = new ReferenceReportNode();
Object nextReference = treatReference(child, point, useToString);
if (nextReference != null && !weakAndSoft)
{
if (nextReference instanceof Reference)
{
// 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
// We also don't stop at SoftReferences, since if they survive
// our attempts at flushing them out, we want to know about them
String msg = child.getMessage();
if (msg.indexOf("FieldReference private java.lang.Object java.lang.ref.Reference.referent=") >= 0
&& !(nextReference instanceof SoftReference))
{
if (nextReference instanceof Map.Entry)
{
// WeakHashMap$Entry is suspicious.
// Put in some more info about the entry
@SuppressWarnings("unchecked")
Map.Entry entry = (Entry) nextReference;
Object key = entry.getKey();
msg += " KEY=" + (key == null ? " null" : key.getClass().getName() + "@" + System.identityHashCode(key));
Object val= entry.getValue();
msg += " VALUE=" + (val == null ? " null" : val.getClass().getName() + "@" + System.identityHashCode(val));
child.setMessage(msg);
}
prunableLeaves.add(child);
nextReference = null;
}
else if (msg.indexOf("java.lang.ThreadLocal$ThreadLocalMap$Entry") >= 0)
{
// Get the key and follow that to see why it isn't released
nextReference = ((Reference<?>) nextReference).get();
}
// else just keep going
}
}
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("<br>");
out.print(writeLevel(level));
if (node.isCritical())
{
out.print("<b>");
if (node.isLeaf())
{
out.print("<font color=\"red\">");
}
out.print(node.getMessage());
if (node.isLeaf())
{
out.print("</font>");
}
out.println("</b>");
}
else
{
out.println(node.getMessage());
}
for (Iterator<ReferenceReportNode> 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
{
String base = obj.getClass().getName() + "@" + System.identityHashCode(obj);
if (callToString || obj instanceof Class)
{
base += "(" + obj.toString() + ")";
}
return base;
}
}
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.
{
@SuppressWarnings("unchecked")
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();
}
}