1.给IDE安装ASM Bytecode Outline插件。
2.安装好之后,重启IDE。
接着选择对应的java文件,右键 --> Show Bytecode outline,可以看到ASM的输出,既可以查看Class文件
测试对象实例化的过程
public class Customer {
int id = 1001;
String name;
Account acct;
{
name = "匿名客户";
}
public Customer(){
acct = new Account();
}
}
class Account{
}
编译后的源码:
1、判断对象是否进行过加载(加载类元信息)
虚拟机遇到new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且校验这个符号引用代表的类是否已经被加载、解析和初始化。(即判断类元信息是否存在)。如果没有,那么在双亲委派模型下,使用类加载器以ClassLoader+包名+类名为key进行查找.class文件。如果没有找到文件,则抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的Class类对象。
2、为对象分配内存
首先计算对象占用空间大小,接着堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小。
3、处理并发安全问题
因为堆是线程共享空间,在堆分配对象空间肯定会有线程安全问题。可以采用CAS失败重试,或者每个线程分配一块TLAB.
4、属性的默认初始化(零值初始化)
5、设置对象头
对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例 数据(Instance Data)和对齐填充(Padding)
虚拟机对象的对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如哈 希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
对象头的另外一部分是类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针 来确定该对象是哪个类的实例。
6、执行init方法进行初始化
包括属性显示初始化、代码块中的初始化、构造器中的初始化。
备注一:
- new:指令检验Object是否加载过,在堆内堆上分配了内存并向操作数栈压入了指向这段内存的引用
- dup:指令备份了一份操作数栈顶指向这段内存的引用
目的:那么操作数栈顶就有两个,再后是调用invokespecial 指令进行初始化,此时会消耗一个引用作为传给构造器的“this”参数,那么还剩下一个引用,会被astore_1指令存储到局部变量表中。 - invokespecial:调用初始化方法
- astore_1:将obj存储到局部变量表 1的位置
什么是引用?
1.如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。引用不等同于对象本身,根据虚拟机种类的不同,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置。
2.如果局部变量是一个reference类型,它引用的对象在Java堆中可被各个
线程共享,但是reference本身在Java栈的局部变量表中是线程私有的。