All the functionality of ClassLoader is nested in org.apache.catalina.loader.WebappClassLoader
1) Structure of WebappClassLoader
public class WebappClassLoader extends URLClassLoader implements Lifecycle{ /** * The cache of ResourceEntry for classes and resources we have loaded, * keyed by resource name. */ protected HashMap resourceEntries = new HashMap(); /** * The list of not found resources. */ protected HashMap notFoundResources = new HashMap(); public Class loadClass(String name, boolean resolve); protected Class findLoadedClass0(String name); public Class findClass(String name); protected Class findClassInternal(String name); // This method is derived from java.lang.ClassLoader; it will invoke native method searching JVM for classes loaded by current class loaders // It will return the loaded class if and only if the class is previously loaded(call defineClass method) by current class loader instance protected final Class<?> findLoadedClass(String name); // This method is derived from java.lang.ClassLoader, it will invoke native method and it is the only way of converting byte[] into Class<?>; even if in our self defined ClassLoader. protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain); }
2) Overrides loadClass() on URLClassLoader
public Class loadClass(String name, boolean resolve){ // (0) Check local class cache clazz = findLoadedClass0(name); if (clazz != null) { return (clazz); } // (0.1) Check ClassLoader inherent cache, call native method clazz = findLoadedClass(name); if (clazz != null) { return (clazz); } // Here we can guarantee that target class hasn't been loaded by WebAppClassLoader before, but whether it has been loaded by System/Ext/Bootstrap ClassLoader is unknown // Try to find class from bootstrap/ext/system ClassLoader's inherent cache, if cannot find, try to load .class from class path; throw exception when cannot be load either try { clazz = system.loadClass(name); return (clazz); } catch (ClassNotFoundException e) { // Ignore } // Here we are sure that the target class hasn't loaded by current application before. // We will try to load it from container's WEB-INF/libs // delegateLoad means WebappClassLoader will use parent first model, if false, means WebappClassLoader will use child first model. boolean delegateLoad = delegate || filter(name); // load class with parent if (delegateLoad) { try{ clazz = parent.loadClass(name); if (clazz != null) { return (clazz); } }catch(ClassNotFoundException e){ // swallow e } } // load class with child(It is the end of parent delegation, and it is the start of child first) try { clazz = findClass(name); if (clazz != null) { return (clazz); } } catch (ClassNotFoundException e) { ; } // load class with parent(It is then end of child first model) if (!delegateLoad) { ClassLoader loader = parent; try { clazz = loader.loadClass(name); if (clazz != null) { return (clazz); } } catch (ClassNotFoundException e) { ; } } // All possible tries has failed, throw exception throw new ClassNotFoundException(name); } // get class from WebappClassLoader customized class cache protected Class findLoadedClass0(String name) { ResourceEntry entry = (ResourceEntry) resourceEntries.get(name); if (entry != null) { return entry.loadedClass; } return (null); // FIXME - findLoadedResource() }
3) How tomcat class reload works? Take a look at WebappClassLoader.stop()/start() as it implements interface Lifecycle;
// All ClassLoader did is clear its local class cache because we do not have the access to inherited java.lang.ClassLoader's inherent cache. // reload is realized by calling stop() and start(), then loadClass() loading all classes under WEB-INF/lib // loadClass(name) will first check local class cache which will return null, then it will check inherent cache, that would return "null" because // every time we restart, our WebappClassLoader is newly instantiated(the old instance is discarded) public void stop(){ started = false; notFoundResources.clear(); resourceEntries.clear(); repositoryURLs = null; } public void start() throws LifecycleException { started = true; }
The logic where a new WebappClassLoader instance is created every time the container is restarted:
// WebappLoader public void start(){ classLoader = createClassLoader(); classLoader.setResources(container.getResources()); classLoader.setDebug(this.debug); classLoader.setDelegate(this.delegate); // ... if (classLoader instanceof Lifecycle) ((Lifecycle) classLoader).start(); } /** * Create associated classLoader. */ private WebappClassLoader createClassLoader() throws Exception { Class clazz = Class.forName(loaderClass); WebappClassLoader classLoader = null; if (parentClassLoader == null) { parentClassLoader = Thread.currentThread().getContextClassLoader(); } Class[] argTypes = { ClassLoader.class }; Object[] args = { parentClassLoader }; Constructor constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader) constr.newInstance(args); return classLoader; }
Summary:
1) We do not have direct access to native class cache defined by JVM. But we can make a little tweaks to realize class loading:
1> We can suppose the native class cache keeps a map of Map<String, Class> with key of ClassLoader.instance.hashCode[classname+instancecode], and value of loaded class.
2> The class would be recorded into native cache when the class loader's instance calls defineClass(), and the cached class would be returned only when current class loader instance calls findLoadedClass0();