目录
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
Java语言有
运行时动态加载和连接
的特性。在Java语言中类的加载,连接,初始化等动作都是在Java程序运行期间动态完成的,例如在面向接口程序中,可以在运行期再指定其实现子类。我们先简单看下Java虚拟机类加载的步骤:
虚拟机从Class文件加载一个类主要分为:加载(Loading),连接(Linking),初始化(Initialzation)三个步骤,其中连接阶段分为:验证(Verfication),准备(Preparation),解析(Resolution)三个步骤。
1.什么时候去加载一个类
java虚拟机规法并没规定什么时候去加载一个类,但是规定在一下5中情况下必须初始化一个类(当然初始化之前一定加载了)。
1).遇到new getstatic,putstatic,invokestatic指令时,如果没有初始化,则首先触发初始化。也就是代码中的三种场景:new关键字实例化对象,读取或设置静态对象,调用静态方法。
2).使用java.lang.reflect中的类反射调用一个类的时候,需要先初始化。
3).初始化一个类的时候,如果发现父类没有初始化,则首先初始化父类。
4).虚拟机启动时,首先会初始化包含main方法的主类。
5).JDK1.7,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句句柄,并且这个方法句柄所有的类没有进行初始化过,则需要先触发其初始化。
注意:
当一个类在初始化时,要求其父类全部都已经初始化过了。但是一个接口初始化时,并不要求其父接口全部初始化过了。只有在真正使用父接口的时候,才会初始化。
2.类的加载过程
2.1 加载
在加载阶段,虚拟机要完成以下三件事:
1).通过一个来的全限定名来获取定义此类的二进制字节流。(这里并没说有说明是从Class文件中获取,虚拟机设计团队搭建了一个很开放的平台,例如:从Zip包,网络,运行时生成,其他文件生成或者数据库读取等。)
2).将字节流的静态数据结构转换成方法区运行时数据结构。
3).在内存中生成java.lang.Class对象,作为方法区这个类的各种数据的访问接口。(对于HotSpot虚拟机而言,这个对象就存储在方法区中)
加载阶段和连接阶段的部分内容是交叉进行的,对于一部分字节码加载阶段还未结束,连接阶段可能已经开始了。
2.2 验证
验证阶段的目的是确保字节流信息符合当前的虚拟机的要求,不是危害虚拟机自身的安全。
验证阶段验证的内容包括:文件格式验证,元数据验证,字节流验证,符号引用验证。
2.2.1 文件格式验证
文件格式验证是验证字节流是否符合Class文件规范。
- 是否以魔数0xCAFEBABE开头。
- 主,次版本号是否在当前虚拟机的处理范围之内。
- 常量池中是否有不支持的常量类型。
- 指向常量的各种索引值是否有指向不存在的常量或不符合类型的常量等。
文件格式验证是基于二进制字节流的验证,只有加载进来的二进制字节流合格,才会在方法区中存储。以下的验证都是基于方法区中存储结构进行的。
2.2.2 元数据验证
元数据验证是对字节码描述的信息进行语义分析,保证不存在不符合Java语言规范的数据信息。例如:
- 这个类是否有父类
- 这个类是否继承了不允许被继承的类。
- 如果这个类不是抽象类,是否实现了父类或接口中要求实现的所有方法等。
2.2.3 字节码验证
字节码验证是对字节码描述的信息的语法分析。例如:
- 保证任意时刻操作数栈的数据类型于指令代码都可以配合工作。
- 保证跳转指令不会跳到方法体以外的字节码指令上。
2.2.4 符号引用验证
符号引用验证发生在虚拟机将符号引用转换为直接引用的时候。(符号引用是Class文件在编译时所引用的地址无法知道,只能用符号代替。在字节流加载在虚拟机以后,符号引用可以转换成真正的引用)。符号引用的验证校验的内如比如:
- 符号引用中通过字符串描述的全限定名是否能够找到对应的类。
- 符号引用中的类,字段,方法的访问性是否可被当前类访问。
2.3 准备
准备阶段对类变量分配内存,以及设置初始值。
这里说的类变量是被static修饰的变量,在方法区中分配空间。不是实例变量,实例变量的保存在堆中。还有一点设置初始值,是设置这个类型的初始值,例如int 初始值为0.比如 public sttic int value = 123 在准备阶段设定的初始值是0,而非123。因为这个时候还未执行任何Java方法,把value设置为123的putstatic指令时程序被编译后,存放在类构造器<clinit>()方法之中,所以把value设置为123是在初始化阶段进行的。
2.4 解析
解析阶段是虚拟机把加载进来的常量池中的符号引用转换成直接引用的过程。
2.5 初始化
前面说过在准备阶段,虚拟机已为变量分配了系统要求的初始值。在初始化阶段,才开始真正执行Java代码,也就是设置代码要求的变量的初始值。
初始化阶段也可以说是执行类构造器<clinit>()方法的过程。
总结一下类加载主要操作:
其中有一些操作的细节说的不是很详细,以后再更吧。