JVM ClassLoader加载过程
JVM类加载阶段
-
Loading
-
Linking
-
Initialization
类加载器子系统的作用
- 类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。
- ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定
- 加载的类信息存放与一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息时Class文件中常量池部分的内存映射)
类加载器ClassLoader角色
- class file存在与本地磁盘上,可以理解为设计师在纸上的模板,而最终这个模板在执行的时候是要加载到JVM中来根据这个文件实例化处n个一模一样的实例。
- class file加载到JVM中,被称为DNA元数据模板,就是方法区
- 在.class文件-> JVM -> 最终称为元数据模板,此过程就要一个运输工具,扮演一个快递员的角色
Loading
- 通过一个类的全限定名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
Linking
验证(Verify):
- 目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,确保被加载类的正确性,不会危害虚拟机自身安全
- 主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
准备(Prepare):
- 为类变量分配内存并且设置该类变量的默认初始值,即零值。
- 这里不包括final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化。
- 不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量时会随着对象一起分配到Java堆中
解析(Resolve):
- 将常量池内的符号引用转换为直接引用的过程。
- 解析操作往往伴随这JVM在执行完初始化之后再执行
- 符号引用就是一组符号来描述所引用的目标。符号引用的字面量明确定义再《java虚拟机规范》的Class文件格式中。直接引用就是直接指向目标的指针,相对偏移量或一个间接定位到目标的句柄。
- 解析动作主要针对类或接口,字段,类方法,接口方法,方法类型等。对应常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
Initialization
- 初始化阶段就是执行类构造器方法()的过程
- 此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并出来。
- 构造器方法中指令按语句再源文件中出现的执行顺序。
- ()不同与类的构造器。(关联:构造器是虚拟机视角下的())
- 若该类具有父类:JVM会保证子类的()执行前,父类的() 已经执行完毕。
- 虚拟机必须保证一个类的()方法再多线程下被同步加锁。
public class Main{
private static int num = 1;
public static void main(String[] args){
System.out.println(ClassInitTest.num);
}
}
public class ClassInitTest{
private static int num = 1;
static{
num = 2;
number = 20;
}
private static int number = 10;//Linking中prepare中:number = 0-->initial 10->20
public static void main(String[] args){
System.out.println(ClassInitTest.num);
}
}
非法前向引用
public class ClinitTest{
private int a = 1;
//private static int c = 3;
public static void main(String[] args){
int b = 2;
}
}
上述代码编译后没有()方法。
public class ClinitTest{
private int a = 1;
//private static int c = 3;
public static void main(String[] args){
int b = 2;
}
public ClinitTest(){
a = 10;
int d = 20;
}
}
public class ClinitTest1{
//子类再加载之前先保障父类加载
static class Father{
public static int A = 1;
static{
A = 2;
}
}
static class Son extends Father{
public static int B = A;
}
public static void main(String args){
System.out.println(Son.B);//2
}
}