app class loader总结

本文深入解析Java中ClassLoader的工作原理及不同类型的类加载器的作用范围,包括BootStrapClassLoader、ExtClassLoader、AppClassLoader、EJBClassLoader及WebAppClassLoader等。特别讨论了类加载的委托模式及其在Web应用中的特例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自javaeye,作者bruce,原文链接http://forum.javaeye.com/viewtopic.php?t=11
摘录部分内容如下

Class Loader的作用范围:

BootStrap Class Loader:

load JRE/lib/rt.jar, sunrsasign.jar, charsets.jar, jce.jar, jsse.jar, plugin.jar

Ext Class Loader:

load JRE/lib/ext目录下的库文件, load JRE/classes目录下的类

App Class Loader:

load CLASSPATH变量指定路径下的类

以上的load路径都是写死在JVM的C++源代码里面的,不能改变,详细请见王森的《Java深度历险》

EJB Class Loader:

继承自App Class Loader,继承层次根据App Server有所不同,一个EJB Class Loader它的load Class的范围仅限于JAR或者EAR范围之内。App Server会针对每一个EJB包文件创建一个EJB Class Loader的实例.

Web App Class Loader:

继承自App Class Loader,继承层次根据App Server有所不同,一个Web App Class Loader:它的load Class的范围在 WEB-INF/lib下的库文件和WEB-INF/classes目录下的class文件。

Class Loader加载类的顺序:
The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.
一个例外:按照Servlet 规范, Web App Classloader的行为和普通的ClassLoader不同,它要先去load所需的Class,不成功在交给Parent来装入(除了jdk本身的类)。 

<think>我们正在讨论Java中的类加载器。用户的问题是关于不同类型的类加载器及其区别。根据提供的引用,我们可以整理出以下信息: 引用[1]提到:JVM类加载过程中使用同一个ClassLoader加载某个类时,无论加载多少次,在堆区生成的java.lang.Class对象始终只有一个。除非使用自定义类加载器破坏双亲委派机制,同一个类被不同类加载器加载,JVM才会将其视为两个不同的Class对象。 引用[2]说明:类加载器有几种,并举例java.lang.Object类,无论哪个类加载器需要加载这个类,最终都由Bootstrap ClassLoader加载,因此Object在程序各个类加载器环境中都是同一个类。同时强调了双亲委派模型的重要性,避免用户自定义的Object类造成混乱。 引用[3]则介绍了获取Class对象的几种方式,特别是字面常量方式(如Object.class)的优势:编译器检查、效率高、不会自动初始化类,并且适用于接口、数组和基本数据类型。 现在,我们需要回答用户的问题:Java中几种class loader的区别是什么? Java中有三种(实际上是四层)内置的类加载器,按照层次从高到低分别是: 1. Bootstrap ClassLoader(启动类加载器) 2. Extension ClassLoader(扩展类加载器) 3. Application ClassLoader(应用程序类加载器,也称系统类加载器) 4. 用户自定义类加载器(User-Defined ClassLoader) 它们之间的区别主要体现在以下几个方面: - 加载路径不同 - 加载的类不同 - 在JVM中的层次关系不同(双亲委派机制) 下面详细说明: ### 1. Bootstrap ClassLoader(启动类加载器) - **实现**:由C/C++实现,是JVM的一部分,不继承java.lang.ClassLoader。 - **职责**:加载Java核心库,如`$JAVA_HOME/jre/lib`下的核心类库(如rt.jar、charsets.jar等)以及被`-Xbootclasspath`参数指定的路径中的类。 - **特点**:是最顶层的类加载器,是其他所有类加载器的父加载器(注意:这里的父子关系不是继承,而是组合关系)。 - **示例**:如`java.lang.Object`、`java.lang.String`等核心类都由它加载[^2]。 ### 2. Extension ClassLoader(扩展类加载器) - **实现**:由Java实现,是`sun.misc.Launcher$ExtClassLoader`的实例。 - **职责**:加载扩展目录(`$JAVA_HOME/jre/lib/ext`)或由`java.ext.dirs`系统变量指定的路径中的所有类库。 - **特点**:它的父加载器是Bootstrap ClassLoader(虽然从Java角度看,其父类加载器为null,因为Bootstrap不是Java类,但逻辑上它是Extension ClassLoader的父加载器)。 - **示例**:加载扩展功能包,如javax包中的一些类。 ### 3. Application ClassLoader(应用程序类加载器) - **实现**:由Java实现,是`sun.misc.Launcher$AppClassLoader`的实例。 - **职责**:加载用户类路径(ClassPath)上的类库。如果没有自定义类加载器,默认情况下应用程序的类都由它加载。 - **特点**:它的父加载器是Extension ClassLoader。它是程序中默认的类加载器,通过`ClassLoader.getSystemClassLoader()`方法可以获取到它。 ### 4. User-Defined ClassLoader(用户自定义类加载器) - **实现**:由用户自定义的类加载器,继承`java.lang.ClassLoader`类。 - **职责**:根据用户需求加载特定路径的类。 - **特点**:可以打破双亲委派机制(通过重写`loadClass()`方法),实现热部署、模块化加载等。但如果不破坏双亲委派,则默认行为是委派给父类加载器。 ### 双亲委派机制(Parent Delegation Model) 类加载器在加载类时,首先会委托给父类加载器去尝试加载,只有当父类加载器无法加载时(在自己的搜索范围内找不到该类),才由自己加载。这种机制的好处是: 1. **避免重复加载**:确保一个类在同一个类加载器环境中只会被加载一次。 2. **安全**:防止核心API被篡改。例如,用户自定义一个`java.lang.Object`类,由于双亲委派机制,最终会由Bootstrap ClassLoader加载核心的Object类,而用户自定义的Object类不会被加载,从而保证了Java核心库的安全[^2]。 ### 类加载器的区别总结 | 类加载器类型 | 实现语言 | 加载路径/类库 | 父加载器 | 特点 | |--------------------|----------|-----------------------------------|----------------------|------------------------------------------| | Bootstrap | C/C++ | `$JAVA_HOME/jre/lib`核心库 | 无(最顶层) | 加载核心类,如java.lang包 | | Extension | Java | `$JAVA_HOME/jre/lib/ext`扩展库 | Bootstrap(逻辑上) | 加载扩展功能包 | | Application | Java | 用户类路径(ClassPath) | Extension | 默认的应用程序类加载器 | | User-Defined | Java | 用户指定路径 | 可指定(通常为App) | 可自定义行为,如打破双亲委派 | ### 示例:类加载过程 以加载用户自定义的`com.example.MyClass`为例: 1. `AppClassLoader`收到加载请求,先委派给父加载器`ExtClassLoader`。 2. `ExtClassLoader`再委派给父加载器`Bootstrap ClassLoader`。 3. `Bootstrap`在核心库中找不到该类,返回失败。 4. `ExtClassLoader`在扩展库中查找,也找不到,返回失败。 5. `AppClassLoader`在ClassPath中找到并加载`com.example.MyClass`。 ### 破坏双亲委派 在某些场景下(如热部署、OSGi模块化),需要打破双亲委派机制。例如,用户自定义类加载器重写`loadClass()`方法,先自己尝试加载类,而不是先委派给父类加载器。这样同一个类由不同的类加载器加载,会被JVM视为不同的类(即使全限定名相同),因为它们的类加载器不同[^1]。 ### 获取Class对象的方式 引用[3]提到,获取Class对象有几种方式,其中字面常量方式(如`Object.class`)是最简单、安全且高效的,因为它在编译期就确定,且不会触发类的初始化(而`Class.forName()`会触发静态初始化块)。这种方式也适用于基本数据类型和数组[^3]。 通过以上分析,我们可以清晰地理解不同类加载器的区别及其在Java类加载机制中的作用。</think>### Java类加载器类型及其区别 Java类加载器采用**双亲委派模型**,主要分为四类,区别如下: #### 1. **Bootstrap ClassLoader(启动类加载器)** - **实现语言**:C/C++(非Java实现) - **加载路径**:`$JAVA_HOME/jre/lib`核心库(如`rt.jar`、`charsets.jar`) - **特点**: - 最顶层的类加载器,是其他所有类加载器的逻辑父类 - **唯一不继承`java.lang.ClassLoader`**的加载器 - 负责加载Java核心类(如`java.lang.Object`)[^2] - **示例**: ```java System.out.println(Object.class.getClassLoader()); // 输出null(表示由Bootstrap加载) ``` #### 2. **Extension ClassLoader(扩展类加载器)** - **实现类**:`sun.misc.Launcher$ExtClassLoader`(Java实现) - **加载路径**:`$JAVA_HOME/jre/lib/ext`目录或`java.ext.dirs`指定路径 - **特点**: - 父加载器是Bootstrap ClassLoader - 加载标准扩展库(如`javax.*`包) - **示例**: ```java System.out.println(javax.crypto.Cipher.class.getClassLoader()); // 输出sun.misc.Launcher$ExtClassLoader@xxx ``` #### 3. **Application ClassLoader(应用类加载器/系统类加载器)** - **实现类**:`sun.misc.Launcher$AppClassLoader`(Java实现) - **加载路径**:用户类路径(ClassPath) - **特点**: - 父加载器是Extension ClassLoader - **默认的类加载器**,负责加载用户编写的类 - 通过`ClassLoader.getSystemClassLoader()`获取 - **示例**: ```java System.out.println(MyClass.class.getClassLoader()); // 输出sun.misc.Launcher$AppClassLoader@xxx ``` #### 4. **User-Defined ClassLoader(自定义类加载器)** - **实现方式**:继承`java.lang.ClassLoader`并重写`findClass()` - **加载路径**:用户指定路径(如网络、加密文件等) - **特点**: - 可打破双亲委派机制(重写`loadClass()`) - 同一类被不同加载器加载时,JVM视为不同类[^1] - **典型场景**: - 热部署(如Tomcat的WebappClassLoader) - 模块化加载(如OSGi) - 加载加密字节码 --- ### 核心机制:双亲委派模型 1. **工作流程**: - 类加载请求先委派给父加载器 - 父加载器无法完成时(如找不到类),子加载器才尝试加载 ```mermaid graph LR A[自定义加载器] --> B[AppClassLoader] B --> C[ExtClassLoader] C --> D[BootstrapClassLoader] D --失败--> C C --失败--> B B --失败--> A ``` 2. **核心作用**: - **避免重复加载**:确保类在JVM中唯一(同一加载器下)[^1] - **防止核心API篡改**:如用户定义`java.lang.Object`会被Bootstrap优先加载,自定义类无效[^2] - **安全隔离**:不同加载器的类互不可见(如Tomcat隔离Web应用) --- ### 关键区别总结 | 加载器类型 | 父加载器 | 加载范围 | 是否可被Java代码访问 | |------------------------|-------------------|--------------------------|----------------------| | Bootstrap ClassLoader | 无(顶级) | Java核心库 | ❌(返回null) | | Extension ClassLoader | Bootstrap | 扩展目录(`jre/lib/ext`)| ✔️ | | Application ClassLoader| Extension | 用户类路径(ClassPath) | ✔️ | | User-Defined | 可指定(默认App) | 自定义路径 | ✔️ | --- ### 示例:破坏双亲委派 自定义加载器重写`loadClass()`方法,跳过父加载器: ```java public class MyClassLoader extends ClassLoader { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { // 绕过双亲委派,直接自己加载 if (name.startsWith("com.myapp.")) { return findClass(name); } return super.loadClass(name); } } // 此时加载com.myapp.Foo,JVM会视为与AppClassLoader加载的com.myapp.Foo不同[^1] ``` --- ### 类加载与Class对象 - **Class对象唯一性**:同一类加载器对同一类多次加载,仍对应同一Class对象(`==`比较为`true`)[^1] - **获取Class对象方式**: - 安全高效:`MyClass.class`(字面量,不触发初始化)[^3] - 动态加载:`Class.forName("MyClass")`(触发初始化) --- ### 常见问题场景 1. **`ClassCastException`**: - 原因:同一类被不同加载器加载,JVM视为不同类 - 解决:确保关键类由同一加载器加载 2. **`NoClassDefFoundError`**: - 原因:父加载器加载的类依赖子加载器的类(违反委派机制) - 解决:调整类加载逻辑或使用线程上下文加载器(`Thread.currentThread().getContextClassLoader()`)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值