ClassLoader
动态加载某个 class 文件到内存中, 因为我们写好程序以后, 不管是 CS 结构还是 BS 结构的程序, 都是由许多 class 文件组成的; 当我们运行程序时, 需要从一个 class 文件中调用另一个 class 文件, 如果这个 class 文件不存在那么就会引发系统异常从而报错。 我们知道在程序启动的时候,不会一次性将所有的 class 文件都加载到到内存中, 而是根据程序需要使用 ClassLoader 动态的将所需要的 Class 类加载到内存中, 当这个类被加载到内存中后才能被其他的类调用。 所以 ClassLoader 就是用来动态将 class 类文件加载到内存中的。一句话总结就是它可以将类加载到虚拟中时检查类的完整性
Java 默认提供三个 ClassLoader
1、BootStrap ClassLoader
引导类加载器 ( 启动类加载器 ): 是用本地代码实现的类装入器, 用于加载 Java 中的核心类库, 是类加载器层次中最顶层的加载器, 负责将 <Java_Runtime_Home>/lib 下面的核心类库或 -Xbootclasspath 选项指定的 jar 包加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类的加载器的引用,所以不允许直接通过引用进行操作。
public static void main(String[] args){
URL[] url=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i=0;i<url.length;i++){
System.out.println(url[i].toExternalForm());
}
}
下面就程序运行结果对 BootStrap 加载的类进行介绍
- resources.jar
- rt.jar
- sunrsasign.jar
- jsse.jar
- jce.jar
- charsets.jar
- jfr.jar
- classes
2、Extension ClassLoader
扩展类加载器: 负责加载 Java 扩展类库, 它负责将 <Java_Runtime_Home>\lib\ext 或者由系统变量 -Djava.ext.dir 指定位置中的类库加载到内存中, 开发者可以直接使用标准扩展类加载器
3、App ClassLoader
系统类加载器:负责加载应用程序 classpath 目录下的所有 jar 和 class 文件
ClassLoader 加载类的原理
- 原理介绍
ClassLoader 使用的时双亲委托模型来搜索类的, 每个 ClassLoader 都有一个父类加载器的引用( 包含关系 ),虽然虚拟机内置的 BootStrap ClassLoader 本身没有父类加载器, 但是它可以作为父类加载器作用于其他 CLassLoader 的实例。 当一个类加载器实例需要加载一个类时,它会先把这个任务委托给他的父类加载器执行, 这个过程是由上到下一次检查的,这个任务会一直传到我们的顶层类加载器 Bootstrap ClassLoader ,它会先试图加载,如果没有加载到,就把任务回传给 Extension ClassLoader; 如果 Extension 也没加载到,会传给 App ClassLoader ;如果它也没有加载到, 就返回给委托的发起者,由它到指定的问价系统或网络等 URL 中加载该类。如果它们都没有加载到这个类, 就会抛出一个 ClassNotFoundException 异常。否则会将找到的这个类生成一个类的定义,并将它加载到内存中, 最后返回这个类在内存中的 Class 实例对象。
使用双亲委托模型的原因
首先这样可以避免重复加载, 当父类已经加载了该类的时候, 子类没有必要再加载一次;其次,考虑到安全因素,如果不是这种模型,那我们就可以随时使用自定义的 String 来动态代替 Java 核心 api 中定义的类型,这样会存在安全隐患, 而双亲委托的方式,就可以避免这种情况, 因为 String 已经在启动的时候就被引导类加载器(BootStrap ClassLoader)加载了,所以用户自定义的 ClassLoader 永远无法加载一个自己写的 String,除非重写 JDK 中的ClassLoader 搜索类的算法。
怎样判定两个 class 是否相同, 在 JVM 搜索类的时候?
JVM 在判定两个 class 是否相同时, 不仅需要判断两个类名是否相同,而且还要判断这个类是否是由同一个类加载器实例加载的。 只有在两者同时满足的情况下, JVM 才认为这两个 class 是相同的