Java的双亲委派模型
JDK8及之前版本的三层类加载器
-
Bootstrap Class Loader
HotSpot中由C++语言实现,是虚拟机自身的一部分。这个类加载器负责加载存放在<JAVA_HOME>\lib
目录,或者被-Xbootclasspath
参数所指定的路径中存放的,而且是Java虚拟机能够 识别的(按照文件名识别,如rt.jar
、tools.jar
,名字不符合的类库即使放在lib目录中也不会被加载)类 库加载到虚拟机的内存中。通过这个类加载的,在java代码中获取classloader
的时候返回的是null
-
扩展类加载器(Extension Class Loader)
这个类加载器是在类sun.misc.Launcher$ExtClassLoader
中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext
目录中,或者被java.ext.dirs
系统变量所指定的路径中所有的类库。
JDK9之后,引入了模块化的机制,这个类就退出了历史舞台了。 -
应用程序类加载器(Application Class Loader)
这个类加载器由sun.misc.Launcher$AppClassLoader
来实现。由于应用程序类加载器是ClassLoader
类中的getSystemClassLoader()
方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径 (ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。 -
自定义类加载器
用户可以自己在java代码中声明一个类加载器,但是父加载器只能是“Application Class Loader”,这样就可能会出现,因为自定义的类加载器不同,导致的ClassNotFoundException
Parents Delegation Model,双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。
ClassLoader加载类的流程
Java代码中具体的类加载是通过,Class
类中的loadClass
方法来完成的
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 从jvm缓存中查找类是否存在,这个地方应该是线程内部的缓存
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 一些不相关的代码
if (c == null) {
// 此方法在ClassLoder类中是空的,需要子类来具体实现,也就是
c = findClass(name);
}
}
if (resolve) {
// jdk11这个方法已经没用了。
resolveClass(c);
}
return c;
}
}
- 从vm缓存中查找
- 从父类加载器中查找,如果父类加载器为空,则从
Bootstrap Class Loader
中查找 - 从自己的
findClass
重载方法中查找 - 抛出异常
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
Tomcat的类加载模型
Common Class Loader
位于Tomcat架构顶层的公用类加载器,父加载器为Java的Application Class Loader
,默认读取conf/catalina.properties
配置文件中的common.loader
属性,默认值为:${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar
Catalina Class Loader
以Common Class Loader
为父加载器,用于加载tomcat应用服务器的类加载器,默认读取conf/catalina.properties
配置文件中的server.loader
属性,默认值为空
Shared Class Loader
以Common Class Loader
为父加载器,所有Web应用的父类加载器,默认读取conf/catalina.properties
配置文件中的shared.loader
属性,默认值为空
Webapp Class Loader
以Shared Class Loader
为父加载器,加载/WEB-INF/classes
目录下的Class文件和资源,/WEB-INF/lib
目录下的jar包文件,只对当前web应用可见
这级别的ClassLoader对应的类为org.apache.catalina.loader.WebappClassLoaderBase
重写了loadClass
方法,破坏了java中的“双亲委派模型”
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
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
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);
// 其实这个地方拿到的只能是Java中的ExtensionClassLoader,因为BootClassLoader是null
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 {
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 {
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 {
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
}
}
}
throw new ClassNotFoundException(name);
}
总结下来分这么几个步骤:
破坏委托模式时:
- 从缓存中加载
- 从JVM中的ExtensionClassLoader中加载,这个地方因为走的是委托模式,所以会按照Bootstrap -> Extension的路径查找
- 从当前类加载器中加载
- 从父加载器中加载,这个地方也会遵从委托模式,所以会按照 Bootstrap -> Extension -> Application -> Common -> Shared 路径查找
开启委托模式时:
- 从缓存中加载
- 从JVM中的ExtensionClassLoader中加载,这个地方因为走的是委托模式,所以会按照Bootstrap -> Extension的路径查找
- 从父加载器中加载,这个地方也会遵从委托模式,所以会按照 Bootstrap -> Extension -> Application -> Common -> Shared 路径查找
- 从当前类加载器中加载