摘自《深入理解Java虚拟机》第二版
虚拟机类加载机制
Java从代码到最终执行,一般需要两个过程:
- 编译
- 运行
编译:把Java文件通过javac命令变异成字节码,也就是.class文件。
运行:把编译好的.class文件交给JVM运行。
这里说的类加载过程指的是JVM把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。
1 类加载过程
类加载过程主要分为三部分:
- 加载
- 连接(具体分为验证、准备、解析)
- 初始化
具体来说就是加载、验证、准备、解析、初始化五个阶段。
1.1 加载
“加载”是“类加载”过程的一个阶段。简单来说加载就是把class字节码文件从各个来源通过类加载器装入内存中。
- 字节码来源:一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译。
- 类加载器:包括启动类加载器,扩展类加载器,应用程序类加载器,以及用户的自定义类加载器。
1.2 验证
保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
- 文件格式的验证:是否以魔数oxCAFEBABE开头、常量中是否有不被支持的常量。
- 元数据的验证:保证其描述信息符合Java语言规范的要求。如:这类是否有父类等。
- 字节码的验证:保证语义是合法的。如:保证类型转换的合理性。
- 符号引用的验证:对类自身以外的信息进行匹配性校验。如:通过全限定名是否能找到对应的类。
1.3 准备
主要是为类变量(静态变量)分配内存,并且赋初值(这里的初值指的是JVM根据变量类型的默认初始值,不是代码中的初始值)。
如:8中基本类型的初始为0,引用类型的初值为null,注意:常量的初值即为代码中设置的值如final static temp= 22, 那么该阶段temp的初值就是22。
1.4 解析
将常量池内的符号引用替换为直接引用的过程。
- 符号引用:即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
- 直接引用:可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量。
例如:调用方法test(),方法地址为2222,test就是符号引用,2222就是直接引用。
1.5 初始化
这个阶段主要是对类变量初始化,是执行类构造器的过程。
换句话说,只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
2 类加载器
启动类加载器(bootstrap class loader):用来加载Java的核心库,用原生代码实现(c++)。
扩展类加载器(extensions class loader):加载Java的扩展库。
应用程序加载器/系统类加载器(system class loader):它根据Java应用的类路径(classpath)来加载类。
自定义类加载器:自己定义的类加载器。
说到类加载器那么就要谈谈类加载器之间的关系,加载器之间的关系称作为双亲委派模型(除了bootstrap ClassLoader外,其他的类加载器都有自己的父类加载器,类加载器都是使用组合关系来复用父类加载器的代码)
双亲委派模型工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是这样的,因此所有的加载器请求最终都应该传送到顶层的启动类加载器,之后当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。