001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022:
023: package org.jboss.test.classloader.leak.clstore;
024:
025: import java.io.CharArrayWriter;
026: import java.io.PrintWriter;
027: import java.lang.ref.SoftReference;
028: import java.lang.ref.WeakReference;
029: import java.lang.reflect.Field;
030: import java.util.ArrayList;
031: import java.util.HashMap;
032: import java.util.HashSet;
033: import java.util.Iterator;
034: import java.util.Map;
035: import java.util.Set;
036: import java.util.WeakHashMap;
037:
038: import org.jboss.logging.Logger;
039: import org.jboss.profiler.jvmti.JVMTICallBack;
040: import org.jboss.profiler.jvmti.JVMTIInterface;
041: import org.jboss.profiler.jvmti.ReferenceDataPoint;
042:
043: /**
044: * A LeakAnalyzer.
045: *
046: * @author <a href="brian.stansberry@jboss.com">Brian Stansberry</a>
047: * @version $Revision: 1.1 $
048: */
049: public class LeakAnalyzer extends JVMTIInterface {
050: WeakHashMap whm;
051: private static final Logger log = Logger
052: .getLogger(LeakAnalyzer.class);
053:
054: /**
055: * Create a new LeakAnalyzer.
056: *
057: */
058: public LeakAnalyzer() {
059: super ();
060: }
061:
062: public boolean isActive() {
063: // System.loadLibrary fails if it is called twice, which it will
064: // be if this class is redeployed. So, the first time we get a positive
065: // result, store it in a system property, and thereafter return
066: // the system property
067: String existing = System
068: .getProperty("jboss.test.jbossAgent.avail");
069: if (existing != null)
070: return Boolean.parseBoolean(existing);
071:
072: boolean active = super .isActive();
073: System.setProperty("jboss.test.jbossAgent.avail", Boolean
074: .toString(active));
075: return active;
076: }
077:
078: /**
079: * Show the reference holders tree of an object. This returns a report you
080: * can visualize through MBean.
081: */
082: public String exploreObjectReferences(HashMap referencesMap,
083: Object thatObject, int maxLevel, boolean useToString,
084: boolean condensed) {
085: ReferenceReportNode root = new ReferenceReportNode(
086: callToString(thatObject, useToString));
087:
088: Set<ReferenceReportNode> prunableLeaves = new HashSet<ReferenceReportNode>();
089:
090: CharArrayWriter charArray = new CharArrayWriter();
091: PrintWriter out = new PrintWriter(charArray);
092:
093: try {
094: exploreObject(root, thatObject, 0, maxLevel, useToString,
095: false, referencesMap, new HashSet(), prunableLeaves);
096:
097: for (Iterator<ReferenceReportNode> it = prunableLeaves
098: .iterator(); it.hasNext();) {
099: ReferenceReportNode nonCrit = it.next();
100: nonCrit.markNonCritical();
101: if (condensed)
102: nonCrit.removeBranch();
103: }
104:
105: writeReport(root, 0, out);
106: } catch (Exception e) {
107: charArray = new CharArrayWriter();
108: out = new PrintWriter(charArray);
109: e.printStackTrace(out);
110: }
111:
112: return charArray.toString();
113: }
114:
115: /** Explore references recursively */
116: private void exploreObject(ReferenceReportNode node, Object source,
117: int currentLevel, final int maxLevel, boolean useToString,
118: boolean weakAndSoft, Map mapDataPoints,
119: Set alreadyExplored, Set prunableLeaves) {
120: if (maxLevel >= 0 && currentLevel >= maxLevel) {
121: node.setMessage("<i>MaxLevel</i>)");
122: return;
123: }
124:
125: Integer index = new Integer(System.identityHashCode(source));
126:
127: if (alreadyExplored.contains(index)) {
128: String message = null;
129: if (source instanceof Class) {
130: message = " object instanceOf "
131: + source
132: + "@"
133: + index
134: + " was already described before on this report";
135: } else {
136: message = " object instanceOf "
137: + source.getClass()
138: + "@"
139: + index
140: + " was already described before on this report";
141: }
142:
143: node.setMessage(message);
144: prunableLeaves.add(node);
145: return;
146: }
147:
148: alreadyExplored.add(index);
149:
150: log.info("resolving references of "
151: + callToString(source, useToString) + "...");
152: Long sourceTag = new Long(this .getTagOnObject(source));
153: ArrayList listPoints = (ArrayList) mapDataPoints.get(sourceTag);
154: if (listPoints == null) {
155: log.info("didn't find references");
156: return;
157: }
158:
159: log.info("References found");
160:
161: for (Iterator iter = listPoints.iterator(); iter.hasNext();) {
162: ReferenceDataPoint point = (ReferenceDataPoint) iter.next();
163:
164: ReferenceReportNode child = new ReferenceReportNode();
165:
166: Object nextReference = treatReference(child, point,
167: useToString);
168:
169: if (nextReference != null && !weakAndSoft) {
170: if (nextReference instanceof WeakReference
171: || nextReference instanceof SoftReference) {
172: // WeakHashMap$Entry and ThreadLocal$ThreadLocalMap$Entry are
173: // special cases, where the Entry key is a weak ref, but the
174: // value is strong. We don't want to ignore similar cases. So
175: // only mark as prunable if the ref is the standard
176: // java.lang.ref.Referent.referent -- all others are potential
177: // strong references
178: String msg = child.getMessage();
179: if (msg
180: .indexOf("java.lang.ref.Reference.referent=") >= 0) {
181: prunableLeaves.add(child);
182: }
183:
184: nextReference = null;
185: }
186: }
187:
188: if (nextReference != null) {
189: exploreObject(child, nextReference, currentLevel + 1,
190: maxLevel, useToString, weakAndSoft,
191: mapDataPoints, alreadyExplored, prunableLeaves);
192: }
193:
194: if (child.getMessage() != null
195: || child.getChildren().size() > 0)
196: node.addChild(child);
197:
198: }
199:
200: }
201:
202: private void writeReport(ReferenceReportNode node, int level,
203: PrintWriter out) {
204: out.print("<br>");
205: out.print(writeLevel(level));
206: if (node.isCritical()) {
207: out.print("<b>");
208: if (node.isLeaf()) {
209: out.print("<font color=\"red\">");
210: }
211: out.print(node.getMessage());
212: if (node.isLeaf()) {
213: out.print("</font>");
214: }
215: out.println("</b>");
216: } else {
217: out.println(node.getMessage());
218: }
219:
220: for (Iterator<ReferenceReportNode> it = node.getChildren()
221: .iterator(); it.hasNext();) {
222: writeReport(it.next(), level + 1, out);
223: }
224: }
225:
226: private String callToString(Object obj, boolean callToString) {
227:
228: try {
229: if (obj == null) {
230: return "null";
231: } else {
232: if (callToString) {
233: return obj.toString();
234: } else {
235: if (obj instanceof Class) {
236: return obj.toString();
237: } else {
238: return obj.getClass().getName() + "@"
239: + System.identityHashCode(obj);
240: }
241: }
242: }
243:
244: } catch (Throwable e) {
245: return obj.getClass().getName()
246: + " toString had an Exception ";
247: }
248: }
249:
250: private Object treatReference(ReferenceReportNode node,
251: ReferenceDataPoint point, boolean useToString) {
252: Object referenceHolder = null;
253: if (point.getReferenceHolder() == 0
254: || point.getReferenceHolder() == -1) {
255: referenceHolder = null;
256: } else {
257: referenceHolder = this .getObjectOnTag(point
258: .getReferenceHolder());
259: }
260:
261: Object nextReference = null;
262: CharArrayWriter charArray = new CharArrayWriter();
263: PrintWriter out = new PrintWriter(charArray);
264:
265: switch (point.getReferenceType()) {
266: case JVMTICallBack.JVMTI_REFERENCE_CLASS:
267: // Reference from an object to its class.
268: out.print("InstanceOfReference:");
269: out.println("ToString="
270: + callToString(referenceHolder, useToString));
271:
272: nextReference = referenceHolder;
273: break;
274: case JVMTICallBack.JVMTI_REFERENCE_FIELD:
275: // Reference from an objectb to the value of one of its
276: // instance fields. For references of this kind
277: // the referrer_index parameter to the
278: // jvmtiObjectReferenceCallback is the index of the the
279: // instance field. The index is based on the order of
280: // all the object's fields. This includes all fields
281: // of the directly declared static and instance fields
282: // in the class, and includes all fields (both public
283: // and private) fields declared in superclasses
284: // and superinterfaces. The index is thus calculated
285: // by summing the index of field in the directly
286: // declared class (see GetClassFields), with the
287: // total number of fields (both public and private)
288: // declared in all superclasses and superinterfaces.
289: // The index starts at zero.
290: {
291:
292: String fieldName = null;
293:
294: if (referenceHolder == null) {
295: fieldName = "Reference GONE";
296: } else {
297: Class clazz = referenceHolder.getClass();
298: Field field = this .getObjectField(clazz, (int) point
299: .getIndex());
300: if (field == null) {
301: fieldName = "UndefinedField@" + referenceHolder;
302: } else {
303: fieldName = field.toString();
304: }
305: }
306: out.print("FieldReference " + fieldName + "="
307: + callToString(referenceHolder, useToString));
308: nextReference = referenceHolder;
309: break;
310: }
311: case JVMTICallBack.JVMTI_REFERENCE_ARRAY_ELEMENT:
312: // Reference from an array to one of its elements. For
313: // references of this kind the referrer_index parameter to the
314: // jvmtiObjectReferenceCallback is the array index.
315:
316: if (referenceHolder == null) {
317: out.println("arrayRef Position " + point.getIndex()
318: + " is gone");
319: } else {
320: out.println("arrayRef "
321: + referenceHolder.getClass().getName() + "["
322: + point.getIndex() + "] id=@"
323: + System.identityHashCode(referenceHolder));
324: }
325: nextReference = referenceHolder;
326: break;
327: case JVMTICallBack.JVMTI_REFERENCE_CLASS_LOADER:
328: // Reference from a class to its class loader.
329: out.println("ClassLoaderReference @ "
330: + callToString(referenceHolder, useToString));
331: nextReference = referenceHolder;
332: break;
333: case JVMTICallBack.JVMTI_REFERENCE_SIGNERS:
334: // Reference from a class to its signers array.
335: out.println("ReferenceSigner@"
336: + callToString(referenceHolder, useToString));
337: nextReference = referenceHolder;
338: break;
339: case JVMTICallBack.JVMTI_REFERENCE_PROTECTION_DOMAIN:
340: // Reference from a class to its protection domain.
341: out.println("ProtectionDomain@"
342: + callToString(referenceHolder, useToString));
343: nextReference = referenceHolder;
344: break;
345: case JVMTICallBack.JVMTI_REFERENCE_INTERFACE:
346: // Reference from a class to one of its interfaces.
347: out.println("ReferenceInterface@"
348: + callToString(referenceHolder, useToString));
349: nextReference = referenceHolder;
350: break;
351: case JVMTICallBack.JVMTI_REFERENCE_STATIC_FIELD:// Reference from a
352: // class to the
353: // value of one of
354: // its static
355: // fields. For
356: // references of
357: // this kind the
358: // referrer_index
359: // parameter to the
360: // jvmtiObjectReferenceCallback
361: // is the index of
362: // the static field.
363: // The index is
364: // based on the
365: // order of the
366: // directly declared
367: // static and
368: // instance fields
369: // in the class (not
370: // inherited
371: // fields), starting
372: // at zero. See
373: // GetClassFields.
374: {
375: Class clazz = (Class) referenceHolder;
376: Field field = this .getObjectField(clazz, (int) point
377: .getIndex());
378: String fieldName = null;
379: if (field == null) {
380: fieldName = "UndefinedField@" + referenceHolder;
381: } else {
382: fieldName = field.toString();
383: }
384: out.println("StaticFieldReference " + fieldName);
385: nextReference = null;
386: break;
387: }
388: case JVMTICallBack.JVMTI_REFERENCE_CONSTANT_POOL:
389: // Reference from a class to a resolved entry in
390: // the constant pool. For references of this kind the
391: // referrer_index parameter to the jvmtiObjectReferenceCallback
392: // is the index into constant pool table of the class, starting
393: // at 1. See The Constant Pool in the Java Virtual Machine
394: // Specification.
395: out.println(" ReferenceInterface@"
396: + callToString(referenceHolder, useToString));
397: nextReference = referenceHolder;
398: break;
399: case JVMTICallBack.ROOT_REFERENCE:
400: out.println("Root");
401: nextReference = null;
402: break;
403: case JVMTICallBack.THREAD_REFERENCE:
404:
405: Class methodClass = this .getMethodClass(point.getMethod());
406: if (methodClass != null) {
407: String className = null;
408: if (methodClass != null) {
409: className = methodClass.getName();
410: }
411:
412: Thread.yield();
413:
414: // this is weird but without this sleep here, the JVM crashes.
415: /*
416: * try { Thread.sleep(10); } catch (InterruptedException e) {
417: * e.printStackTrace(); }
418: */
419:
420: String methodName = this .getMethodName(point
421: .getMethod());
422: out.println("Reference inside a method - " + className
423: + "::" + methodName);
424: }
425: nextReference = null;
426: break;
427: default:
428: log.warn("unexpected reference " + point);
429: }
430:
431: String msg = charArray.toString();
432: if (msg.trim().length() > 0)
433: node.setMessage(msg);
434:
435: return nextReference;
436: }
437:
438: private static String writeLevel(int level) {
439: StringBuffer levelSb = new StringBuffer();
440: for (int i = 0; i <= level; i++) {
441: levelSb.append("!--");
442: }
443: return levelSb.toString();
444: }
445: }
|