一、加载
- 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类。它的重要 field 有:
- _java_mirror : java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
- _super :父类
- _fields :成员变量
- _methods :方法
- _constants :常量池
- _class_loader :类加载器
- _vtable :虚方法表
- _itable :接口方法表
- 如果这个类还有父类没有加载,先加载父类
- 加载和链接可能是交替运行的
注意
- instanceKlass 这样的【元数据】是存储在方法区(1.8 后的元空间内),但 _java_mirror 是存储在堆中
- 可以通过前面介绍的 HSDB 工具查看
二、链接
1)验证
验证类是否符合 JVM规范,安全性检查
-
修改 Verify.java 的魔数:将 cafebabe 修改为 cafebaby
-
解释执行结果:类格式错误
PS F:\software\IDEA\JavaProjects\JvmTest\target\classes\org\example\classLoading\link> java Verify Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 1128351301 in class file Verify at java.lang.ClassLoader.defineClass1(Native Method) ...
2)准备
为 static 变量分配空间,设置默认值
- JDK 7 之前:static 变量 存储于 instanceKlass 末尾
- JDK 7 开始:static 变量 存储于 _java_mirror(类.class对象) 末尾
- 注意:final 类型的 static 变量
- 基本类型常量、字符串常量:编译阶段值就确定了,赋值操作在本阶段完成
- 引用类型常量:赋值操作在 初始化阶段 完成
3)解析
将自己在其他类的常量池中的符号引用解析为直接引用
类A没有解析为直接引用
public class Analysis{
public static void main(String[] args) throws IOException {
//1.访问类A的静态变量(基本类型、字符串)不会导致类A的初始化(解析)
System.out.println(A.a);
System.in.read();
}
}
class A{
final static int a=1;
static int b=1;
static {
System.out.println("init");
}
}
-
运行程序
-
执行命令行命令,查看当前进程id
PS F:\software\IDEA\JavaProjects\JvmTest> jps 13816 Analysis ...
-
进入 jdk 安装目录,执行命令打开 HSDB工具
F:\software\Java\jdk1.8.0_333>java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
-
将 HSDB工具 链接到当前进程
-
找到 Analysis 类的常量池,查看类A的引用
类A解析为直接引用
public class Analysis{
public static void main(String[] args) throws IOException {
//2.访问类变量导致初始化
System.out.println(A.b);
System.in.read();
}
}
class A{
final static int a=1;
static int b=1;
static {
System.out.println("init");
}
}
-
运行程序
-
执行命令行命令,查看当前进程id
PS F:\software\IDEA\JavaProjects\JvmTest> jps 4768 Analysis ...
-
进入 jdk 安装目录,执行命令打开 HSDB工具
F:\software\Java\jdk1.8.0_333>java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
-
将 HSDB工具 链接到当前进程
-
找到 Analysis 类的常量池,查看类A的引用
三、初始化
1)<cinit>()V 方法
初始化即调用 <cinit>()V ,虚拟机会保证 <cinit>()V 的线程安全
2)发生的时机
导致类初始化的情况:
- main 方法所在的类
- new
- 首次访问类的 静态变量、静态方法
- 子类初始化,如果父类还没初始化
- Class.forName(String)
不会导致类初始化的情况 :
- 类加载器的 loadClass 方法 :加载阶段
- Class.forName(String,false) :加载阶段
- 创建该类的数组不会触发初始化 :加载阶段
- 访问 类.class :加载阶段
- 访问类的 静态常量(基本类型和字符串) :链接-准备阶段
验证:导致类初始化的情况
public class Init {
static {
//1.main方法所在类。首先被初始化
System.out.println("1.init:main方法所在类");
}
public static void main(String[] args) throws ClassNotFoundException {
//2.new
A newTest=new A();
//3.子类初始化
//4.访问类的 静态变量、静态方法
int a=B.b;
//5.Class.forName(String)
Class.forName("org.example.classLoading.link.D");
}
}
class A{
static {
System.out.println("2.init:new的类");
}
}
class B extends C{
static int b=1;
static {
System.out.println("4.init:访问类的静态变量、静态方法");
}
}
class C{
static {
System.out.println("3.init:子类初始化");
}
}
class D{
static {
System.out.println("5.init:Class.forName(String)");
}
}
1.init:main方法所在类
2.init:new的类
3.init:子类初始化
4.init:访问类的静态变量、静态方法
5.init:Class.forName(String)
验证:不会导致类初始化的情况
public class Init {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = Init.class.getClassLoader();
//1.类加载器的 loadClass 方法 :加载阶段
classLoader.loadClass("org.example.classLoading.link.A");
//2.Class.forName(String,false) :加载阶段
Class.forName("org.example.classLoading.link.A",false,classLoader);
//3.创建该类的数组不会触发初始化 :加载阶段
A[] arr=new A[2];
//4.访问 类.class :加载阶段
Class<A> clazz = A.class;
//5.访问类的 静态常量(基本类型和字符串) :链接-准备阶段
int a=A.a;
}
}
class A{
static final int a=1;
static {
System.out.println("init A");
}
}
Process finished with exit code 0