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