对象

一 对象的创建

当我们创建对象的时候,可能都知道会在内存堆中开辟一块空间,并且初始化对象,但不清楚对象的创建实际经历了哪些步骤。先来看下图
在这里插入图片描述
对象的创建分为 6 个步骤。

  1. 就是我们熟知的,在代码层面告诉虚拟机我要创建一个对象;
  2. 根据当前要创建的对象去常量池查找是否有符号引用;
  3. 如果没有找到,就要进行类加载;
  4. 类加载完毕,虚拟机就为这个对象分配堆内存;
  5. 接着初始化对象中的数据类型赋上默认值;
  6. 执行构造方法、代码块等方法;

给对象分配内存的方式

1. 指针碰撞
在这里插入图片描述
假设堆内存空间是规整的,然后把堆内存分成两块,左边是没有使用的内存空间,右边是已经使用的内存空间。
当有新的对象进来的时候,图上方的竖线往左移动,就成图下方的样子,画斜线的区域就是新增对象的空间。
这种规整地给对象分配内存的方式叫作指针碰撞

2. 空闲列表
但 Java 堆内存不是规整的,分配的内存空间在堆内存中是无序的。那么就需要有一个列表来维护堆内存中未使用的部分。当分配了一块内存出去之后,列表需要把这个块空间的记录删掉。

到底选择哪种分配方式呢?
选择哪种内存分配方式,是由堆内存是否规整决定的。而 Java 堆是否规整是由垃圾回收策略决定的。
当垃圾回收器带有压缩整理的功能,在进行垃圾回收的时候,会自动地进行压缩整理,把内存区域划分成有规整的结构,就可以使用指针碰撞。若垃圾回收器没有这种功能,则使用空闲列表

线程安全性问题

当并发给对象分配空间的时候,有可能引发线程安全性的问题。
一种方法是线程同步,这种方式会牺牲性能。
另外一种方法是本地线程分配缓冲,这种方式是在堆内存中,为每个线程单独开辟一个空间,这样就可以避免线程间使用共享数据的隐患了。当单独的空间使用完了,需在丢内存中再划分一个空间,但这时候就需要用线程同步的方法来避免线程安全性问题。

二 对象的结构

Header(对象头)

对象头里包括自身运行时数据类型指针

自身运行时数据(Mark Word)
根据虚拟机是 32 位还是 64 位来决定数据的长途 32b/64b
数据包括有哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳

类型指针
虚拟机通过该指针去确认当前对象是属于哪个类的实例,但并不是每个对象都有类型指针,跟对象的访问定位有关。

InstanceData

对象的实例,存储对象的有效信息。
存储顺序受虚拟机分配策略有关,和字段在 Java 源码中的顺序有关。

Padding

对齐填充,不是必然存在的,它的涵义仅仅是占位符,用来填充内存的作用。
因为 Header 是8个字节的整数倍,如果实例部分没有对齐,则需要 Padding 来填充。

三 对象的访问定位

对象的定位分为使用句柄直接指针,具体使用哪种,是有虚拟机来实现的。

使用句柄

栈里面的引用类型先指向堆空间的一块区域,这块区域叫句柄池,句柄池保存了实例对象的地址,所以通过句柄池指向对象实例。

在这里插入图片描述

直接指针

这个好理解,栈里边的引用类型直接指向堆内存对象实例的区域

有什么区别吗?
使用句柄,在堆内存的地址是固定的,所以栈内存这边引用地址是不需要改变的,变的是句柄池里面的记录的地址。
使用直接指针速度快,减少寻址的过程,减少性能的开销,性能比较高。


----补充一下

在这里插入图片描述
运行时创建的对象是存放在堆内存中,但是编译期的时候,类信息是存放在方法区。

所以寻址的时候不单找堆内存,还需要找方法区。
所以就会有两个指针,一个是到对象实例数据的指针,一个是到对象类型数据的指针

假设使用句柄,句柄的值需要包括上面两个指针,才能去找到相应的区域。
假设直接指针,对象实例需要包括上面两个指针,才能去找到相应的区域。

为什么会有到对象类型数据的指针
因为创建对象的时候,需要绑定这个对象实例是在哪个类下面的。(看创建对象第二步)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值