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