# java的双亲委派类加载机制(父亲委派机制可能更准确)
- 当一个类加载器受到类加载请求是,它首先不会自己去尝试加载类,而是依次将这个请求委托给父类加载器去完成,直到委托到最顶层的启动类加载器为止
- 只有当父类加载器无法完成该加载项时,子加载器才会尝试加载该类
# 为什么要使用双亲委派机制
- 可以避免类的重复加载,并且可以确保Java程序的稳定性、安全性、高效性和可靠性;
- 比如,在应用程序中定义一个
java.lang.String
类,启动应用程序,最终加载的类,依然是ext.jar中的类 - 另外,应用程序的类,不能使用
java.*
的包名,否则不能通过defineClass
方法的校验;
# 双亲委派机制的代码解析
# 顶级接口封装
public abstract class ClassLoader {
//xxx:父类加载器
private final ClassLoader parent;
//xxx: 加载类的方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//xxx: 加载类的动作,是串行的(虚拟机内串行)
synchronized (getClassLoadingLock(name)) {
//首先检查,该类是否已经被加载过
Class<?> c = findLoadedClass(name);
//xxx: 如果未加载过,则进行加载流程
if (c == null) {
//xxx: 如果父类不为空,则用父加载器加载, 否则用启动类加载器加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
//xxx: 如果负加载器,没加载到,则使用当前的类加载器进行加载
//xxx: 这里实际构成了一个 栈结构, 先进行压栈,所有入栈完毕后,出栈时才进行操作!
//xxx: 并且,出栈时的操作是有条件的,当前序的出栈没完成目标时,才进行操作
if (c == null) {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
//xxx: 当前类加载加载类的方法,由子类实现
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}
# 类加载的层级结构的构造
public class Launcher {
//xxx: 引导器的类加载器
private ClassLoader loader;
//xxx: appClassLoader的类层级结构
static class AppClassLoader extends URLClassLoader {
//xxx: 类资源的加载路径定义
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
/*xxx: 构建应用类加载器的工厂方法 */
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
//xxx: 省略其它抽象...
return new Launcher.AppClassLoader(var1x, var0);
}
//xxx: 内部私有构造器, 父类加载器由框架定义
AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
/*xxx: 加载类资源*/
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
//xxx: 省略其它抽象...
/*xxx: 该方法用于优化类加载器的性能,如果每次都搜索所有URL,即使不存在,也会花费大量的时间*/
/*xxx: 通过该方法,避免不必要的搜索, 即不用再压栈*/
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
//xxx: 委托给 双亲机制, 注意,它的直接父类没有覆写该方法
return super.loadClass(var1, var2);
}
}
}
static class ExtClassLoader extends URLClassLoader {
/*xxx: ext的父类加载器未空, 即 它的父类加载器为 启动类加载器 */
/*xxx: 且它的父类加载器是不能更改的, 因为是一个 定参, 且没有提供重载方法 */
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
//xxx:省略抽象, 核心方法均由父类提供...
}
public Launcher() {
//xxx: 省略其它抽象...
Launcher.ExtClassLoader var1 = Launcher.ExtClassLoader.getExtClassLoader();
//xxx: 构造双亲结构
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
//xxx: 省略其它抽象...
//xxx: 将启动类加载,设置为上下文类加载器
Thread.currentThread().setContextClassLoader(this.loader);
}
}
# 类加载器的实际加载功臣-URLClassLoader
public class URLClassLoader extends SecureClassLoader implements Closeable {
//xxx: 用于加载资源的路径,内部包含多个资源路径
private final URLClassPath ucp;
//xxx: 可以维持类加载器层级关系(但不是必须的), 同时需要指明 用于加载的资源路径
public URLClassLoader(URL[] urls, ClassLoader parent) {
super(parent);
//xxx: 省略其它抽象...
ucp = new URLClassPath(urls, acc);
}
public URLClassLoader(URL[] urls) {
//xxx: 省略其它抽象...
ucp = new URLClassPath(urls, acc);
}
//xxx: 实际查找类的方法
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
//xxx: 省略其它抽象...
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
//xxx: 注意,即使存在该类,也可能因为io错误导致类加载异常
throw new ClassNotFoundException(name, e);
} catch (ClassFormatError e2) {
throw e2;
}
}else{
return null;
}
}
//xxx: 获取资源的方法
public Resource getResource(String var1, boolean var2) {
int[] var4 = this.getLookupCache(var1);
URLClassPath.Loader var3;
for(int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {
Resource var6 = var3.getResource(var1, var2);
if (var6 != null) {
return var6;
}
}
return null;
}
//xxx: 获取资源的定义
private static class Loader implements Closeable {
private final URL base;
private JarFile jarfile;
Resource getResource(final String var1, boolean var2) {
//xxx: 省略其它抽象...
final URL var3 = new URL(this.base, ParseUtil.encodePath(var1, false));
final URLConnection var4 = var3.openConnection();
InputStream var8 = var4.getInputStream();
//xxx: 以上的步骤,出现任何异常,都将返回 null
//xxx: 本质上是流操作
return new Resource() {
public String getName() {
return var1;
}
public URL getURL() {
return var3;
}
public URL getCodeSourceURL() {
return Loader.this.base;
}
public InputStream getInputStream() throws IOException {
return var8;
}
public int getContentLength() throws IOException {
return var4.getContentLength();
}
};
}
}
}
# 双亲委派机制下-加载类的规则(主要是URLClassLoader)
- 父亲类加载,如果已经加载了类,则子类加载器不会再加载;并且要注意,类加载器本身有保护机制,不得自定义
java.*
的类 - 如果不同的jar包,存在同名的类(全限定名相同的类),会使用第一个加载的类,并且不会报错;
- 如果同一个jar包,存在同名的类(全限定名相同的类),则会报类重复定义的错误;(正常的情况下,不会出现这种情况,因为编译期通不过)
# 双亲委派机制的难点
# 理解super.loadClass与parent.loadClass的区别以及二者的联系
- super.loadClass本身只是交由父类处理,当调用该方法后,即进入了双亲委派机制
- 双亲委派的实际实现是由
paren.loadClass
的压栈实现的,类加载器的父子模型,运行时定义 - 这里实际可以衍生出两个概念,类对象层级与实体对象层级,实体对象层级是虚拟的,动态的;类对象层级是确定的,静态的;
- 双亲委派机制,类对象层级与实体对象层级共同完成,也是它的复杂所在;
- 双亲委派机制,由于包含动态的含义,因此它可能不止两级,可能存在多级;且需要遵循特定的使用规范,比如不能继承于覆写了loadClass的类加载器
# 理解loadClass与findClass的委派关系
- 类加载器加载类的实际方法,由findClass负责实现
- 要让类加载器正常运行,必须实现findClass方法,但是它通常有一个特定实现类:URLClassLoader.
- URLClassLoader是双亲委派模型的关键;
# fatjar原理
- springBoot可以将多个jar打包成一个jar包,直接执行
- 需要注意,war本质上也是一个fatjar,不过由于当其在servlet容器中执行时,没有使用fatjar机制;
# springBoot的fatjar类加载器体系源码解析
# fatJar引导器设计
/*xxx: fatjar 的实际启动类*/
public class JarLauncher extends ExecutableArchiveLauncher {
/*xxx:fatjar 还有一个类似于 classpath的索引信息,存储了项目的所有三方依赖情况 */
private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx";
static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
if (entry.isDirectory()) {
return entry.getName().equals("BOOT-INF/classes/");
}
return entry.getName().startsWith("BOOT-INF/lib/");
};
@Override
/*xxx: BOOT-INF/classes/,BOOT-INF/lib/被判定为包内资源*/
protected boolean isNestedArchive(Archive.Entry entry) {
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
}
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}
/*xxx: war包的实际启动类 */
/*xxx: 虽然此处有war的执行逻辑, 但实际上 在servlet容器中时, 并不会使用该逻辑*/
/*xxx: 该逻辑可能是为 以 fatjar的方式启动war而准备的 */
public class WarLauncher extends ExecutableArchiveLauncher {
@Override
/*xxx: WEB-INF/classes, WEB-IND/lib/,WEB-INF/lib-provider/被认为是包内资源*/
public boolean isNestedArchive(Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals("WEB-INF/classes/");
}
return entry.getName().startsWith("WEB-INF/lib/") || entry.getName().startsWith("WEB-INF/lib-provided/");
}
public static void main(String[] args) throws Exception {
new WarLauncher().launch(args);
}
}
public abstract class Launcher {
/*xxx: 实际的启动流程 */
protected void launch(String[] args) throws Exception {
//xxx: 创建本地ClassLoader,为 LaunchedURLClassLoader
ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
String jarMode = System.getProperty("jarmode");
String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
/*xxx: 调用launch引导方法 */
launch(args, launchClass, classLoader);
}
/*xxx: 为特定的 archive 创建 类加载器 */
/*xxx: 参数为 fatjar 或者war的三方依赖库 或实体类、资源等信息 */
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(50);
while (archives.hasNext()) {
urls.add(archives.next().getUrl());
}
/*xxx: 为这些资源创建类加载器 */
return createClassLoader(urls.toArray(new URL[0]));
}
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
/*xxx: 创建类加载器, 它的父上下文应该为 AppClassLoader*/
return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
}
protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
/*xxx: 将自定义的类加载器,设置为上下文加载器, 即主线程的上下文加载器 */
Thread.currentThread().setContextClassLoader(classLoader);
/*xxx: 执行应用层面的启动类 */
createMainMethodRunner(launchClass, args, classLoader).run();
}
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
/*xxx: 创建引导启动类 */
return new MainMethodRunner(mainClass, args);
}
}
# fatjar类加载器设计
- fatjar类加载器,主要原因在于其可以加载位于jar包内的jar资源
- 这应该是fatjar的类加载器最大的区别
/*xxx: fatJar的类加载器 */
public class LaunchedURLClassLoader extends URLClassLoader {
/*xxx: 创建类加载 它的父上下文应该为 AppClassLoader*/
public LaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) {
super(urls, parent);
this.exploded = exploded;
/*xxx: 根文档,可能代表 fatjar或者 war*/
this.rootArchive = rootArchive;
}
@Override
/*xxx: 加载类资源, 遵循父亲委派机制*/
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//xxx: 省略其它抽象..
return super.loadClass(name, resolve);
}
}
# 启动实际的应用
public class MainMethodRunner {
private final String mainClassName;
private final String[] args;
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null) ? args.clone() : null;
}
public void run() throws Exception {
/*xxx: 执行应用层面的 启动类, 使用自定义的类加载器加载 启动类,也就是fatjar类加载器-LaunchedURLClassLoader 进行执行 */
Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.setAccessible(true);
/*xxx: 通过反射进行执行 */
mainMethod.invoke(null, new Object[] { this.args });
}
}
# 常见的servlet容器类加载器机制
# tomcat容器-双亲委派机制的扩展
# 类加载器的结构设计
//xxx: 其会加载 $CATALINA_HOME/lib, $CATALINA_HOME/common, $CATALINA_HOME/shared 目录下的所有JAR文件和类文件
//xxx: 同时还支持对其进行扩展
public class CommonClassLoader extends URLClassLoader {
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 1. 先从缓存中查找是否已经加载过该类
Class<?> clazz = findLoadedClass(name);
if (clazz != null) {
return clazz;
}
// 2. 如果没有加载过,尝试从本地URL中查找类
try {
clazz = findClass(name);
return clazz;
} catch (ClassNotFoundException ignored) {
}
// 3. 如果在本地URL中找不到类,则委派给父类加载
return super.loadClass(name);
}
}
public class WebappClassLoader extends WebappClassLoaderBase {
}
public abstract class WebappClassLoaderBase extends URLClassLoader
implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
//xxx: 父亲类加载器
protected final ClassLoader parent;
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//加载类时,串行执行
synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
Class<?> clazz = null;
//xxx: 首先查找之前是否已经加载过
clazz = findLoadedClass0(name);
if (clazz != null) {
return clazz;
}
//xxx: 首先查找之前本地是否已经加载过, 遵循双亲委派机制
clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
if (clazz != null) {
return clazz;
}
//xxx: 是否代理委托加载,交给父亲加载器加载,其顺序是 CommonClassLoader->bootstrap->ext->app
if (delegateLoad) {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
return clazz;
}
}
//xxx: 本地加载
clazz = findClass(name);
if (clazz != null) {
return clazz;
}
//xxx: 没有委托的情况下,再让父类加载
if (!delegateLoad) {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
return clazz;
}
}
throw new ClassNotFoundException(name);
}
}
//xxx: 所有已经加载过的资源均进行缓存
protected Class<?> findLoadedClass0(String name) {
String path = binaryNameToPath(name, true);
ResourceEntry entry = resourceEntries.get(path);
if (entry != null) {
return entry.loadedClass;
}
return null;
}
}
# 类加载器的运行设计
/*xxx: 通过这个类,对catalina进行引导启动*/
public final class Bootstrap {
public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
//xxx: 省略其它抽象...
Bootstrap bootstrap = new Bootstrap();
bootstrap.init();
daemon = bootstrap;
} else {
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
}
public void init() throws Exception {
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
//xxx: 省略其它抽象...
}
private void initClassLoaders() {
try {
/*xxx: 通用类加载的的名称为 common,它的父加载器为 null*/
commonLoader = createClassLoader("common", null);
if (commonLoader == null) {
/*xxx: 如果没有指定 common类加载需要加载的路径,则使用 当前类加载,应该是: AppClassLoader*/
commonLoader = this.getClass().getClassLoader();
}
/*xxx: catalina类加载的名称为 server,它的父加载器为 common*/
catalinaLoader = createClassLoader("server", commonLoader);
/*xxx: 共享类加载的名称: shared,它的父加载器也为 common*/
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
System.exit(1);
}
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
/*xxx: 没有指定类加载器时,则返回父类加载器*/
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
/*xxx: 每个类加载器,都有对应加载的区域,可以自行配置 */
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(new Repository(repository, RepositoryType.DIR));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
}
public class WebappLoader extends LifecycleMBeanBase
implements Loader, PropertyChangeListener {
//xxx: 实际的 WebApplicationClassLoader
private WebappClassLoaderBase classLoader = null;
//xxx: 默认采用并行类加载器
private String loaderClass = ParallelWebappClassLoader.class.getName();
@Override
//xxx: 启动生命周期组件
protected void startInternal() throws LifecycleException {
//xxx: 省略其它抽象...
classLoader = createClassLoader();
classLoader.setResources(context.getResources());
classLoader.setDelegate(this.delegate);
//xxx: 省略其它抽象...
classLoader.start();
}
private WebappClassLoaderBase createClassLoader()
throws Exception {
//xxx: 如果已经创建过,则不再创建
if (classLoader != null) {
return classLoader;
}
Class<?> clazz = Class.forName(loaderClass);
Class<?>[] argTypes = { ClassLoader.class };
//xxx: 创建类加载器的同时,制定了父类加载器
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoaderBase) constr.newInstance(args);
}
}
# 其它servlet容器,如WebLogic,TongWeb
- 略,类比即可
# spring-boot热部署类加载器
# springBoot热部署类加载器层级
RestartClassLoader
- BootClassLoader的子类
- 当应用程序进行热部署时,它会重新加载已经修改过的类,如果没有被修改过,则委托给BootClassLoader
BootClassLoader
- 从classpath中加载SpringBoot框架的类库,如spring-boot-starter-web,spring-boot-starter-data-jpa等;
- 它在热更新的环境时,才存在于三方依赖中
- 它加载了应用程序的核心类库,整个应用程序运行期间,都不会发生变化,算是提高效率的;
LaunchedURLClassLoader
- 内容见fatjar部分;
AppClassLoader
- 内容见父亲委派机制部分;