火柴人武林大会
156.74M · 2026-02-04
Java类加载器采用树形层级结构,包含三类核心加载器:
java.lang.*),位于<JAVA_HOME>/lib目录。<JAVA_HOME>/lib/ext目录或java.ext.dirs指定的扩展类。三者通过组合关系形成层级,而非继承。
当类加载器收到加载请求时,执行以下步骤:
findClass()方法加载。// ClassLoader源码核心逻辑(简化)
protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false); // 委派父加载器
} else {
c = findBootstrapClassOrNull(name); // Bootstrap加载
}
} catch (ClassNotFoundException ignored) {}
if (c == null) {
c = findClass(name); // 自加载
}
}
if (resolve) resolveClass(c);
return c;
}
}
此流程确保类加载请求最终由最顶层的Bootstrap尝试处理。
java.lang.String等类会被Bootstrap加载器拦截,避免篡改核心API。System.getProperty("java.version")等全局资源加载一致性。问题:java.sql.Driver由Bootstrap加载,但具体实现(如MySQL驱动)需由应用类加载器加载。
实现:通过Thread.currentThread().getContextClassLoader()获取上下文加载器,打破委派链。
// JDBC驱动加载示例
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class, Thread.currentThread().getContextClassLoader());
Tomcat的WebappClassLoader:优先从WEB-INF/lib加载类,而非委派给父加载器,实现Web应用隔离。
代码示例:
// Tomcat类加载逻辑(简化)
public Class<?> loadClass(String name) {
if (name.startsWith("com.example.app")) {
return findClass(name); // 自优先加载
}
return super.loadClass(name);
}
java.*等核心包委派,其他包由自身加载。问题:不同加载器加载同名类(如com.user.User),导致ClassCastException。
// 示例:两个Web应用加载不同版本的Log4j
ClassLoader loader1 = app1.getClassLoader();
ClassLoader loader2 = app2.getClassLoader();
Class<?> log4j1 = loader1.loadClass("org.apache.log4j.Logger");
Class<?> log4j2 = loader2.loadClass("org.apache.log4j.Logger");
// log4j1 != log4j2,类型不兼容
java.lang.Runtime,可能执行危险操作。弱引用缓存:对Class对象使用WeakReference,避免内存泄漏。
WeakReference<Class<?>> clazzRef = new WeakReference<>(MyClass.class);
清理机制:在Web应用停止时,显式卸载类加载器并释放资源。
JVM参数:
-verbose:class:跟踪类加载过程。-XX:+TraceClassLoading:输出详细加载日志。工具分析:使用JConsole、VisualVM监控类加载器状态。
双亲委派模型通过层级委派、唯一性保障和安全隔离,成为Java类加载机制的基石。尽管在SPI、热部署等场景中需灵活打破该模型,但开发者需深刻理解其风险,通过隔离加载、严格资源管理和监控调优规避潜在问题。未来,随着模块化系统(如Project Jigsaw)的普及,类加载机制将更趋精细化,但双亲委派的核心思想仍将延续。