1. jvm 默认的类加载机制
加载某个类时会先委托父加载器寻找目标类,找不到再 委托上层父加载器加载,如果 所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入 目标类。这就是双亲委派机制。
2. tomcat 打破双亲委派
tomcat 的ClassLoader
CommonClassLoader : Tomcat 通用类加载器, 加载的资源可被 Tomcat 和 所有的 Web 应用程序共同获取
CatalinaClassLoader : Tomcat 类加载器, 加载的资源只能被 Tomcat 获取(但 所有 WebappClassLoader 不能获取到 catalinaLoader 加载的类)
SharedClassLoader : Tomcat 各个Context的父加载器, 这个类是所有 WebappClassLoader 的父类, sharedLoader 所加载的类将被所有的 WebappClassLoader 共享获取
WebappClassLoader : 每个Context 对应一个 WebappClassloader, 主要用 于加载 WEB-INF/lib 与 WEB-INF/classes 下面的资源
源码:
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
if (log.isDebugEnabled()) {
log.debug("loadClass(" + name + ", " + resolve + ")");
}
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
// (0) Check our previously loaded local class cache
//1. 先在本地 cache 查找该类是否已经加载过
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// (0.1) Check our previously loaded class cache
//2. 从系统类加载器的 cache 中查找是否加载过
clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// (0.2) Try loading the class with the bootstrap class loader, to prevent
// the webapp from overriding Java SE classes. This implements
// SRV.10.7.2
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// Use getResource as it won't trigger an expensive
// ClassNotFoundException if the resource is not available from
// the Java SE class loader. However (see
// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
// details) when running under a security manager in rare cases
// this call may trigger a ClassCircularityError.
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
// details of how this may trigger a StackOverflowError
// Given these reported errors, catch Throwable to ensure any
// other edge cases are also caught
URL url;
if (securityManager != null) {
PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
// Swallow all exceptions apart from those that must be re-thrown
ExceptionUtils.handleThrowable(t);
// The getResource() trick won't work for this class. We have to
// try loading it directly and accept that we might get a
// ClassNotFoundException.
tryLoadingFromJavaseLoader = true;
}
if (tryLoadingFromJavaseLoader) {
try {
// 3. 尝试用 ExtClassLoader 类加载器类加载
// 目的:防止 Web 应用自己的类覆盖 JRE 的核心类
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (0.5) Permission to access this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = sm.getString("webappClassLoader.restrictedPackage", name);
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested
if (delegateLoad) {
if (log.isDebugEnabled()) {
log.debug(" Delegating to parent classloader1 " + parent);
}
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Loading class from parent");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (2) Search local repositories
if (log.isDebugEnabled()) {
log.debug(" Searching local repositories");
}
try {
// 4. 尝试在本地目录搜索 class 并加载
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Loading class from local repository");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// (3) Delegate to parent unconditionally
//默认为非委托
if (!delegateLoad) {
if (log.isDebugEnabled()) {
log.debug(" Delegating to parent classloader at end: " + parent);
}
try {
// 5. 尝试用系统类加载器 (也就是 AppClassLoader) 来加载
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Loading class from parent");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
//6. 上述过程都加载失败,抛出异常
throw new ClassNotFoundException(name);
}
总结:
1. findLoadedClass0(name); Finds the class with the given name if it has previously been loaded and cached by this class loader 2. findLoadedClass(name); Returns the class with the given binary name if this loader has been recorded by the Java virtual machine as an initiating loader of a class with that binary name. 3.javaseLoader.loadClass(name). --> 尝试用 ExtClassLoader 类加载器类加载 4.findClass(name) -->尝试在本地目录搜索 class 并加载/ Find the specified class in our local repositories 5. Class.forName(name, false, parent); -->尝试用系统类加载器 (也就是 AppClassLoader) 来加载