对象在创建时,发生了什么?

434 阅读2分钟
Object o = new Object();

当执行这行代码时,发生了什么?

1、先了解jvm的内存布局

给每个线程分配个虚拟机栈,保存线程的局部变量。每个方法在执行时,会创建一个栈帧结构,压到栈中。栈帧中包含四个内容:局部变量、操作数栈、动态连接、方法返回地址。对象在堆上创建。局部变量是指向堆上对象的指针。

image.png

2、创建对象的过程

(1)先去方法区查询类是否加载(jdk 1.8后没有方法区了,变成了元数据区,放在直接内存中),如果类没有加载,先加载类。当类加载后,类的对象所占用的内存大小就确定了。
(2)然后在堆中分配一块内存,并创建对象头。对象头中包含四个内容:标记字mark words,指向类元数据的指针klass,对象数据,对齐。mark words中记录了gc的信息、锁的信息等等。
(3)给对象的变量赋个初始值。
(4)执行_init()构造方法,初始化对象的变量。
比如对象中有一个属性这么写:private int num = 3;,那么在(3)时,num的初始值是0,等执行了(4)之后,num的值才是3。
(5)栈中的局部变量指向堆中的这块空间。

3、经典的double check问题

在单例模式中经典的问题就是由于创建对象不是原子的,而是分了很多步,由于编译器和操作系统有指令重排序的优化,不能保证多步之间的先后顺序,比如上面的(5)可能在(4)之前执行。
在单例的写法中,为了提高启动速度,会在使用到单例的时候再创建对象(延迟创建),为了保证只会创建出一个对象,一般会使用sycrhonized image.png
这种写法是有问题的,问题在于,当线程A执行new方法,执行过程是分配内存空间->instance变量指向这段内存->初始化对象,当线程A执行完第二步时,线程B来了,线程B发现instance不等于null,就直接把instance拿出去用,但是这时候instance还没有初始化完,显然是有问题的。
解决方法:

image.png
关键点在于instance变量必须用volatile修饰。用volatile修饰后,会在new操作前后添加内存屏障,禁止指令重排序,就能解决上面的问题了。

image.png