Skip to content

面试速答(先看这里)

**一句话结论:**传统的采用双亲委派的类加载机制大家都知道,要加载一个类的额时候,先委托父类加载器加载该类;

60秒标准回答:

传统的采用双亲委派的类加载机制大家都知道,要加载一个类的额时候,先委托父类加载器加载该类;如果父类加载器无法找到(类不存在),才由当前 ClassLoader 加载;这样保证了核心类库(rt.jar)不会被重复加载,避免了类冲突问题

在JDK 1.8之前,传统的双亲委派如下

在JDK 1.9中,传统的双亲委派如下

**答题顺序:**结论 → 原理/机制 → 关键流程 → 场景与取舍 → 易错点

回答主线:

  • **要点1:**在JDK 1.8之前,传统的双亲委派如下:
  • **要点2:**在JDK 1.9中,传统的双亲委派如下:
  • **要点3:**Spring Boot 打破了传统的双亲委派模型,采用 自定义的 LaunchedURLClassLoader 进行类加载,主要为了支持 "Fat JAR"(可执行 JAR) 的运行。
  • **要点4:**在 Spring Boot 中,使用 Maven 或 Gradle 构建项目时, lib/ 目录中的第三方依赖是以 JAR 形式打包进主 JAR 内部,默认会生成一个包含所有依赖项的 fat jar。
  • **要点5:**为了支持 Fat JAR 运行模式 ,Spring Boot 使用 LaunchedURLClassLoader 替代 AppClassLoader ,打破双亲委派机制,核心做法是:

**记忆锚点:**JAR → LaunchedURLClassLoader → Spring → Boot → AppClassLoader → JDK

易错提醒:

  • 这样保证了核心类库(rt.jar)不会被重复加载,避免了类冲突问题。

加分表达:

  • 如果父类加载器无法找到(类不存在),才由当前 ClassLoader 加载;
  • 为了支持 Fat JAR 运行模式 ,Spring Boot 使用 LaunchedURLClassLoader 替代 AppClassLoader ,打破双亲委派机制,核心做法是: 先加载 BOOT-INF/classes 目录下的应用类 (优先于 JDK 类)。

追问准备:

  • 围绕「JAR」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「LaunchedURLClassLoader」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「Spring」:底层原理是什么?使用时有哪些边界和常见坑?
  • 如果线上出现异常,你会如何定位、验证并规避?

典型回答 ​

传统的采用双亲委派的类加载机制大家都知道,要加载一个类的额时候,先委托父类加载器加载该类;如果父类加载器无法找到(类不存在),才由当前 ClassLoader 加载;这样保证了核心类库(rt.jar)不会被重复加载,避免了类冲突问题。

在JDK 1.8之前,传统的双亲委派如下:

📄 ✅什么是双亲委派?如何破坏?

打开文档:✅什么是双亲委派?如何破坏?

在JDK 1.9中,传统的双亲委派如下:

📄 ✅JDK1.8和1.9中类加载器有哪些不同

打开文档:✅JDK1.8和1.9中类加载器有哪些不同

Spring Boot 打破了传统的双亲委派模型,采用 自定义的 **LaunchedURLClassLoader** 进行类加载,主要为了支持 "Fat JAR"(可执行 JAR) 的运行。

📄 ✅什么是fat jar?

打开文档:✅什么是fat jar?

在 Spring Boot 中,使用 Maven 或 Gradle 构建项目时,lib/ 目录中的第三方依赖是以 JAR 形式打包进主 JAR 内部,默认会生成一个包含所有依赖项的 fat jar。

传统的 Application ClassLoader 只能从 外部 **classpath** 加载类,无法直接加载 JAR 包内嵌的其他 JAR(fat jar)。 因此 Spring Boot 需要自定义类加载器。

为了支持 Fat JAR 运行模式,Spring Boot 使用 **LaunchedURLClassLoader** 替代 **AppClassLoader**,打破双亲委派机制,核心做法是:

  • 先加载 **BOOT-INF/classes** 目录下的应用类(优先于 JDK 类)。
  • 再加载 **BOOT-INF/lib/** 目录下的依赖 JAR(传统 AppClassLoader 无法加载嵌套 JAR)。
  • 最后才交给父类加载器(即 JDK 提供的 AppClassLoader)。

也就是说Spring Boot 先尝试加载自身的类和依赖 JAR,找不到才交给父类加载器,从而打破双亲委派。

Talk is Cheap,Show me the Code:

https://github.com/joansmith/spring-boot/blob/master/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java

以下是LaunchedURLClassLoader 的源码:

plain
@Override
	protected Class<?> loadClass(String name, boolean resolve)
			throws ClassNotFoundException {
		synchronized (LaunchedURLClassLoader.LOCK_PROVIDER.getLock(this, name)) {
			Class<?> loadedClass = findLoadedClass(name);
			if (loadedClass == null) {
				Handler.setUseFastConnectionExceptions(true);
				try {
					loadedClass = doLoadClass(name);
				}
				finally {
					Handler.setUseFastConnectionExceptions(false);
				}
			}
			if (resolve) {
				resolveClass(loadedClass);
			}
			return loadedClass;
		}
	}

	private Class<?> doLoadClass(String name) throws ClassNotFoundException {

		// 1) Try the root class loader
		try {
			if (this.rootClassLoader != null) {
				return this.rootClassLoader.loadClass(name);
			}
		}
		catch (Exception ex) {
			// Ignore and continue
		}

		// 2) Try to find locally
		try {
			findPackage(name);
			Class<?> cls = findClass(name);
			return cls;
		}
		catch (Exception ex) {
			// Ignore and continue
		}

		// 3) Use standard loading
		return super.loadClass(name, false);
	}

可以看到,在loadClass方法中,主要调用doLoadClass,仔细看doLoadClass的第37行,和第45行,可以看到,先执行37行的findClass,然后再执行45行的super.loadClass,这就意味着,他会先自己加载,然后加载不到再委托给父加载器加载的。