JVM学习之对象的实例化、内存布局与访问定位

目录

背景

对象实例化

对象实例化的几种方式

字节码角度查看对象创建过程

对象实例化的步骤

对象的内存布局

对象的访问定位


背景

上周跟着做尚硅谷的电商数仓,好悬把我写吐了,JVM学习笔记因此一直没有更新,现在补上..

对象实例化

对象实例化的几种方式

1)、new

2)、Class.newInstance()

3)、Constructor的newInstance()

4)、clone()

5)、反序列化

6)、第三方库Objenesis

字节码角度查看对象创建过程

代码Object o = new Object()对应的字节码如下

         0: new           #4                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: return

字节码中,new操作符加载Object类到方法区并在堆的伊甸园区创建对象,然后进行零值初始化

dup表示复制,把堆中对象复制一份到操作数栈中

invokespecial表示调用自身的方法,也就是Object的构造方法,可以对属性进行显式初始化

astore_1是把操作数栈中的对象引用弹出到局部变量表,下标为1的位置中

对象实例化的步骤

1)、判断对象对应的类是否加载、链接、初始化

JVM遇到一条new指令,首先检查这个指令的参数能否在元空间的常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否被加载、链接、初始化。如果没有,就通过双亲委派机制查找这个类的class文件,如果找不到,就报错ClassNotFount,如果能找到,就进行加载,生成类对象

2)、为对象分配内存

首先计算对象占用空间大小,接着从堆中划分一块内存给新对象。对于对象中的引用类型属性,统一占4个字节

如果内存规整,则进行指针碰撞。也就是把所有用过的内存放在一边,空闲内存放另一边,中间放一个指针作为分界点的指示器。分配内存仅仅把指针向空闲那边移动一段与对象大小相等距离。如果垃圾回收器选择Serial、ParNew这种基于压缩算法的,虚拟机就采用这种分配方式。一般使用带有compact(整理)过程的收集器时,使用指针碰撞

否则,虚拟机需要维护一个列表,并进行空闲列表分配。也就是当已使用的内存和空闲内存相互交错,虚拟机会维护一个空闲列表,记录哪些内存可用,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表内容。CMS回收器用的就是空闲列表法。

3)、处理并发安全问题

使用CAS失败重试、区域加锁来保证更新的原子性。

也可以每个线程预先分配一块TLAB,通过-XX:+/-UseTLAB参数来使能或关闭此功能。

4)、初始化分配到的空间

所有属性设置默认值,保证对象实例字段在不赋值的情况下可以直接使用

5)、设置对象的对象头

将对象的所属类、对象的哈希码、对象的GC信息和锁信息等数据存储到对象的对象头中,具体设置方式由具体的虚拟机来实现

6)、执行init方法进行初始化

从java程序的角度来看,此时初始化才真正开始,初始化属性、执行实例化代码块、调用类的构造方法,并把堆内对象首地址赋值给引用属性

因此一般来说(new指令后面是否跟随一个invokespecial),new指令之后就是按照程序员的意愿来进行实例化对象

对象的内存布局

对象在堆空间的内存布局包括以下三部分:

1)、对象头

包括运行时元数据(哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程id、偏向时间戳)和类型指针(指向类元数据InstanceKlass,确定对象所属类型),如果是数组,还需要记录数组长度

2)、实例数据

这是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段,包括从父类继承下来的字段

存储规则有三:相同宽度的字段总是被分配在一起;父类中定义的变量会出现在子类之前;如果CompactFields(默认为true)为true,子类的窄变量可能插入到父类变量的间隙

3)、填充

不是必须的,只是个占位符

 

内存布局总结如下

 

对象的访问定位

JVM是如何通过栈帧中的对象引用访问到堆中的对象实例呢?

有两种方式:句柄访问,直接指针

使用句柄访问方式时,java堆中有一个句柄池,里面有两个指针,分别指向堆中实例池中的对象实例数据,和方法区的对象类型数据,如下图所示

这种方式占用内存、效率偏低,但当堆中对象位置发生了变化(例如GC时),只需要改变句柄池中实例指针的值,不用改变栈中局部变量表。

 

使用直接指针方法(如HotSpot)时,指向对象类型指针存放在对象实例数据中,指向方法区的对象类型数据

这种方法的优缺点和句柄访问互补


版权声明:本文为qq_37475168原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。