类加载器包括
- 启动类加载器
- 拓展类加载器(Java9之后已经被模块系统代替)
- 应用类加载器
加载内容
-
启动类加载器(Bootstrap ClassLoader)
负责加载核心 Java 类库,包括
java.lang.*、java.util.*、java.io.*等基础类。这些类通常位于JAVA_HOME/lib目录下的核心 JAR 文件(如rt.jar,从 Java 9 开始则是模块化的 JRT 文件系统)。
-
拓展类加载器(Extension ClassLoader)
负责加载 Java 扩展库中的类,通常位于
JAVA_HOME/lib/ext(新版java中已经没有这个目录了,已经被替代了),有时会将第三方jar包放进去
-
应用类加载器(Application ClassLoader)
负责加载应用程序类路径(classpath) 下的类。也就是运行时指定的
-classpath路径下的所有类,主要是用户代码和第三方库。
类关系
- 启动类加载器是所有加载器的顶层,没有父类加载器。
- 扩展类加载器的父类是启动类加载器。
- 应用类加载器的父加载器是扩展类加载器。
加载顺序
- 启动类加载器 ,然后是拓展类加载器,然后是应用类加载器(依托于双亲委托机制,后面会解释)
解释
JAVA_HOME
- JAVA_HOME是你JAVA下载的路径,JAVA_HOME是你配的环境变量
第三方jar包的加载
-
第三方jar包通常由应用类加载器加载而不是拓展类加载器
- 灵活性:将第三方库放在应用类加载器的类路径中更灵活,可以根据应用的需求灵活添加或移除依赖。
- 隔离性:每个应用可以有不同的类路径,放在应用类加载器可以避免多个应用之间的库版本冲突,而拓展类加载器是系统层面的,他会应用于所有项目
- 管理便利:大多数构建工具(如 Maven、Gradle)会自动将依赖放在应用的类路径中,而不是系统级的扩展路径。
应用类程序路径classpath
-
默认情况下,Java 的 classpath 是当前目录
假设在当前目录有如下结构:
arduin oproject/ ├── com/ │ └── example/ │ └── Main.class └── other/ └── Helper.class在
project目录下执行以下命令:bash cd project java com.example.Main此时,JVM 将会在
project/com/example路径下查找Main.class。 -
使用
-classpath或-cp命令行参数指定类路径在运行 Java 程序时,可以通过
-classpath或-cp参数指定类路径,例如:bash java -classpath /path/to/classes:/path/to/libs/library.jar com.example.Main或者
bash java -cp /path/to/classes:/path/to/libs/library.jar com.example.Main上述命令中,classpath 被设置为
/path/to/classes和/path/to/libs/library.jar,应用类加载器将会在这些路径中查找和加载类。
-
用
CLASSPATH环境变量在一些情况下,可以设置
CLASSPATH环境变量,指定 Java 应用程序的类路径。系统将默认使用CLASSPATH变量中定义的路径。-
Linux/macOS:
bash export CLASSPATH=/path/to/classes:/path/to/libs/library.jar -
Windows:
cmd set CLASSPATH=C:\path\to\classes;C:\path\to\libs\library.jar
注意:使用
-classpath参数会覆盖CLASSPATH环境变量,因此更常用的是使用-classpath指定特定程序的类路径,而不是全局设置CLASSPATH。 -
-
使用构建工具(如 Maven、Gradle)自动设置类路径
现代 Java 项目通常使用构建工具(如 Maven 和 Gradle),这些工具在构建和运行时会自动生成并指定类路径。比如,Maven 会将项目的第三方依赖下载到
target/classes目录下,并在执行时自动生成完整的类路径,无需手动指定。在 IDE 中运行 Java 程序时,IDE(如 IntelliJ IDEA、Eclipse)会根据项目结构和依赖配置生成类路径,并传递给 JVM,所以不需要手动设置。
双亲委托机制
-
假设一个类加载器
A要加载类X,双亲委托机制的工作流程如下:- 向上委托:类加载器
A先把加载请求交给它的父加载器B。
-
逐级上溯:
B再把请求往上委托给它的父加载器C,依此类推,直到请求到达最顶层的启动类加载器。- 找到类:如果某一层的父加载器找到了
X,则加载该类并返回。
- 未找到类:如果所有父加载器都没有找到
X,则加载请求沿着委托链返回到原始加载器A。
- 找到类:如果某一层的父加载器找到了
- 自己加载:此时,加载器
A自行尝试加载X,如果A找到该类,则成功加载。
- 向上委托:类加载器
-
为什么使用双亲委托机制?
- 安全性:通过双亲委托机制,Java 核心类(如
java.lang.String等)会优先由启动类加载器加载,避免被用户自定义的同名类覆盖,从而保证系统的安全性和稳定性。 - 类唯一性:双亲委托机制保证了同一类在 JVM 中的唯一性,防止不同类加载器加载出多个相同的类,确保类型检查的一致性。
- 模块化和分离性:双亲委托机制允许用户定义自定义类加载器,但核心类由启动类加载器统一管理,这样在模块化和隔离性方面实现了较好的平衡。
- 安全性:通过双亲委托机制,Java 核心类(如
JVM怎么通过classpath找到其所调用的类的
-
类路径的作用:
classpath指定了 JVM 在加载类时要查找的根目录或 JAR 文件路径。例如,classpath可能是项目的bin或out目录,这里存放编译好的.class文件。 -
包名映射路径:类的完全限定名(如
com.example.Main和com.example.Helper)会映射到文件系统的目录结构中:com.example.Main类在文件系统中对应路径为com/example/Main.class。com.example.Helper类对应路径为com/example/Helper.class。
-
类加载过程:
- 当执行
java com.example.Main时,应用类加载器会从classpath根目录(比如bin或out)下查找com/example/Main.class文件并加载Main类。 - 当
Main类中使用Helper类时,JVM 根据类路径在com/example目录下查找Helper.class文件。 - 如果在
classpath路径中找到了com/example/Helper.class,应用类加载器将加载它。
- 当执行