综述
加载->验证->准备->解析->初始化->使用->卸载
1.加载(主要包括三件事)
-
根据Class全限定名==获取二进制文件==,来源可以是Class文件、jar包、网络(Applet)、有其他文件生成(.JSP)
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,(我对静态存储结构的理解:类中的静态变量、成员变量、方法) ==疑点==:运行时数据结构是什么 ==是Class吗==?(变量什么的怎么存的,方法表是什么)
-
在java heap中新建java.lang.Class,作为访问方法区运行时数据结构的入口
加载阶段 类加载器:
2.验证
1.验证是否符合JVM规范(本章内容不细究)
3.准备
- 为类变量赋初值
4.解析
1.将常量池中的符号引用转化为直接引用的过程(
人话:符号引用存在于constant_pool 主要包括
- 1)类和接口的全限定名
- 2)字段的名称和描述符
- 3)方法的名称和描述符
- -----直接引用是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄,有直接引用说明目标已存在于内存中)解析和初始化时机不确定,虚拟机会根据需要来判断,到底是在类被加载器加载时就对常量池中的符号引用进行解析(初始化之前),还是等到一个符号引用将要被使用前才去解析它(初始化之后)
解析主要针对类和接口、字段、类方法、接口方法进行,分别对应constant_pool中的
CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info四种常量类型。
2.对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束
class Super{
public static int m = 11;
static{
System.out.println("执行了super类静态语句块");
}
}
class Father extends Super{
public static int m = 33;
static{
System.out.println("执行了父类静态语句块");
}
}
class Child extends Father{
static{
System.out.println("执行了子类静态语句块");
}
}
public class StaticTest{
public static void main(String[] args){
System.out.println(Child.m);
}
}
执行结果如下:
执行了super类静态语句块
执行了父类静态语句块
33
如果注释掉Father类中对m定义的那一行,则输出结果如下:
执行了super类静态语句块
11
- 具体类结构文件可以看这个老哥写的【深入Java虚拟机】之二:Class类文件结构
5.初始化
- <clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的;
- 如果一个类中没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。
- 接口中不能使用静态语句块,但仍然有类变量(final static)初始化的赋值操作,因此接口与类一样会生成<clinit>()方法。但是接口与类不同的是:执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,那就可能造成多个线程阻塞,在实际应用中这种阻塞往往是很隐蔽的。
网友评论