对于每个开发人员来说,java.lang.ClassNotFoundExcetpion这个异常几乎都遇到过,而追求其该异常的来源的话,就免不了谈一谈Java的类加载器了。
本文就基于Java类加载来详解对应的6大执行全流程,让你对Java类加载有更加全面的了解。
类加载器简介
Java程序被编译器编译之后成为字节码文件(.class文件),当程序需要某个类时,虚拟机便会将对应的class文件进行加载,创建出对应的Class对象。而这个将class文件加载到虚拟机内存的过程,便是类加载。
类加载器负责在运行时将Java类动态加载到JVM(Java虚拟机),是JRE(Java运行时环境)的一部分。由于类加载器的存在,JVM无需了解底层文件或文件系统即可运行Java程序。
Java类不会一次全部加载到内存中,而是在应用程序需要时才会加载。此时,类加载器负责将类加载到内存中。
类加载的过程
类的生命周期通常包括:加载、链接、初始化、使用和卸载。上图中包含了类加载的三个阶段:加载阶段、链接阶段和初始化阶段。
如果将这三个阶段再拆分细化包括:加载、验证、准备、解析和初始化。
关于这几个阶段的作用,大致分为如下:
- 加载:通过一个类的完全限定查找类字节码文件,转化为方法区运行时的数据结构,创建一个代表该类的Class对象。
- 验证:确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。
- 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值。不包含被final修饰的static变量,因为它在编译时已经分配了。
- 解析:将常量池内的符号引用转换为直接引用的过程。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载。
- 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。
接下来我们来详细学习Java虚拟机中类加载的全过程,即加载、验证、准备、解析和初始化。
加载
加载是JVM加载的起点,在加载阶段,虚拟机需要完成 3 件事情。
1 通过一个类的全限定名来获取此类的二进制字节流。
2 将这个字节流所代表的静态存储转化为方法区的运行时数据结构。
3 在内存中生成一个代表这个类的 Class 对象。
- 1)通过一个类的全限定名来获取定义此类的二进制字节流。
- 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
加载阶段结束后,Java虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区之中了,方法区中的数据存储格式完全由虚拟机实现自行定义,《Java虚拟机规范》未规定此区域的具体数据结构。
类型数据妥善安置在方法区之后,会在Java堆内存中实例化一个java.lang.Class类的对象, 这个对象将作为程序访问方法区中的类型数据的外部接口。
方法区在JDK不同版本的具体实现就不再详细说了。在JDK1.8中,类型数据存储在元空间中。
验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求。
验证阶段大致上会完成下面四个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证。
- 文件格式验证
第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,需要验证魔数OXCAFEBABE 开头、版本号、常量池常量类型是否支持、指向常量的索引值等等。
- 元数据验证
第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java语言规范》的要求,包括类是否有父类、父类是否继承了final修饰的类、非抽象类是否实现了父类定义的方法、类是否与父类有矛盾等等。
- 字节码验证
第三阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的。
- 符号引用验证
最后一个阶段的校验行为发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。
符号引用验证主要验证类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。
准备
准备阶段是给静态变量分配内存并设置类变量初始值的阶段。
在JDK 7及之前,这些变量的内存在方法区(永久代)中分配,在JDK 8及之后,静态变量则会随着Class对象一起存放在Java堆中。
解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。
- 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
- 直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
初始化
类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。
到了初始化阶段,才真正开始执行类中定义的 Java 代码。
在准备阶段,变量已经赋值过一次系统要求的初始值,而在初始化阶段,则根据程序员自己设置的变量赋值。
以上就是Java类加载的整个6大阶段的解析,希望对你了解Java类加载有所帮助。
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》