/* * 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(); } }