介绍
当我们将java文件编译成class文件后,class文件描述了魔数、常量池、字段表、方法表、属性表等二进制数据。而此时就需要类加载的过程,将二进制数据加载到内存中。注意本篇随笔主要关心类加载的时机和阶段。至于如何加载class文件,主要就是类加载器的内容和双亲委派模型的内容了。
类加载阶段
类从被加载到jvm内存开始,到卸载出内存为止,生命周期包括加载、验证、准备、解析、初始化、使用、卸载这7个阶段。其中验证、准备、解析这三个部分称为连接。
2.jpg
解析阶段有时候可能会在初始化阶段之后再开始,这是支持运行时绑定。
加载阶段
这个阶段主要是查找类并且加载类的二进制数据,该阶段是类加载过程中的第一阶段。
虚拟机启动加载:
[Opened F:\Java\jre\lib\rt.jar]
[Loaded java.lang.Object from F:\Java\jre\lib\rt.jar]
[Loaded java.io.Serializable from F:\Java\jre\lib\rt.jar]
[Loaded java.lang.Comparable from F:\Java\jre\lib\rt.jar]
[Loaded java.lang.CharSequence from F:\Java\jre\lib\rt.jar]
[Loaded java.lang.String from F:\Java\jre\lib\rt.jar]
[Loaded java.lang.reflect.GenericDeclaration from F:\Java\jre\lib\rt.jar]
[Loaded java.lang.reflect.Type from F:\Java\jre\lib\rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from F:\Java\jre\lib\rt.jar]
[Loaded java.lang.Class from F:\Java\jre\lib\rt.jar]
。。。
说明:可以看出在jvm启动时,加载了JAVA_HOME下的rt.jar包,可以看出该包中包含了我们常用的lang包、util包等class类。
运行时加载的过程:
- 通过一个类的全限定名来获取该class文件的二进制字节流。
- 将字节流的静态存储结构转化为方法区的运行时数据结构。
- 为上面的class文件,在堆中生成一个java.lang.Class对象对该类的数据访问入口。
加载需要注意的点:
① 首先获取二进制字节流不一定非要从class中获取,可以从网络中获取,也可以在运行期使用动态代理Proxy来生成。
② 非数组类的加载可以使用系统提供的类加载器,也可以使用自定义的类加载器(就是重写loadClass方法)。
验证阶段
这个阶段主要是验证被加载的类是否正确。目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证流程:
1.文件格式验证
该阶段验证就是为了验证字节流是否符合class文件格式规范,该阶段验证的目得是保证二进制字节流可以正确的解析并存储到方法区,只有通过当前这个阶段,才会进入到内存的方法区中存储。
2.元数据验证
对于类的元数据信息进行语义校验,保证符合java语言规范的元数据信息。
3.字节码验证
主要是确定程序语义是否合法,符合逻辑。将对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。
4.符号引用验证
主要是对类自身意外的信息进行匹配校验,其主要作用就是保证解析动作能正常执行。
准备
该阶段就是正式为类变量分配内存并设置类变量的初始值,注意类变量使用的内存在方法区上分配。(类变量是static修饰的变量,不包括实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆上)。
该阶段赋值初始化变量指的是那些没有被final关键字修饰的static变量值。
例:public static int value = 123;
value在经历过准备阶段后值为0,至于赋值为123在初始化阶段进行。
例:public static final int value = 123;
value在经历过准备阶段后值为123。
解析
该阶段就是将jvm中常量池内的符合引用替换为直接引用的过程。
① 符号引用
符号引用使用一组符合描述引用目标,注意符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
② 直接引用
直接引用可以是直接指向目标的指针、相对偏移量、或者是一个能间接定位到目标的句柄。注意如果有了直接引用,那么引用的目标必定已经在内存中存在了。
初始化
初始化阶段做的事就是执行类的初始化方法<clinit>。方法<clinit>是由编译器自动生成的,实际上就是由static变量赋值指定的值和static静态代码块合并产生的。
例子:
public class MyStaticClass {
public static int myId = 1;
public static int number;
static {
number = 2;
}
}
javap -verbose MyStaticClass.class查看:
C:\Users\Administrator\Desktop>javap -verbose MyStaticClass.class
Classfile /C:/Users/Administrator/Desktop/MyStaticClass.class
Last modified 2018-10-3; size 430 bytes
MD5 checksum d79347edadcf21d81b0f606af176d5ec
Compiled from "MyStaticClass.java"
public class spring_enterprise.MyStaticClass
SourceFile: "MyStaticClass.java"
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // spring_enterprise/MyStaticClass
#2 = Utf8 spring_enterprise/MyStaticClass
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 myId
#6 = Utf8 I
#7 = Utf8 number
#8 = Utf8 <clinit>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Fieldref #1.#12 // spring_enterprise/MyStaticClass.m
Id:I
#12 = NameAndType #5:#6 // myId:I
#13 = Fieldref #1.#14 // spring_enterprise/MyStaticClass.n
mber:I
#14 = NameAndType #7:#6 // number:I
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 <init>
#18 = Methodref #3.#19 // java/lang/Object."<init>":()V
#19 = NameAndType #17:#9 // "<init>":()V
#20 = Utf8 this
#21 = Utf8 Lspring_enterprise/MyStaticClass;
#22 = Utf8 SourceFile
#23 = Utf8 MyStaticClass.java
{
public static int myId;
flags: ACC_PUBLIC, ACC_STATIC
public static int number;
flags: ACC_PUBLIC, ACC_STATIC
static {};
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_1
1: putstatic #11 // Field myId:I
4: iconst_2
5: putstatic #13 // Field number:I
8: return
LineNumberTable:
line 5: 0
line 9: 4
line 10: 8
LocalVariableTable:
Start Length Slot Name Signature
public spring_enterprise.MyStaticClass();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #18 // Method java/lang/Object."<init
":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lspring_enterprise/MyStaticClass;
}
生成的<clinit>方法中,对于static赋值语句以及static静态代码块中对myId和number成员变量赋值。其中赋值操作使用putstatic指令完成的。
注意几点:
1. 父类定义的静态代码块优先于子类的静态代码块先执行
public class SuperClass {
public static int number;
static {
number = 4;
System.out.println("SuperClass");
}
}
public class SonClass extends SuperClass {
static {
number = 2;
System.out.println("SonClass");
}
public static void main(String[] args) {
System.out.println(number);
}
}
6.jpg
2. 子类引用父类的静态字段不会被初始化
public class SuperClass {
public static int number;
static {
number = 4;
System.out.println("SuperClass");
}
}
public class SonClass extends SuperClass {
static {
number = 2;
System.out.println("SonClass");
}
}
public class MyTest {
public static void main(String[] args) {
System.out.println(SonClass.number);
}
}
结果:
7.jpg
3. 前面我们提到过final修饰的static变量在准备阶段就在方法区上赋值了,在解析阶段中实际上<clinit>方法什么都不用做。
public class MyFinalClass {
public static final int myId = 2;
public static final int number = 4;
}
{
public static final int myId;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 2
public static final int number;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 4
public spring_enterprise.MyFinalClass();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #14 // Method java/lang/Object."<init>
":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lspring_enterprise/MyFinalClass;
}
说明:没有执行putstatic指令,没有为类的指定的静态域赋值。
网友评论