[JVM]五 对象在内存中的结构

对象实例化

创建对象的方式:

1)new ,静态方法创建 , 工厂模式的静态方法创建
2)class的newInstance() 反射,只能调用空参的构造器,权限必须是public (过时)
3)Constructor的newInstance() 反射,可带参,无权限要求
4)使用clone() 实现Cloneable接口 浅复制
5)反序列化,从文件或网络中创建对象
6)第三方库Objenesis

对象创建的步骤

1)判断对象对那个的类是否加载,链接,初始化

虚拟机首先检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化(即判断类的元信息是否存在)。如果没有就根据双亲委派机制使用当前ClassLoader包名类名进行查找当前的.class文件,如果没有找到文件则抛出ClassNotFoundException异常。如果找到则进行类的加载,并生成对应的class类对象

2)为对象分配内存

首先计算对象占用空间(此时对象所占空间大小已经明确),接着在堆空间划分一块内存,如果实例成员变量是引用变量,进分批引用空间即可,即4个字节大小

①如果内存规整,则虚拟机采用指针碰撞法来为对象分配内存,意思是所有用过的内存存在一边,空闲的内存存在另一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅把指针向空闲那边移动一段与对象大小相等的距离

②如果内存不规整,(可使用内存是碎片化的),此时 虚拟机需要维护一个空闲列表,列表记录了内存块那些是可用的,在分配的时候从列表中找到一块足够的空间划分给对象实例,并更新列表的内容。

选择那种分配是由java堆是否规整决定,而java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定

3)处理并发安全问题,因为堆空间绝大部分是共享,多个线程new对象就会出现线程安全问题,采用CAS失败重试,区域加锁保证更新的原子性。

或每个线程在Eden区域预先分配一块TLAB 使用参数-XX:±UseTLAB参数设置 JDK8默认开启

4)属性默认初始化,所有属性设置默认值,保证对象实例字段在不赋值时可以使用

5) 设置对象的对象头,(即类的元数据信息) , 对象的HashCode和对象的GC信息,锁信息等数据存储在对象头中, 由jvm实现

6) init方法初始化,初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。

内存布局

对象头

运行时元数据

哈希值
GC年龄分代
锁状态标志
线程持有的锁
偏向线程ID
偏向时间戳

类型指针

类型指针:指向方法区对象所属的具体类型

如果对象是数组 还要记录数组的长度

实例数据

实例数据包括定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)

规则:
父类中定义的变量出现在子类之前
同级别相同宽度的字段分配到一起
如果CompactFields参数为true 子类的窄变量可能插入到父类的空隙

对齐填充

占位符

对象的访问定位

即通过引用找到对象的过程

对象的访问定位是通过栈上引用 reference访问

对象访问的两种方式方式:

句柄访问

[JVM]五 对象在内存中的结构_第1张图片
优缺点

缺点:空间消耗,从引用到句柄再到对象实例 效率低

优点:对象引用地址稳定,如果堆空间中对象发生引用,只需要维护句柄池地址

直接指针

[JVM]五 对象在内存中的结构_第2张图片

直接指针是hostpot虚拟机采用的方式

优缺点

优点:节省空间,从引用直接找到对象实例数据 效率高

缺点:当对象地址变化时,栈空间地址需要做出修改

$.图:运行时数据区与对象在内存中的结构

[JVM]五 对象在内存中的结构_第3张图片

你可能感兴趣的:(JVM)