你的对象在哪里?长什么样?我带你去看一看
-
new
指令:表示首先在堆中申请一块内存,此时堆中的内存中存储着该对象属性n的半初始化状态值n=0。 -
dup
指令:表示复制引用。 -
invokespecial
指令:表示调用对象的初始化方法,后面对应的注释Method "
"
,此时属性值n才会被初始化为1。 -
astore_1
指令:此时会将TestObj obj =new TestObj()的引用obj 与该堆中的对象建立连接。 -
return
指令:执行完最后返回。
从上面的指令中分析可以看出,当创建一个对象的时候,主要分为以下三个步骤,执行的原理图如下:

了解完对象的半初始化,那么什么又是对象分配?
说到JVM中的对象分配,我们得从对象在JVM中执行new指令后开始讲起。客观且慢,请听我详细道来。
在JVM中当遇到一条new指令时,会首先检查这条指令的参数是否在常量池中能定位到一个类的符号引用,若是定位不到,就表示没有被加载、解析和初始化过,就会先执行加载该类。
JVM中加载类信息的详细过程,请参考这一篇文章[ 面试官:你知道java类是怎么跑起来的吗?问的我一脸懵 ]。
若是存在该符号引用表示之前已经加载过该类信息,接下来就直接执行在堆中进行对象内存的分配。
但是随着JVM的发展, JIT编译器 的出现,所有的对象分配在堆中就不那么绝对了,当创建对象为对象分配内存时,也会尝试在栈上分配,在JVM书籍中的描述如下所示:

那么什么是逃逸技术?
每个线程执行方法都会创建一个栈帧,该栈帧用于存储方法的局部变量,当一个变量不会在其他方法中使用到,只在该方法中使用,就不会逃逸。
什么又是变量替换呢?标量替换就是创建一个对象的时候,直接以对象的属性进行入栈存储,方法结束后直接弹栈结束,不会有GC的介入。
因此,在栈上分配是对JVM的一种优化措施,减少了GC的活动,提高了Java虚拟机的执行效率。
当对象执行在堆上进行内存分配的时候,为了防止多线程分配内存存在混乱的情况,通常在多线程的时候对对象内存的分配 有以下两种方案进行解决:
-
对分配内存的动作进行同步,但是同步的的操作太消耗性能,大大降低了JVM的性能。
-
对堆内存为每一个线程划分一块 本地线程分配缓冲 (
TLAB
),是线程私有的,这样每一个线程只需要在自己的TLAB中进行分配即可,就不用进行同步,也能达到线程安全的目的:
那么当一个对象在堆中分配完一个内存后,对象在堆中又是怎么存在的呢?
客观不急请听我慢慢道来,当对象在堆中进行完内存分配后,一个普通对象在堆中以如下图的形式存在:

markword class pointer instance data padding
那么对象都已经存在堆中了,我们又是怎么访问该对象的?
若要访问堆中已经存在的对象,有以下两种方式:
(1) 句柄的方式:会在堆中划分一块下的内存作为句柄池,对象的引用不会直接存储数据的地址,而是指向句柄池的指针,由句柄池的指针存储数据的地址。
句柄池的方式,由于对象引用不会直接指向数据的地址,这样当GC进行回收垃圾的时候,移动对象,对象的地址改变了就不用改变reference的本身内容。
这个也是句柄访问方式的唯一优点,具体句柄访问方式的原理图如下所示:

(2) 直接方式:直接方式是reference直接指向数据的,这样减少了一次指针的定位,速度快,直接访问的方式原理图如下:

注意:在HostSpot的源码实现中,使用的是第二种直接访问的方式