001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.geronimo.kernel.config;
017:
018: import java.beans.Introspector;
019: import java.io.IOException;
020: import java.io.ObjectInputStream;
021: import java.io.ObjectOutputStream;
022: import java.io.ObjectStreamClass;
023: import java.lang.reflect.Field;
024: import java.net.URL;
025: import java.net.URLClassLoader;
026: import java.net.URLStreamHandlerFactory;
027: import java.util.ArrayList;
028: import java.util.Collection;
029: import java.util.Collections;
030: import java.util.Enumeration;
031: import java.util.HashSet;
032: import java.util.LinkedList;
033: import java.util.List;
034: import java.util.Map;
035: import java.util.Set;
036:
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039: import org.apache.geronimo.kernel.classloader.UnionEnumeration;
040: import org.apache.geronimo.kernel.repository.Artifact;
041: import org.apache.geronimo.kernel.util.ClassLoaderRegistry;
042:
043: /**
044: * A MultiParentClassLoader is a simple extension of the URLClassLoader that simply changes the single parent class
045: * loader model to support a list of parent class loaders. Each operation that accesses a parent, has been replaced
046: * with a operation that checks each parent in order. This getParent method of this class will always return null,
047: * which may be interpreted by the calling code to mean that this class loader is a direct child of the system class
048: * loader.
049: *
050: * @version $Rev: 562116 $ $Date: 2007-08-02 07:14:55 -0700 (Thu, 02 Aug 2007) $
051: */
052: public class MultiParentClassLoader extends URLClassLoader {
053: private static final Log log = LogFactory
054: .getLog(MultiParentClassLoader.class);
055: private final Artifact id;
056: private final ClassLoader[] parents;
057: private final boolean inverseClassLoading;
058: private final String[] hiddenClasses;
059: private final String[] nonOverridableClasses;
060: private final String[] hiddenResources;
061: private final String[] nonOverridableResources;
062: private boolean destroyed = false;
063:
064: // I used this pattern as its temporary and with the static final we get compile time
065: // optimizations.
066: private final static int classLoaderSearchMode;
067: private final static int ORIGINAL_SEARCH = 1;
068: private final static int OPTIMIZED_SEARCH = 2;
069:
070: static {
071: // Extract the classLoaderSearchMode if specified. If not, default to "safe".
072: String mode = System
073: .getProperty("Xorg.apache.geronimo.kernel.config.MPCLSearchOption");
074: int runtimeMode = OPTIMIZED_SEARCH; // Default to optimized
075: String runtimeModeMessage = "Original Classloading";
076: if (mode != null) {
077: if (mode.equals("safe")) {
078: runtimeMode = ORIGINAL_SEARCH;
079: runtimeModeMessage = "Safe ClassLoading";
080: } else if (mode.equals("optimized"))
081: runtimeMode = OPTIMIZED_SEARCH;
082: }
083:
084: classLoaderSearchMode = runtimeMode;
085: log
086: .info("ClassLoading behaviour has changed. The "
087: + runtimeModeMessage
088: + " mode is in effect. If you are experiencing a problem\n"
089: + "you can change the behaviour by specifying -DXorg.apache.geronimo.kernel.config.MPCLSearchOption= property. Specify \n"
090: + "=\"safe\" to revert to the original behaviour. This is a temporary change until we decide whether or not to make it\n"
091: + "permanent for the 2.0 release");
092: }
093:
094: /**
095: * Creates a named class loader with no parents.
096: *
097: * @param id the id of this class loader
098: * @param urls the urls from which this class loader will classes and resources
099: */
100: public MultiParentClassLoader(Artifact id, URL[] urls) {
101: super (urls);
102: this .id = id;
103: parents = new ClassLoader[] { ClassLoader
104: .getSystemClassLoader() };
105: inverseClassLoading = false;
106: hiddenClasses = new String[0];
107: nonOverridableClasses = new String[0];
108: hiddenResources = new String[0];
109: nonOverridableResources = new String[0];
110: ClassLoaderRegistry.add(this );
111: }
112:
113: /**
114: * Creates a named class loader as a child of the specified parent.
115: *
116: * @param id the id of this class loader
117: * @param urls the urls from which this class loader will classes and resources
118: * @param parent the parent of this class loader
119: */
120: public MultiParentClassLoader(Artifact id, URL[] urls,
121: ClassLoader parent) {
122: this (id, urls, new ClassLoader[] { parent });
123: }
124:
125: public MultiParentClassLoader(Artifact id, URL[] urls,
126: ClassLoader parent, boolean inverseClassLoading,
127: String[] hiddenClasses, String[] nonOverridableClasses) {
128: this (id, urls, new ClassLoader[] { parent },
129: inverseClassLoading, hiddenClasses,
130: nonOverridableClasses);
131: }
132:
133: /**
134: * Creates a named class loader as a child of the specified parent and using the specified URLStreamHandlerFactory
135: * for accessing the urls..
136: *
137: * @param id the id of this class loader
138: * @param urls the urls from which this class loader will classes and resources
139: * @param parent the parent of this class loader
140: * @param factory the URLStreamHandlerFactory used to access the urls
141: */
142: public MultiParentClassLoader(Artifact id, URL[] urls,
143: ClassLoader parent, URLStreamHandlerFactory factory) {
144: this (id, urls, new ClassLoader[] { parent }, factory);
145: }
146:
147: /**
148: * Creates a named class loader as a child of the specified parents.
149: *
150: * @param id the id of this class loader
151: * @param urls the urls from which this class loader will classes and resources
152: * @param parents the parents of this class loader
153: */
154: public MultiParentClassLoader(Artifact id, URL[] urls,
155: ClassLoader[] parents) {
156: super (urls);
157: this .id = id;
158: this .parents = copyParents(parents);
159: inverseClassLoading = false;
160: hiddenClasses = new String[0];
161: nonOverridableClasses = new String[0];
162: hiddenResources = new String[0];
163: nonOverridableResources = new String[0];
164: ClassLoaderRegistry.add(this );
165: }
166:
167: public MultiParentClassLoader(Artifact id, URL[] urls,
168: ClassLoader[] parents, boolean inverseClassLoading,
169: Collection hiddenClasses, Collection nonOverridableClasses) {
170: this (id, urls, parents, inverseClassLoading,
171: (String[]) hiddenClasses
172: .toArray(new String[hiddenClasses.size()]),
173: (String[]) nonOverridableClasses
174: .toArray(new String[nonOverridableClasses
175: .size()]));
176: }
177:
178: public MultiParentClassLoader(Artifact id, URL[] urls,
179: ClassLoader[] parents, boolean inverseClassLoading,
180: String[] hiddenClasses, String[] nonOverridableClasses) {
181: super (urls);
182: this .id = id;
183: this .parents = copyParents(parents);
184: this .inverseClassLoading = inverseClassLoading;
185: this .hiddenClasses = hiddenClasses;
186: this .nonOverridableClasses = nonOverridableClasses;
187: hiddenResources = toResources(hiddenClasses);
188: nonOverridableResources = toResources(nonOverridableClasses);
189: ClassLoaderRegistry.add(this );
190: }
191:
192: public MultiParentClassLoader(MultiParentClassLoader source) {
193: this (source.id, source.getURLs(),
194: deepCopyParents(source.parents),
195: source.inverseClassLoading, source.hiddenClasses,
196: source.nonOverridableClasses);
197: }
198:
199: static ClassLoader copy(ClassLoader source) {
200: if (source instanceof MultiParentClassLoader) {
201: return new MultiParentClassLoader(
202: (MultiParentClassLoader) source);
203: } else if (source instanceof URLClassLoader) {
204: return new URLClassLoader(((URLClassLoader) source)
205: .getURLs(), source.getParent());
206: } else {
207: return new URLClassLoader(new URL[0], source);
208: }
209: }
210:
211: ClassLoader copy() {
212: return MultiParentClassLoader.copy(this );
213: }
214:
215: private String[] toResources(String[] classes) {
216: String[] resources = new String[classes.length];
217: for (int i = 0; i < classes.length; i++) {
218: String className = classes[i];
219: resources[i] = className.replace('.', '/');
220: }
221: return resources;
222: }
223:
224: /**
225: * Creates a named class loader as a child of the specified parents and using the specified URLStreamHandlerFactory
226: * for accessing the urls..
227: *
228: * @param id the id of this class loader
229: * @param urls the urls from which this class loader will classes and resources
230: * @param parents the parents of this class loader
231: * @param factory the URLStreamHandlerFactory used to access the urls
232: */
233: public MultiParentClassLoader(Artifact id, URL[] urls,
234: ClassLoader[] parents, URLStreamHandlerFactory factory) {
235: super (urls, null, factory);
236: this .id = id;
237: this .parents = copyParents(parents);
238: inverseClassLoading = false;
239: hiddenClasses = new String[0];
240: nonOverridableClasses = new String[0];
241: hiddenResources = new String[0];
242: nonOverridableResources = new String[0];
243: ClassLoaderRegistry.add(this );
244: }
245:
246: private static ClassLoader[] copyParents(ClassLoader[] parents) {
247: ClassLoader[] newParentsArray = new ClassLoader[parents.length];
248: for (int i = 0; i < parents.length; i++) {
249: ClassLoader parent = parents[i];
250: if (parent == null) {
251: throw new NullPointerException("parent[" + i
252: + "] is null");
253: }
254: newParentsArray[i] = parent;
255: }
256: return newParentsArray;
257: }
258:
259: private static ClassLoader[] deepCopyParents(ClassLoader[] parents) {
260: ClassLoader[] newParentsArray = new ClassLoader[parents.length];
261: for (int i = 0; i < parents.length; i++) {
262: ClassLoader parent = parents[i];
263: if (parent == null) {
264: throw new NullPointerException("parent[" + i
265: + "] is null");
266: }
267: if (parent instanceof MultiParentClassLoader) {
268: parent = ((MultiParentClassLoader) parent).copy();
269: }
270: newParentsArray[i] = parent;
271: }
272: return newParentsArray;
273: }
274:
275: /**
276: * Gets the id of this class loader.
277: *
278: * @return the id of this class loader
279: */
280: public Artifact getId() {
281: return id;
282: }
283:
284: /**
285: * Gets the parents of this class loader.
286: *
287: * @return the parents of this class loader
288: */
289: public ClassLoader[] getParents() {
290: return parents;
291: }
292:
293: public void addURL(URL url) {
294: // todo this needs a security check
295: super .addURL(url);
296: }
297:
298: /**
299: * TODO This method should be removed and replaced with the best classLoading option. Its intent is to
300: * provide a way for folks to switch back to the old classLoader if this fix breaks something.
301: */
302: protected synchronized Class<?> loadClass(String name,
303: boolean resolve) throws ClassNotFoundException {
304: if (classLoaderSearchMode == ORIGINAL_SEARCH)
305: return loadSafeClass(name, resolve);
306: else
307: return loadOptimizedClass(name, resolve);
308: }
309:
310: /**
311: * This method executes the old class loading behaviour before optimization.
312: *
313: * @param name
314: * @param resolve
315: * @return
316: * @throws ClassNotFoundException
317: */
318: protected synchronized Class<?> loadSafeClass(String name,
319: boolean resolve) throws ClassNotFoundException {
320: //
321: // Check if class is in the loaded classes cache
322: //
323: Class cachedClass = findLoadedClass(name);
324: if (cachedClass != null) {
325: return resolveClass(cachedClass, resolve);
326: }
327:
328: // This is a reasonable hack. We can add some classes to the list below.
329: // Since we know these classes are in the system class loader let's not waste our
330: // time going through the hierarchy.
331: //
332: // The order is based on profiling the server. It may not be optimal for all
333: // workloads.
334:
335: if (name.startsWith("java.") || name.equals("boolean")
336: || name.equals("int") || name.equals("double")
337: || name.equals("long")) {
338: Class clazz = ClassLoader.getSystemClassLoader().loadClass(
339: name);
340: return resolveClass(clazz, resolve);
341: }
342:
343: //
344: // if we are using inverse class loading, check local urls first
345: //
346: if (inverseClassLoading && !isDestroyed()
347: && !isNonOverridableClass(name)) {
348: try {
349: Class clazz = findClass(name);
350: return resolveClass(clazz, resolve);
351: } catch (ClassNotFoundException ignored) {
352: }
353: }
354:
355: //
356: // Check parent class loaders
357: //
358: if (!isHiddenClass(name)) {
359: for (ClassLoader parent : parents) {
360: try {
361: Class clazz = parent.loadClass(name);
362: return resolveClass(clazz, resolve);
363: } catch (ClassNotFoundException ignored) {
364: // this parent didn't have the class; try the next one
365: }
366: }
367: }
368:
369: //
370: // if we are not using inverse class loading, check local urls now
371: //
372: // don't worry about excluding non-overridable classes here... we
373: // have alredy checked he parent and the parent didn't have the
374: // class, so we can override now
375: if (!isDestroyed()) {
376: try {
377: Class clazz = findClass(name);
378: return resolveClass(clazz, resolve);
379: } catch (ClassNotFoundException ignored) {
380: }
381: }
382:
383: throw new ClassNotFoundException(name + " in classloader " + id);
384: }
385:
386: /**
387: *
388: * Optimized classloading.
389: *
390: * This method is the normal way to resolve class loads. This method recursively calls its parents to resolve
391: * classloading requests. Here is the sequence of operations:
392: *
393: * 1. Call findClass to see if we already have this class loaded.
394: * 2. If not, call the SystemClassLoader which needs to be called anyway.
395: * 3. Check if inverse loading, if so look in our class loader.
396: * 4. Search our parents, recursively. Keeping track of which parents have already been called.
397: * Since MultiParentClassLoaders can appear more than once we do not search an already searched classloader.
398: * 5. Search our classloader.
399: *
400: */
401: protected synchronized Class<?> loadOptimizedClass(String name,
402: boolean resolve) throws ClassNotFoundException {
403: // System.err.println("Started load for class "+name+" in classloader "+this);
404: //
405: // Check if class is in the loaded classes cache
406: //
407: Class cachedClass = findLoadedClass(name);
408: if (cachedClass != null) {
409: return resolveClass(cachedClass, resolve);
410: }
411:
412: //
413: // No dice, let's offer the primordial loader a shot...
414: //
415: try {
416: return resolveClass(findSystemClass(name), resolve);
417: } catch (ClassNotFoundException cnfe) {
418: // ignore...just being a good citizen.
419: }
420:
421: //
422: // if we are using inverse class loading, check local urls first
423: //
424: if (inverseClassLoading && !isDestroyed()
425: && !isNonOverridableClass(name)) {
426: try {
427: Class clazz = findClass(name);
428: return resolveClass(clazz, resolve);
429: } catch (ClassNotFoundException ignored) {
430: }
431: }
432:
433: //
434: // Check parent class loaders
435: //
436: if (!isHiddenClass(name)) {
437: try {
438: LinkedList<ClassLoader> visitedClassLoaders = new LinkedList<ClassLoader>();
439: Class clazz = checkParents(name, resolve,
440: visitedClassLoaders);
441: if (clazz != null)
442: return resolveClass(clazz, resolve);
443: } catch (ClassNotFoundException cnfe) {
444: // ignore
445: }
446: }
447:
448: //
449: // if we are not using inverse class loading, check local urls now
450: //
451: // don't worry about excluding non-overridable classes here... we
452: // have alredy checked he parent and the parent didn't have the
453: // class, so we can override now
454: if (!isDestroyed()) {
455: try {
456: Class clazz = findClass(name);
457: return resolveClass(clazz, resolve);
458: } catch (ClassNotFoundException ignored) {
459: }
460: }
461:
462: throw new ClassNotFoundException(name + " in classloader " + id);
463: }
464:
465: /**
466: * This method is an internal hook that allows us to be performant on Class lookups when multiparent
467: * classloaders are involved. We can bypass certain lookups that have already occurred in the initiating
468: * classloader. Also, we track the classLoaders that are visited by adding them to an already vistied list.
469: * In this way, we can bypass redundant checks for the same class.
470: *
471: * @param name
472: * @param visitedClassLoaders
473: * @return
474: * @throws ClassNotFoundException
475: */
476: protected synchronized Class<?> loadClassInternal(String name,
477: boolean resolve, LinkedList<ClassLoader> visitedClassLoaders)
478: throws ClassNotFoundException {
479: //
480: // Check if class is in the loaded classes cache
481: //
482: Class cachedClass = findLoadedClass(name);
483: if (cachedClass != null) {
484: return resolveClass(cachedClass, resolve);
485: }
486:
487: //
488: // Check parent class loaders
489: //
490: if (!isHiddenClass(name)) {
491: try {
492: Class clazz = checkParents(name, resolve,
493: visitedClassLoaders);
494: if (clazz != null)
495: return resolveClass(clazz, resolve);
496: } catch (ClassNotFoundException cnfe) {
497: // ignore
498: }
499: }
500:
501: //
502: // if we are not using inverse class loading, check local urls now
503: //
504: // don't worry about excluding non-overridable classes here... we
505: // have alredy checked he parent and the parent didn't have the
506: // class, so we can override now
507: if (!isDestroyed()) {
508: Class clazz = findClass(name);
509: return resolveClass(clazz, resolve);
510: }
511:
512: return null; // Caler is expecting a class. Null indicates CNFE and will save some time.
513: }
514:
515: /**
516: * In order to optimize the classLoading process and visit a directed set of
517: * classloaders this internal method for Geronimo MultiParentClassLoaders
518: * is used. Effectively, as each classloader is visited it is passed a linked
519: * list of classloaders that have already been visited and can safely be skipped.
520: * This method assumes the context of an MPCL and is not for use external to this class.
521: *
522: * @param name
523: * @param visitedClassLoaders
524: * @return
525: * @throws ClassNotFoundException
526: */
527: private synchronized Class<?> checkParents(String name,
528: boolean resolve, LinkedList<ClassLoader> visitedClassLoaders)
529: throws ClassNotFoundException {
530: for (ClassLoader parent : parents) {
531: // When we've encountered the primordial loader we are done. Since we've already looked there we do not need
532: // to repeat the check. Simply return null and all things will be handled nicely.
533: if (parent == ClassLoader.getSystemClassLoader())
534: return null;
535: if (!visitedClassLoaders.contains(parent)) {
536: visitedClassLoaders.add(parent); // Track that we've been here before
537: try {
538: if (parent instanceof MultiParentClassLoader) {
539: Class clazz = ((MultiParentClassLoader) parent)
540: .loadClassInternal(name, resolve,
541: visitedClassLoaders);
542: if (clazz != null)
543: return resolveClass(clazz, resolve);
544: } else {
545: return parent.loadClass(name);
546: }
547: } catch (ClassNotFoundException cnfe) {
548: // ignore
549: }
550: }
551: }
552: // To avoid yet another CNFE we'll simply return null and let the caller handle appropriately.
553: return null;
554: }
555:
556: private boolean isNonOverridableClass(String name) {
557: for (String nonOverridableClass : nonOverridableClasses) {
558: if (name.startsWith(nonOverridableClass)) {
559: return true;
560: }
561: }
562: return false;
563: }
564:
565: private boolean isHiddenClass(String name) {
566: for (String hiddenClass : hiddenClasses) {
567: if (name.startsWith(hiddenClass)) {
568: return true;
569: }
570: }
571: return false;
572: }
573:
574: private Class resolveClass(Class clazz, boolean resolve) {
575: if (resolve) {
576: resolveClass(clazz);
577: }
578: return clazz;
579: }
580:
581: public URL getResource(String name) {
582: if (isDestroyed()) {
583: return null;
584: }
585:
586: //
587: // if we are using inverse class loading, check local urls first
588: //
589: if (inverseClassLoading && !isDestroyed()
590: && !isNonOverridableResource(name)) {
591: URL url = findResource(name);
592: if (url != null) {
593: return url;
594: }
595: }
596:
597: //
598: // Check parent class loaders
599: //
600: if (!isHiddenResource(name)) {
601: for (ClassLoader parent : parents) {
602: URL url = parent.getResource(name);
603: if (url != null) {
604: return url;
605: }
606: }
607: }
608:
609: //
610: // if we are not using inverse class loading, check local urls now
611: //
612: // don't worry about excluding non-overridable resources here... we
613: // have alredy checked he parent and the parent didn't have the
614: // resource, so we can override now
615: if (!isDestroyed()) {
616: // parents didn't have the resource; attempt to load it from my urls
617: return findResource(name);
618: }
619:
620: return null;
621: }
622:
623: public Enumeration<URL> findResources(String name)
624: throws IOException {
625: if (isDestroyed()) {
626: return Collections.enumeration(Collections.EMPTY_SET);
627: }
628:
629: Set<ClassLoader> knownClassloaders = new HashSet<ClassLoader>();
630: List<Enumeration<URL>> enumerations = new ArrayList<Enumeration<URL>>();
631:
632: recursiveFind(knownClassloaders, enumerations, name);
633:
634: return new UnionEnumeration<URL>(enumerations);
635: }
636:
637: protected void recursiveFind(Set<ClassLoader> knownClassloaders,
638: List<Enumeration<URL>> enumerations, String name)
639: throws IOException {
640: if (isDestroyed() || knownClassloaders.contains(this )) {
641: return;
642: }
643: knownClassloaders.add(this );
644: if (inverseClassLoading && !isNonOverridableResource(name)) {
645: enumerations.add(internalfindResources(name));
646: }
647: if (!isHiddenResource(name)) {
648: for (ClassLoader parent : parents) {
649: if (parent instanceof MultiParentClassLoader) {
650: ((MultiParentClassLoader) parent).recursiveFind(
651: knownClassloaders, enumerations, name);
652: } else {
653: if (!knownClassloaders.contains(parent)) {
654: enumerations.add(parent.getResources(name));
655: knownClassloaders.add(parent);
656: }
657: }
658: }
659: }
660: if (!inverseClassLoading) {
661: enumerations.add(internalfindResources(name));
662: }
663: }
664:
665: protected Enumeration<URL> internalfindResources(String name)
666: throws IOException {
667: return super .findResources(name);
668: }
669:
670: private boolean isNonOverridableResource(String name) {
671: for (String nonOverridableResource : nonOverridableResources) {
672: if (name.startsWith(nonOverridableResource)) {
673: return true;
674: }
675: }
676: return false;
677: }
678:
679: private boolean isHiddenResource(String name) {
680: for (String hiddenResource : hiddenResources) {
681: if (name.startsWith(hiddenResource)) {
682: return true;
683: }
684: }
685: return false;
686: }
687:
688: public String toString() {
689: return "[" + getClass().getName() + " id=" + id + "]";
690: }
691:
692: public synchronized boolean isDestroyed() {
693: return destroyed;
694: }
695:
696: public void destroy() {
697: synchronized (this ) {
698: if (destroyed)
699: return;
700: destroyed = true;
701: }
702:
703: LogFactory.release(this );
704: clearSoftCache(ObjectInputStream.class, "subclassAudits");
705: clearSoftCache(ObjectOutputStream.class, "subclassAudits");
706: clearSoftCache(ObjectStreamClass.class, "localDescs");
707: clearSoftCache(ObjectStreamClass.class, "reflectors");
708:
709: // The beanInfoCache in java.beans.Introspector will hold on to Classes which
710: // it has introspected. If we don't flush the cache, we may run out of
711: // Permanent Generation space.
712: Introspector.flushCaches();
713:
714: ClassLoaderRegistry.remove(this );
715: }
716:
717: private static final Object lock = new Object();
718: private static boolean clearSoftCacheFailed = false;
719:
720: private static void clearSoftCache(Class clazz, String fieldName) {
721: Map cache = null;
722: try {
723: Field f = clazz.getDeclaredField(fieldName);
724: f.setAccessible(true);
725: cache = (Map) f.get(null);
726: } catch (Throwable e) {
727: synchronized (lock) {
728: if (!clearSoftCacheFailed) {
729: clearSoftCacheFailed = true;
730: LogFactory.getLog(MultiParentClassLoader.class)
731: .debug(
732: "Unable to clear SoftCache field "
733: + fieldName + " in class "
734: + clazz);
735: }
736: }
737: }
738:
739: if (cache != null) {
740: synchronized (cache) {
741: cache.clear();
742: }
743: }
744: }
745:
746: protected void finalize() throws Throwable {
747: ClassLoaderRegistry.remove(this);
748: super.finalize();
749: }
750:
751: }
|