ClassLoader介绍

JVM在运行时会产生三个ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.其中,Bootstrap是用C++编写的,我们在Java中看不到它,是null。它用来加载核心类库,在JVM源代码中这样写道:

static const char classpathFormat[] =
"%/lib/rt.jar:"
"%/lib/i18n.jar:"
"%/lib/sunrsasign.jar:"
"%/lib/jsse.jar:"
"%/lib/jce.jar:"
"%/lib/charsets.jar:"
"%/classes";

知道为什么不需要在classpath中加载这些类了吧?人家在JVM启动的时候就自动加载了,并且在运行过程中根本不能修改Bootstrap加载路径。
Extension ClassLoader用来加载扩展类,即/lib/ext中的类。
最后AppClassLoader才是加载Classpath的。
ClassLoader加载类用的是委托模型。即先让Parent类(而不是Super,不是继承关系)寻找,Parent找不到才自己找。看来 ClassLoader还是蛮孝顺的。三者的关系为:AppClassLoader的Parent是ExtClassLoader,而 ExtClassLoader的Parent为Bootstrap ClassLoader。加载一个类时,首先BootStrap先进行寻找,找不到再由ExtClassLoader寻找,最后才是 AppClassLoader。
为什么要设计的这么复杂呢?其中一个重要原因就是安全性。比如在Applet中,如果编写了一个java.lang.String类并具有破坏性。假如不采用这种委托机制,就会将这个具有破坏性的String加载到了用户机器上,导致破坏用户安全。但采用这种委托机制则不会出现这种情况。因为要加载java.lang.String类时,系统最终会由Bootstrap进行加载,这个具有破坏性的String永远没有机会加载。
我们来看这段代码:

//A.java
public class A{
public static void main(String[] args){
A a=new A();
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(a.getClass().getClassLoader());
B b=new B();
b.print();
}
}
//B.java
public class B{
public void print(){
System.out.println(this.getClass().getClassLoader());
}
}


1、我们将它放在Classpath中,则打印出
sun.misc.Launcher$AppClassLoader@92e78c
sun.misc.Launcher$AppClassLoader@92e78c
可见都是由AppClassLoader来加载的。
2、我们将其放在%jre%/lib/ext/classes(即ExtClassLoader的加载目录。其加载/lib/ext中的jar文件或者子目录classes中的class文件)中。则会打印出:
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$ExtClassLoader
3、我们将A.class放到%jre%/lib/ext/classes中,而将B.class放到classpaht中又会怎么样呢?结果是:

sun.misc.Launcher$ExtClassLoader
Exception in thread "main" java.lang.NoClassDefFoundError:B
at A.main(A.java:6)

怎么会这样呢?这其中有一个重要的问题:A类当然是由ExtClassLoader来加载的,B类要由哪个加载呢?B类要由调用它自己的类的类加载器(真拗口)。也就是说,A调用了B,所以B由A的类加载器ExtClassLoader来加载。ExtClassLoader根据委托机制,先拜托 Bootstrap加载,Bootstrap没有找到。然后它再自己寻找B类,还是没找到,所以抛出异常。ExtClassLoader不会请求 AppClassLoader来加载!你可能会想:这算什么问题,我把两个类放到一起不就行了?
[color=blue]呵呵,没这么简单。比如JDBC是核心类库,而各个数据库的JDBC驱动则是扩展类库或在classpath中定义的。所以JDBC由 Bootstrap ClassLoader加载,而驱动要由AppClassLoader加载。等等,问题来了,Bootstrap不会请求AppClassLoader加载类啊。那么,他们怎么实现的呢?我就涉及到一个Context ClassLoader的问题,调用Thread.getContextClassLoader。[/color]
<think>好的,用户现在想详细了解Java ClassLoader的原理。先看看之前的对话历史,用户之前问过关于垃圾回收的根节点,其中提到了系统类加载器加载的类作为GC Root,现在用户根据提供的引用资料,进一步询问ClassLoader的原理。需要结合用户提供的四个引用内容来组织回答。 首先,用户引用了四个资料,其中引用1和引用2提到了ClassLoader的委派模型和双亲委派机制,以及如何打印类加载器的层次结构。引用4详细说明了不同类加载器的层次结构,包括启动类加载器、扩展类加载器、应用类加载器和自定义加载器。引用3是一个测试类的代码示例,演示了自定义网络类加载器的使用。用户的需求是获取ClassLoader原理的详细信息,所以需要覆盖这些方面。 接下来要确保回答结构清晰,分点解释。可能需要包括类加载器的定义、作用、核心机制(如双亲委派)、类加载过程、层次结构、自定义类加载器以及示例代码。同时,用户提到要参考之前的回答,所以可能需要联系之前提到的GC Root中的系统类加载器部分,说明类加载器如何影响对象的可达性。 需要注意的是用户可能对双亲委派模型的具体流程和优势感兴趣,以及如何打破这个模型。同时,用户提供的代码示例可以用于演示类加载器的层次结构和自定义加载器的实现,应该结合这些示例来解释原理。 还需要检查是否需要覆盖类加载器的各个阶段:加载、链接、初始化,但根据引用内容,用户提供的资料主要关注加载阶段和委派机制,可能不需要深入每个阶段的细节,但简要提及会更全面。 最后,生成的相关问题需要与ClassLoader相关,比如双亲委派的好处、如何自定义类加载器、类加载器与GC的关系等。确保问题与用户之前的问题和当前主题相关。</think>### Java ClassLoader 原理详解 #### 一、核心概念与作用 **类加载器(ClassLoader)** 是 Java 虚拟机(JVM)实现动态类加载的核心组件,负责在运行时将 `.class` 文件加载到内存并生成对应的 `Class` 对象。其核心作用包括: - **动态性**:支持程序运行时加载新类(如插件化开发)。 - **隔离性**:不同类加载器加载的类可能形成隔离(如Tomcat隔离Web应用)。 - **安全性**:通过双亲委派机制限制核心类库被篡改。 --- #### 二、类加载器的层次结构 Java 类加载器采用**分层模型**,分为以下四类[^4]: 1. **启动类加载器(Bootstrap ClassLoader)** - **职责**:加载 `<JAVA_HOME>/lib` 目录下的核心类库(如 `java.lang.*`、`java.util.*`)。 - **实现**:由 JVM 自身实现(C/C++代码),不继承 `ClassLoader` 类。 - **示例**:`String.class` 的加载器为 `null`(即 Bootstrap ClassLoader)。 2. **扩展类加载器(Extension ClassLoader)** - **职责**:加载 `<JAVA_HOME>/lib/ext` 目录下的扩展类(如 `javax.*` 包)。 - **实现**:`sun.misc.Launcher$ExtClassLoader` 类。 3. **应用程序类加载器(Application ClassLoader)** - **职责**:加载用户类路径(ClassPath)下的类(用户自定义类或第三方库)。 - **实现**:`sun.misc.Launcher$AppClassLoader` 类。 4. **自定义类加载器(Custom ClassLoader)** - **职责**:实现特定场景的类加载(如网络加载、热部署)。 - **实现**:继承 `ClassLoader` 并重写 `findClass()` 方法。 --- #### 三、双亲委派机制 **双亲委派模型(Parent Delegation Model)** 是类加载的核心规则,流程如下: 1. 类加载请求首先委派给父加载器处理。 2. 若父加载器无法完成(在自己的搜索范围内未找到类),子加载器才尝试加载。 ```java // 示例:打印类加载器层次结构(引用[2][3]) ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while (loader != null) { System.out.println(loader); // 输出:AppClassLoaderExtClassLoader → null(Bootstrap) loader = loader.getParent(); } ``` **优势**: - **安全性**:防止用户自定义类冒充核心类(如自定义 `java.lang.String` 会被 Bootstrap ClassLoader 优先加载,但实际加载的是核心类)。 - **资源复用**:避免重复加载已存在的类。 --- #### 四、类加载过程 类加载分为三个阶段(按顺序执行): 1. **加载(Loading)** - 查找 `.class` 文件并读取字节码。 - 生成 `Class` 对象(作为方法区中类数据的访问入口)。 2. **链接(Linking)** - **验证**:检查字节码是否符合 JVM 规范。 - **准备**:为静态变量分配内存并赋初始值(如 `int` 初始化为 0)。 - **解析**:将符号引用转换为直接引用。 3. **初始化(Initialization)** - 执行静态代码块(`static {}`)和静态变量赋值。 --- #### 五、自定义类加载器 **适用场景**: - 从网络或加密文件中加载类(如引用[3]中的 `NetworkClassLoader`)。 - 实现热部署(如Tomcat重载修改后的类)。 **实现步骤**: 1. 继承 `ClassLoader` 类。 2. 重写 `findClass()` 方法(**不推荐重写 `loadClass()`,否则破坏双亲委派**)。 ```java // 示例:网络类加载器(引用[3]) public class NetworkClassLoader extends ClassLoader { private String rootUrl; @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = loadClassDataFromNetwork(name); // 从网络加载字节码 return defineClass(name, classData, 0, classData.length); } } ``` --- #### 六、类加载器与垃圾回收 - **ClassLoader 与 GC Root**: 系统类加载器(如 `AppClassLoader`)属于 GC Root,其加载的类及静态变量引用的对象不会被回收[^1]。 - **卸载条件**: 类的 `Class` 对象和其 ClassLoader 均不可达时,类才能被卸载。 --- ### 总结 类加载器通过分层模型和双亲委派机制,实现了类的动态加载与安全隔离。理解其原理对解决类冲突(如 `NoClassDefFoundError`)、实现热部署等高级场景至关重要。 --- ### 相关问题 1. **双亲委派模型如何避免核心类被篡改?** 2. **如何实现一个支持热部署的自定义类加载器?** 3. **类加载器与模块化(如Java 9模块系统)的关系是什么?**
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值