目录
一:Java 的类加载过程
在说类加载器之前,先说一下 Java 的类加载过程,既然是做 Java 的牛逼程序员,总不能面试的时候就说直接 java -jar
运行 jar 包
或者 war 包
不就完了,面试嘛 。。。咱们肯定要高大上一点,开始下面的装逼模板(别墨迹,直接背就行):
- 执行 java 命令
- 调用脚本文件创建 Java 虚拟机(C++实现)
- 创建引导类加载器(C++实现)
- C++ 通过 JNI 的方式调用 Java 代码(sun.misc.Launcher.getLauncher() ),创建 JVM 启动实例(Launcher的实例)
- 获取自己的类加载器(AppClassLoader 的实例):launcher.getClassLoader()
- 调用 loadClass 加载要运行的类 :classLoader.loadClass(“com.havemail.demo.XXX”)
- C++ 发起调用,执行 main 方法
- Java 程序运行结束,JVM 销毁
loadClass
的类加载过程主要分为:加载 -> 验证 -> 准备 -> 解析 -> 初始化
-> 使用 -> 卸载
。其中验证、准备与解析
也可以称为连接
过程。
加载
:加载字节码文件(class 文件),用到哪些类就加载哪些 class 文件;在加载过程中会在内存中生成每个类的java.lang.Class 对象,作为方法区这个类的各种数据的入口。验证
:验证字节码文件的正确性,安全性等。准备
:给类的静态变量分配内存,并赋予默认值,这些变量所使用的内存都将在方法区中进行分配 。解析
:将常量池内的符号引用(比如 main 方法)替换为直接引用(内存的指针或句柄等)的过程,该过程为静态链接过程。初始化
:执行静态代码块,对类的静态变量初始化为指定的值;将默认值换成声明类变量时指定的初始值。
二:Java 类加载器
Java 主要提供了下面几种类加载器(3个默认1个自定义):
1. 引导类加载器(BootStrap ClassLoader)
- 负责加载
%JAVA_HOME%/jre/lib
目录下的核心类库,比如rt.jar
、charsets.jar
等; - 负责加载
%JAVA_HOME%/jre/classe
s 中的类。 - 由 JVM 底层的
C/C++
实现,Java 代码访问不到该加载器。 - 加载
-Xbootclasspath
参数指定的路径。
可以通过代码查看该类加载器加载了哪些 jar 包:
public class Main {
public static void main(String[] args) {
URL[] url = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < url.length; i++) {
System.out.println(url[i].toExternalForm());
}
}
}
输出结果如下所示:
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre/classes
2. 扩展类加载器(Ext ClassLoader)
全称:Extensions ClassLoader
- 加载 Java 的扩展列库,默认加载
%JAVA_HOME%/jre/lib/ext
扩展目录中的jar 文件
以及由java.ext.dirs
系统变量指定的路径下的jar 文件
。 Ext ClassLoader
是App ClassLoader
的父加载器(并不是说的父类)。
扩展类加载器加载的路径可以通过代码查看:
public class Main {
public static void main(String[] args) {
System.out.println(System.getProperty("java.ext.dirs"));
}
}
输出如下:
/Users/roc/Library/Java/Extensions:
/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre/lib/ext:
/Library/Java/Extensions:/Network/Library/Java/Extensions:
/System/Library/Java/Extensions:/usr/lib/java
3. 应用程序类加载器(App ClassLoader)
【注】因为通过 ClassLoader.getSystemClassLoader(
获得的是 App ClassLoader
,也可称为 系统类加载器
。
- 负责加载
ClassPath
路径下的类包与文件,主要是加载开发人员编写的类文件。 Java 程序默认的类加载器
。- 加载
-classpath
指定的类包与文件。 - 如果
没有特别指定
,则用户自定义的任何类加载器都会将该加载器作为它的父加载器
。
通过代码可以获取 ClassPath
的加载路径:
public class Main {
public static void main(String[] args) {
System.out.println(System.getProperty("java.class.path"));
}
}
输出的结果有点多,仅放关键的部分:
....
/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/lib/sa-jdi.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/lib/tools.jar:
/Users/roc/Downloads/HelloJava/out/production/HelloJava:
/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar
【注】在 IDEA 中如果是纯 Java 项目则为 out
目录,J2EE 项目则为 target
目录。
4. 自定义类加载器(Custom ClassLoader)
- 加载用户自定义路径下的类包。可以通过继承自 java.lang.ClassLoader 类的方式来实现,以满足特定的场景。
5. 类加载器初始化过程
先看源码再说:
public class Launcher {
// ①
private static Launcher launcher = new Launcher();
private ClassLoader loader;
// ...
// ④
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
Launcher.ExtClassLoader var1;
try {
// ②
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// ③
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// ...
}
public ClassLoader getClassLoader() {
return this.loader;
}
// ...
}
具体过程如下:
① 当系统启动的时候会创建一个
Launcher
的静态实例(采用了单例模式,保证只有一个实例);
②Launcher
的构造方法会创建一个ExtClassLoader
的实例,
③ 然后将ExtClassLoader
的实例传入到AppClassLoader
作为其父加载器并返回AppClassLoader
实例,然后赋值给loader
④ 然后C++
通过 JNI 调用Launcher
类中的getLauncher()
方法获取到实例。
【注】JVM 默认使用 getClassLoader()
方法返回的类加载器(AppCLassLoader
的实例)来加载我们的应用,所以 AppClassLoader
是 Java
的默认类加载器。
三:类加载器加载机制 – 双亲委派机制
上面说的仅仅是类加载器的初始化流程,接下来就是类加载器的加载机制
(也就是当虚拟机需要加载某个类了,这个类如何加载到 JVM 中的过程),Java 的类加载机制使用的是 – 双亲委派机制
,那么什么是双亲委派机制
呢?举个例子:
【注】
- 类加载器加载类的时候,并不会将所有的类完全加载,在程序启动的时候仅仅加载用到的类。
- 加载器加载返回的内容是:在内存中生成的这个类的
java.lang.Class
对象
- 我们自己写了一个 Demo 类,然后运行的时候用到了这个类。
- JVM 想要加载 Demo 类,先让应用程序类加载器加载,应用程序类加载器从
已加载
的类中查找,如果有则直接返回;没有的话,然后应用程序类会委托给扩展类加载器来加载(委托
);扩展类加载器
收到后,从其已加载
的类中查找,如果有则直接返回;没有的话,然后会委托给启动类加载器来加载(委托
);启动类加载器
收到后,从其已加载
的类中查找 Demo 类,找到了直接返回;找不到就从自己的加载路径中加载 Demo 类,找到了直接返回,找不到则将请求派给扩展类加载器(派遣
);扩展类加载器
收到后,就从自己的加载路径中加载 Demo 类,找到了直接返回,找不到则将请求派给应用类加载器(派遣
);应用类加载器
收到后,则从自己的加载路径中加载 Demo 类,找到了直接返回,如果找不到则会抛出异常:ClassNotFound
。
这里是拿App ClassLoader
、Ext ClassLoader
与 Bootstrap ClassLoader
举例,如果有自定义加载器的话也是一样的道理,默认 AppClassLoader
是 自定义加载器
的父加载器
,他们四者的层级关系入下图所示:
【注】简单一句话总结:双亲委派机制就是先找父亲加载,不行再由儿子自己加载;引导类加载器没有上一级,只能是加载。
四:常见面试题
1. 为什么要使用双亲委派机制
避免类的重复加载
:当父亲已经加载了该类时,就没有必要让子 ClassLoader 再次加载一边,保证类的唯一性。沙箱安全机制
:防止开发人员恶意重写 JDK 原有的核心类,从而导致程序有很大的安全问题。
个人博客: Roc‘s Blog