001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: * The Original Software is NetBeans. The Initial Developer of the Original
026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
027: * Microsystems, Inc. All Rights Reserved.
028: *
029: * If you wish your version of this file to be governed by only the CDDL
030: * or only the GPL Version 2, indicate your decision by adding
031: * "[Contributor] elects to include this software in this distribution
032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
033: * single choice of license, a recipient has the option to distribute
034: * your version of this file under either the CDDL, the GPL Version 2 or
035: * to extend the choice of license to its licensees as provided above.
036: * However, if you add GPL Version 2 code and therefore, elected the GPL
037: * Version 2 license, then the option applies only if the new code is
038: * made subject to such option by the copyright holder.
039: */
040:
041: package org.netbeans.lib.profiler.server;
042:
043: import org.netbeans.lib.profiler.global.CommonConstants;
044: import org.netbeans.lib.profiler.server.system.Classes;
045: import java.lang.ref.PhantomReference;
046: import java.lang.ref.ReferenceQueue;
047: import java.lang.ref.WeakReference;
048: import java.lang.reflect.Method;
049: import java.util.Vector;
050: import java.util.WeakHashMap;
051:
052: /**
053: * Functionality that ultimately allows us to obtain a class given its name and class loader.
054: * One reason for this class to exist, is to enable access to non-public methods of class java.lang.ClassLoader,
055: * that allow one to obtain a class loaded by the given loader, or make sure that this class hasn't been loaded by
056: * the given loader. Also this class provides accounting for parent loader for each registered loader, which is
057: * needed at the client side to correctly perform class instrumentation. Finally, we keep track of class (actually
058: * class loader) unloading events, which is necessary e.g. during memory profiling, to prevent
059: * getMethodNamesForJMethodIds from crashing or returning "unknown method" results.
060: *
061: * Manages:
062: * - class unloading
063: * - knowing what is loader for each class in CPU profiling
064: *
065: * @author Misha Dmitriev
066: * @author Ian Formanek
067: */
068: class ClassLoaderManager implements CommonConstants {
069: //~ Static fields/initializers -----------------------------------------------------------------------------------------------
070:
071: // TODO [release]: change value to TRUE to remove the print code below entirely by compiler
072: private static final boolean DEBUG = System
073: .getProperty("org.netbeans.lib.profiler.server.ClassLoaderManager") != null; // NOI18N
074: private static ProfilerServer profilerServer;
075: private static WeakHashMap /*<ClassLoader, ClassLoaderManager>*/manMap;
076: private static Vector /*<ClassLoaderManager>*/manVec;
077: private static ReferenceQueue rq;
078: private static boolean notifyToolAboutUnloadedClasses;
079: private static Method findLoadedClassMethod;
080: private static Method findBootstrapClassMethod;
081:
082: /*
083: public static void reset() {
084: manMap = null;
085: manVec = null;
086: rq = null;
087: }
088: */
089: private static boolean notifyThreadIsRunning;
090:
091: //~ Instance fields ----------------------------------------------------------------------------------------------------------
092:
093: private PhantomReference targetLdrPhantomRef; // This is used to keep track of the moment when the loader is about
094: // to be GCed
095: private WeakReference targetLdrWeakRef; // We use WeakReferences to prevent memory leaks due to direct
096: // references to unused loaders
097: private int indexIntoManVec; // Index into the vector of ClassLoaderManagers "manVec" below.
098: private int parentLoaderId; // Index of the targetLoader's parent loader into "managers" below
099:
100: //~ Constructors -------------------------------------------------------------------------------------------------------------
101:
102: // --- Instance methods ------------------------------------------------------------------------------------------------
103: private ClassLoaderManager(ClassLoader targetLoader,
104: int indexIntoManVec) {
105: this .targetLdrWeakRef = new WeakReference(targetLoader);
106: this .targetLdrPhantomRef = new PhantomReference(targetLoader,
107: rq);
108: this .indexIntoManVec = indexIntoManVec;
109: }
110:
111: //~ Methods ------------------------------------------------------------------------------------------------------------------
112:
113: /** Debugging support */
114: public String toString() {
115: return ("CLManager: indexIntoManVec = " + indexIntoManVec
116: + ", parentLoaderId = " + parentLoaderId); // NOI18N
117: }
118:
119: static int getDefiningLoaderForClass(String className,
120: int initiatingLoaderId) {
121: if (initiatingLoaderId >= manVec.size()) {
122: return -1;
123: }
124:
125: ClassLoaderManager man = (ClassLoaderManager) manVec
126: .get(initiatingLoaderId);
127:
128: if ((man == null) || (man.targetLdrWeakRef.get() == null)) {
129: return -1;
130: }
131:
132: Class clazz = man.getLoadedClassInThisLoaderOnly(className);
133:
134: if (clazz != null) {
135: return registerLoader(clazz);
136: } else {
137: return -1;
138: }
139: }
140:
141: static Class getLoadedClass(String name, int loaderIdx) {
142: if (loaderIdx == -1) {
143: loaderIdx = 0;
144: }
145:
146: Class res = ((ClassLoaderManager) manVec.get(loaderIdx))
147: .getLoadedClass(name);
148:
149: if (res != null) {
150: return res;
151: } else {
152: System.err
153: .println(ENGINE_WARNING
154: + "class "
155: + name
156: + " that should be instrumented is not loaded by target VM"); // NOI18N
157:
158: ClassLoader errLoader = (ClassLoader) (((ClassLoaderManager) manVec
159: .get(loaderIdx)).targetLdrWeakRef.get());
160: System.err.print("*** Requested classloader: " + errLoader); // NOI18N
161:
162: if (errLoader != null) {
163: System.err.println(", its class = "
164: + errLoader.getClass()
165: + ", index = " // NOI18N
166: + loaderIdx + ", hashcode = "
167: + errLoader.hashCode()); // NOI18N
168: } else {
169: System.err.println(", its index = " + loaderIdx); // NOI18N
170: }
171:
172: return null;
173: }
174: }
175:
176: static void setNotifyToolAboutUnloadedClasses(boolean v) {
177: notifyToolAboutUnloadedClasses = v;
178: }
179:
180: /**
181: * Creates a table that maps loader id to its parent class loader.
182: *
183: * @return An array that maps class loader id (idx) to its parent class loader it ([idx])
184: */
185: static int[] getParentLoaderIdTable() {
186: int size = manVec.size();
187: int[] ret = new int[size];
188:
189: for (int i = 0; i < size; i++) {
190: ret[i] = ((ClassLoaderManager) manVec.get(i)).parentLoaderId;
191: }
192:
193: return ret;
194: }
195:
196: /* Not used
197: public static int getParentLoaderId(int thisLoaderId) {
198: if (thisLoaderId == -1 || thisLoaderId == 0) return 0;
199: else return ((ClassLoaderManager) manVec.get(thisLoaderId)).parentLoaderId;
200: }
201: */
202:
203: /**
204: * This whole method exists, in addition to simple getParentLoaderId() above, to make possible passing to the tool
205: * information about "chains of loaders" that may occasionally be discovered when a class is loaded. I.e. it may
206: * happen that a new loader A is created, then A creates a child loader B, and finally a class is loaded by B. At
207: * this time we happen to register *both* loaders B and A in registerLoader() above, and also parent loader A gets
208: * a loaderId with a *greater* value than that for B.
209: *
210: * So for the tool to adequately reflect the class loader structure of the application, we need to pass info about
211: * more than 2 loaders with a single class. Fortunately, when there are x>2 loaders involved, the first x-1 get
212: * sequentially growing IDs. So what is returned here in the int[3] array, is the ID for the first and the last
213: * loader in such a chain, plus the chain length (which is 0 in the case of a simple child-parent pair).
214: *
215: * @param thisLoaderId Class loader Id whose parent we are looking for
216: * @return a 3 item array [0]=first loader in chain, [1]=last loader in chain, [2]=chain length, can be 0 in
217: * simple case
218: */
219: static int[] getThisAndParentLoaderData(int this LoaderId) {
220: if ((this LoaderId == -1) || (this LoaderId == 0)) {
221: return new int[] { 0, 0, 0 };
222: } else {
223: int parentLoaderId = ((ClassLoaderManager) manVec
224: .get(this LoaderId)).parentLoaderId;
225:
226: if (parentLoaderId == -1) {
227: parentLoaderId = 0;
228: }
229:
230: if (parentLoaderId <= this LoaderId) {
231: return new int[] { this LoaderId, parentLoaderId, 0 };
232: } else {
233: int ofs = 0;
234: int curLoaderId = this LoaderId;
235:
236: while (parentLoaderId > curLoaderId) {
237: ofs++;
238: curLoaderId = parentLoaderId;
239: parentLoaderId = ((ClassLoaderManager) manVec
240: .get(curLoaderId)).parentLoaderId;
241: }
242:
243: if (parentLoaderId < 0) {
244: parentLoaderId = 0;
245: }
246:
247: return new int[] { this LoaderId, parentLoaderId, ofs };
248: }
249: }
250: }
251:
252: static void addLoader(ClassLoader loader) {
253: if (DEBUG) {
254: System.out.println("Add loader for: " + loader); // NOI18N
255: }
256:
257: ClassLoaderManager ldrMan = (ClassLoaderManager) manMap
258: .get(loader);
259:
260: if (ldrMan != null) {
261: // a manager for this class loader already exists
262: return;
263: }
264:
265: // create new ClassLoaderManager, with the id being next available int
266: int newId = manVec.size();
267: ldrMan = new ClassLoaderManager(loader, newId);
268:
269: if (DEBUG) {
270: System.out
271: .println("ClassLoaderManager.DEBUG: Add loader for: "
272: + loader + ", new Id: " + newId); // NOI18N
273: }
274:
275: manMap.put(loader, ldrMan);
276: manVec.add(ldrMan); // will be placed at the correct index: newId
277: }
278:
279: /**
280: * This method SHOULD be called frequently enough to allow unloaded classes go away.
281: * On the other hand, since currently it's called from monitoring code, which itself has to execute at regular
282: * enough intervals, it has to return quickly - that's why we are using a separate thread in it, that does the
283: * potentially long-executing work.
284: * In addition to just clearing a PhantomReference, the code could have removed the relevant ClassLoaderManager
285: * from Vector/Hashtable that contain these managers. We can implement that later.
286: */
287: static void checkForUnloadedClasses() {
288: if (rq == null) {
289: return;
290: }
291:
292: PhantomReference clRef = null;
293:
294: if ((clRef = (PhantomReference) rq.poll()) != null) {
295: if (notifyToolAboutUnloadedClasses) {
296: class NotifyThread extends Thread {
297: private PhantomReference clRef;
298:
299: NotifyThread(PhantomReference clRef) {
300: this .clRef = clRef;
301: ThreadInfo.addProfilerServerThread(this );
302: }
303:
304: public void run() {
305: notifyThreadIsRunning = true;
306:
307: do {
308: // Note that there is a small chance that this call will not really dump all information, if some
309: // thread isfor some reason preempted while it's in traceObjAlloc() and not let finish it. This may
310: // result in some jmethodIDs not sent to client in time and thus not resolved in the call below. But
311: // the probability of such an event seems very low, and we also have exception handlers in native
312: // code now, to protect us from such mishaps.
313:
314: // [ian]: this seems like a reason for EXCEPTION_ACCESS_VIOLATION when doing getMethodNamesForJMethodIds
315: ProfilerInterface
316: .dumpExistingResults(false);
317:
318: // synchronized (ProfilerServer.execInSeparateThreadLock) {
319: ProfilerInterface.serialClientOperationsLock
320: .beginTrans(true);
321:
322: try {
323: // This will send the command to the client and will wait for a response from it, which will
324: // come only after the client in turn asks the server for method names for all jmethodIDs it
325: // currently has
326: profilerServer
327: .sendClassLoaderUnloadingCommand();
328: } finally {
329: ProfilerInterface.serialClientOperationsLock
330: .endTrans();
331: }
332: // }
333:
334: clRef.clear();
335: } while ((clRef = (PhantomReference) rq.poll()) != null);
336:
337: // Notify the native code that may cache class file bytes in a separate data structure,
338: // that some classes have gone
339: Classes.notifyAboutClassLoaderUnloading();
340: ThreadInfo.removeProfilerServerThread(this );
341: notifyThreadIsRunning = false;
342: }
343: }
344:
345: if (!notifyThreadIsRunning) {
346: new NotifyThread(clRef).start(); // Otherwise will do the same thing next time we get here
347: }
348: } else {
349: do {
350: clRef.clear();
351: } while ((clRef = (PhantomReference) rq.poll()) != null);
352:
353: Classes.notifyAboutClassLoaderUnloading();
354: }
355: }
356: }
357:
358: static void initialize(ProfilerServer inProfilerServer) {
359: try {
360: Class classLoaderClass = Class
361: .forName("java.lang.ClassLoader"); // NOI18N
362: Class[] stringArg = new Class[] { Class
363: .forName("java.lang.String") }; // NOI18N
364: findLoadedClassMethod = classLoaderClass.getDeclaredMethod(
365: "findLoadedClass", stringArg); // NOI18N
366: findLoadedClassMethod.setAccessible(true); // REQUIRED to suppress
367: findBootstrapClassMethod = classLoaderClass
368: .getDeclaredMethod("findBootstrapClass", stringArg); // NOI18N
369: findBootstrapClassMethod.setAccessible(true); // access checks
370: } catch (Exception ex) {
371: System.err
372: .println("Profiler Agent Error: Internal error initializing ClassLoaderManager"); // NOI18N
373: ex.printStackTrace(System.err);
374: }
375:
376: // This is done to just initialize some reflection classes, which may otherwise be initialized only when
377: // this class is used for the first time, and thus may cause endless class load recursion
378: ClassLoaderManager clm = new ClassLoaderManager(ClassLoader
379: .getSystemClassLoader(), 0);
380: clm.getLoadedClass("java.lang.String"); // NOI18N
381:
382: profilerServer = inProfilerServer;
383:
384: manMap = new WeakHashMap();
385: manVec = new Vector();
386: rq = new ReferenceQueue();
387: }
388:
389: static int registerLoader(Class clazz) {
390: ClassLoader loader = clazz.getClassLoader();
391:
392: if (loader == null) {
393: return -1;
394: }
395:
396: int ret = registerLoader(loader);
397:
398: if (DEBUG) {
399: System.out
400: .println("ClassLoaderManager.DEBUG: Register loader for: "
401: + clazz.getName()
402: + ", ldr: "
403: + loader
404: + ", id: " + ret); // NOI18N
405: }
406:
407: return ret;
408: }
409:
410: private static synchronized int registerLoader(ClassLoader loader) {
411: ClassLoaderManager ldrMan = (ClassLoaderManager) manMap
412: .get(loader);
413:
414: if (ldrMan != null) {
415: if (ldrMan.targetLdrWeakRef.get() == loader) {
416: return ldrMan.indexIntoManVec;
417: } else {
418: // This probably was a really bad (impossible?) clash - check if this loader is actually
419: // registered somewhere
420: int size = manVec.size();
421:
422: for (int i = 0; i < size; i++) {
423: ldrMan = (ClassLoaderManager) manVec.get(i);
424:
425: if (ldrMan.targetLdrWeakRef.get() == loader) {
426: return ldrMan.indexIntoManVec;
427: }
428: }
429: }
430: }
431:
432: int ldrIdx = manVec.size();
433: ldrMan = new ClassLoaderManager(loader, ldrIdx);
434: manMap.put(loader, ldrMan);
435: manVec.add(ldrMan);
436:
437: ClassLoader parentLoader = loader.getParent();
438: ldrMan.parentLoaderId = (parentLoader != null) ? registerLoader(parentLoader)
439: : (-1);
440:
441: return ldrIdx;
442: }
443:
444: /** Get a class with the given name, if it is loaded by the given class loader or one of its parent loaders */
445: private Class getLoadedClass(String className) {
446: try {
447: Object[] args = new Object[] { className };
448: ClassLoader loader = (ClassLoader) targetLdrWeakRef.get();
449:
450: while (loader != null) {
451: Class res = (Class) findLoadedClassMethod.invoke(
452: loader, args);
453:
454: // Class res = targetLoader.findLoadedClass(className);
455: if (res != null) {
456: return res;
457: } else {
458: loader = loader.getParent();
459: }
460: }
461:
462: try {
463: return (Class) findBootstrapClassMethod.invoke(
464: ClassLoader.getSystemClassLoader(), args);
465:
466: // targetLoader.findBootstrapClass(className);
467: } catch (Exception ex) { // ClassNotFoundException may be thrown
468:
469: return null;
470: }
471: } catch (Exception ex) {
472: System.err
473: .println("Profiler Agent Error: internal error in ClassLoaderManager 1"); // NOI18N
474: ex.printStackTrace(System.err);
475: }
476:
477: return null;
478: }
479:
480: private Class getLoadedClassInThisLoaderOnly(String className) {
481: try {
482: Object[] args = new Object[] { className };
483: ClassLoader loader = (ClassLoader) targetLdrWeakRef.get();
484:
485: if (loader != null) {
486: return (Class) findLoadedClassMethod.invoke(loader,
487: args);
488:
489: // Class res = targetLoader.findLoadedClass(className);
490: }
491: } catch (Exception ex) {
492: System.err
493: .println("Profiler Agent Error: internal error in ClassLoaderManager 2"); // NOI18N
494: ex.printStackTrace(System.err);
495: }
496:
497: return null;
498: }
499: }
|