类加载器和类加载过程
本篇主要内容:
- JVM 工作结构简图。
- 类加载过程。
- 类加载器(ClassLoader)。
- 自定义类加载器实现步骤。
JVM 工作结构简图
类加载过程
上图中我们看到了字节码文件经过类加载子系统加载到 JVM 中,那么现在我们在看类加载子系统中发生了什么。
先放一张类加载过程的简图。
其中包括了三个大步骤,他们分别是:
加载
加载指的是将类的 class 文件读入到内存,并为之创建一个 java.lang.Class 对象。
- 通过类的全限定名获取此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个 Class 对象,作为方法区这个类的各种数据的访问入口。
加载通常由类加载器完成,加载类的方式具体有以下几项:
- 本地资源加载。
- 网络加载。Web Applet。
- zip 压缩包加载。jar,war。
- 运行时计算生成。动态代理技术。
- 其他文件生成。JSP 应用。
- 从加密文件中读取,主要是为了防止 class 文件被反编译。
链接
链接分为三个步骤:
- 验证
确保 class 文件的字节流中包含信息符合虚拟机要求。
主要包括,文件格式验证,元数据验证,字节码验证,符号引用验证。 - 准备
为类变量分配内存,并为变量赋零值。
这里不包括用 final 修饰的 static,因为 final 在变异的时候就会分配了,准备阶段会显式初始化。
不会为实例变量分配初始化。 - 解析
将常量池内的符号引用变为直接引用。
初始化
- 初始化阶段就是执行类构造器方法 <clinit>() 方法。
- <clinit>() 方法是由 javac 将类变量赋值动作和静态代码块中的语句合并自动产生的。当类不存在类变量和静态代码块时不会自动生成此方法。
- 一个类只会被加载一次,<clinint》() 方法是同步加锁的。( 后付测试代码)
注:如果想看此方法,可以使用 JClassLib 软件或 IDEA 中的 JClassLib 插件。
附一张 clinit<>() 方法图和同步测试代码。
package cn.lele;
public class ClinitTest02 {public static void main(String[] args) {Runnable r = ()->{System.out.println(Thread.currentThread().getName() + "start");
DeadThread deadThread = new DeadThread();
System.out.println(Thread.currentThread().getName() + "finish");
};
Thread a = new Thread(r,"A");
Thread b = new Thread(r,"B");
a.start();
b.start();}
}
class DeadThread {
static {if (true) {System.out.println(Thread.currentThread().getName() + "初始化当前类");
while (true) {}}
}
}
图中我们看到在 number 声明之前我们就可以在同步代码块中为他赋值,这是因为在链接的准备阶段,number 变量已经分配了内存空间并且赋予了零值。
类加载器(ClassLoader)
类加载过程的第一步加载,是需要类加载器来完成的,Java 为我们提供了三个类加载器来完成这一步。
引导类加载器
- 该引导器使用 C /C++ 实现。
- 该引导器负责加载 JAVA 的核心类库。
- 加载扩展类加载器和应用程序加载器,并制定为他们的父加载器。
- 出于安全考虑,只会加载包名为 java,javax,sun 等开头的类。
扩展类加载器
- sun.misc.Launcher$ExtClassLoader 实现
- 从 JDK 安装目录的 jre/lib/ext 子目录下加载类库。
- 父类加载器为 null,因为正常情况下我们是获取不到引导类加载器的。
应用程序类加载器
- 也是 sun.misc.Launcher 类中通过内部类的方式实现。
- 负责 classpath 指定路径下的类加载,默认使用此加载器。
注:
1.JVM 规范中提到,JVM 只支持引导类加载器和自定义类加载器,规范将所有派生于抽象类 ClassLoader 的类加载器划分为自定义类加载器。2. 引导类加载器 -> 扩展类加载器 -> 应用程序类加载器为上下级关系,并非继承关系。
自定义类加载器
为什么要自定义类加载器。
- 隔离加载类。
- 修改类加载的方式。
- 扩展加载源。
- 防止源码泄露。
怎么自定义类加载器。
- 继承 ClassLoader 类,将自定义的逻辑写在 findClass() 方法中。
- 如果没有特别复杂的需求,可直接继承 URLClassLoader 类。