周日的上午,笔者像往常一样盯着电脑发呆,想起昨晚的梦中,一个身着西装的面试官问道:你知道new一个对象时,Java虚拟机是怎么工作的吗?
我:
没办法,那今天就复习一下好了。
类的生命周期分为7个阶段:加载、验证、准备、解析、初始化、使用、卸载。
下图是笔者自定义的类。
class man {
String s;
public man(String string){
s = string;
}
}
public class Solution{
public void test(){
man a = new man("abc");
return;
}
}
利用jclasslib工具可以看到test方法的字节码文件。
0 new #7 <test/man>
3 dup
4 ldc #9 <abc>
6 invokespecial #11 <test/man.<init>>
9 astore_1
10 return
下面结合字节码文件对该类的生命周期进行分析。
加载:加载阶段发生在第一步之前,主要作用有:
-
将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:
-
_java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
-
_super 即父类
-
_fields 即成员变量
-
_methods 即方法
-
_constants 即常量池
-
_class_loader 即类加载器
-
_vtable 虚方法表
-
_itable 接口方法表
-
-
在堆中生成一个代表这个类的java.lang.Class对象(比如样例中即为man.Class对象),作为方法区这个类的各种数据的访问入口。
验证:验证阶段同样发生在第一步之前,主要作用为验证类是否符合 JVM规范,安全性检查。(注意:验证阶段往往和加载阶段同时进行)
准备:当一个类验证通过时,虚拟机就会进入准备阶段。在这个阶段,虚拟机就会为这个类分配相应的内存空间,并设置初始化值。
解析:将常量池中的类、接口、字段、方法等符号引用转为直接引用,比如类中定义了一个方法,该方法一开始作为符号引用存储于常量池中,解析后可在常量池中直接定位到该方法在内存中的真实地址。(注意:《Java虚拟机规范》并未规定解析阶段发生的具体时间,只要求在invokespecial等17个用于操作符号引用的字节码指令之前,对它们使用的符号引用进行解析,因此,解析有时发生在初始化阶段之后)
注意:验证、准备、解析三个阶段统称为链接阶段,其中验证阶段往往与加载阶段同时进行。
之后正式进入初始化阶段,并开始执行字节码文件中的内容。
初始化:是类加载过程的最后一个步骤,直到初始化阶段,Java虚拟机才真正开始执行类中编写的程序代码。
下面从字节码文件入手,分析整个过程。
首先是
new #7 <test/man>
在该线程的虚拟机栈的操作数栈压入了指向之前分配的man的实例的内存的地址供任何下面的操作来调用。
dup
复制操作数栈的栈顶值,并将其压入栈顶,也就是说此时操作数栈上有连续相同的两个对象地址。
ldc #9 <abc>
将String型常量值"abc"从常量池中推送至栈顶。
//在该条字节码指令之前解析常量池中的#11即可
invokespecial #11 <test/man.<init>>
调用类的构造方法。(注意:这里将之前压入操作数栈的"abc"取出来作为实参传入了类的构造方法,接着从操作数栈顶弹出一个实例对象的引用,即将对象创建在之前入栈的对象地址上)
astore_1
从操作数栈顶取出 man 对象的引用并存到局部变量表。
return
最后由return指令结束方法。
以上便是全部的类加载过程,如有纰漏,欢迎指正。
转载:https://blog.csdn.net/qq_44998067/article/details/115590869