JVM的类加载机制
一、类的生命周期
对于一个类来说,它的生命周期是这样的:
当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化三个步骤对该类进行初始化。所以将这三个步骤称为类加载。
其中连接又分为三步:验证、准备、解析。
下面我们来仔细了解一下类加载的过程:
二、类加载的过程
1.加载
- 加载是将类的class文件读入到内存,并将这些静态数据转换成方法区中的运行时数据结构,并在堆中生成一个代表这个类的
java.lang.Class
对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。
2.连接
当类被加载之后,系统会为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中(意思就是将java类的二进制代码合并到JVM的运行状态中)。
类连接又可分为以下三个阶段:
- 验证:确保加载的类信息是否符合JVM规范,有没有安全方面的问题。主要验证是否符合Class文件格式规范,并且是否能被当前的虚拟机加载处理。
- 准备:正式为类中定义的变量(静态变量)分配内存并设置类变量默认初始值阶段,这些内存都将再方法区中进行分配。
- 解析:虚拟机常量池的符号引用替换成直接引用的过程,也就是初始化变量的过程。 符号引用:是一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。 直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄,该引用是和内存中的布局有关的,并且一定加载进来的。
3.初始化
初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实不然。 比如这样的一句代码:private static int a=10;
,在准备阶段给a赋值是int类型的默认初始值0,到初始化这一阶段才会把a真正的值10赋给它。
三、类加载器的介绍
类加载器就是在类加载阶段实现“通过一个类的全限定名(包名+类名)来获取类的二进制字节流”这个动作的。
3.1 启动类加载器(根类加载器/引导类加载器)(Bootstrap ClassLoader)
它用来加载Java的核心类,是用原生代码来实现的,并不继承自java.lang.ClassLoader
.由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。负责加载扩展类加载器和系统类加载器,并为他们的指定父类加载器。
3.2 扩展类加载器
由Java语言编写的,派生于ClassLoader类,上层类加载器为启动类加载器。它负责加载JRE/lib/ext目录下的类。
3.3 系统类加载器
Java语言编写,派生于ClassLoader类,上层类加载器为扩展类加载器。负责加载我们自己定义的类。
四、双亲委派模型
工作原理:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行,如果费雷加载器还存在其父类加载器,则进一步向上委托,一次递归,请求最终将到达顶层的启动类加载器。如果父类加载器可以完成类加载任务,就成功返回,如果父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
也就是每个儿子都很懒,每次有活交给父亲去干,直到父亲说这件事我也干不了的时候,儿子才自己想办法。
4.1 双亲委派模型的优点
- 避免类的重复加载:比如A类和B类都有一个父类C类,那么A启动时就会将C类加载起来,那么B类进行加载的时候就不需要重复加载C类了。
- 安全性:使用双亲委派模型可以保证Java核心API不被篡改。假设通过网络传递一个名为
java.lang.Integer
的类,通过双亲委派莫辛纳甘传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已经被加载了,并不会重新加载网络传递过来的java.lang.Integer
类,而是直接返回已加载过的Integer.class
,这样便可以防止核心API库被随便篡改。
4.2 双亲委派模型的破坏
双亲委派模型的弊端:不能向下委派,不能不委派。
那么我们要打破双亲委派模型:也就是能向下委派和不委派。
向下委派:SPI机制
SPI机制
SPI机制是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里定义的类。这一机制为很多框架扩展提供了可能,比如在JDBC中就使用到了SPI机制。
SPI机制如何打破双亲委派模型:
在某些情况下父类加载器需要委托子类加载器去加载class文件。受加载范围的限制,父类加载器无法加载到需要的文件。
以Drive接口为例,DriverManger通过启动类加载器加载进来,而com.mysql.jdbc.Driver是通过系统类加载器加载进来的。由于双亲委派模型父类加载器是拿不到通过子加载器加载的类的。这个时候就需要启动类加载器来委托子类加载器来加载Driver实现,从而破坏了双亲委派模型。