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