参考:
对象创建、布局和访问://www.greatytc.com/p/ac162726d7de
对象内存分配://www.greatytc.com/p/fa3569127416
一.对象的创建
1.类加载检查
- 创建对象的指令的参数是否能在常量池定位到一个类的符号引用
- 这个符号引用代表的类是否已被加载、解析和初始化
2.为对象分配内存
- 对象所需内存大小在类加载完成后已确定,虚拟机将堆中一块确定大小的内存划给对象
- 根据堆中是否规整有两种内存分配方式
1)指针碰撞(Bump the pointer)
堆中内存规整,以指针作为已用内存和空闲内存的分界点。分配内存即将分界指针向空闲空间移动一段与对象大小相等的距离。适用Serial、ParNew等收集器。
2)空闲列表(Free List)
堆中内存不规整,虚拟机通过维护一张列表确定内存使用情况。分配内存时更新空闲表。适用CMS等基于Mark-Sweep算法的收集器。 - 解决分配内存时并发的问题
1)时间上同步内存分配动作:CAS + 失败重试
2)空间上划分内存分配动作:每个线程在Java堆中预先分配一小块内存(TLAB 本地线程分配缓冲)。线程只在自己的TLAB上进行内存分配,只有TLAB用完时才进行同步锁定 - 对象内存分配策略
见后文
3.内存空间初始化
- 虚拟机将对象分配到的内存空间都初始化为零值
- 保证对象可以不赋初始值就直接使用
4.对象设置
虚拟机对对象进行必要设置,将设置信息存入对象的对象头,如:
- 对象属于的类
- 对象的哈希码
- 对象的GC分代年龄
5.init
- 进入对象的init方法,将对象按照程序员的意志初始化
二.对象的内存布局
1.对象 = 对象头(Header) + 实例数据(Instance Data) + 对齐填充(Padding)
2.对象头(Header)
包括数据信息和类型指针两部分
- 数据信息存储对象自身运行时数据
包括 哈希码/GC分代年龄/锁状态标志/线程持有的锁/偏向线程ID/偏向时间戳 等
- 类型指针指向对象的类元数据
虚拟机据此确定对象是哪个类的实例
3.实例数据(Instance Data)
- 对象真正存储的有效信息,即代码定义的各类字段内容,包括继承的父类字段
- 相同宽度的字段分配到一起,默认分配策略为 longs/doubles,ints,shorts/chars,bytes/booleans,oop
4.对齐填充(Padding)
- 占位符,保证对象的起始地址为8字节的整数倍
三.对象的访问定位
- 对象的访问方式取决于虚拟机实现,目前主流有使用句柄和直接指针(HotSpot)两种
1.句柄
- 堆中划分一块内存作为句柄池
- 栈中引用存储的是对象的句柄地址
- 堆中句柄存储了对象实例数据与类型数据的具体地址信息
- 优点:对象移动时只改变句柄池中的实例数据指针,不改变引用中的句柄地址
2.直接指针
- 引用中直接存储对象地址
- 优点:节省一次指针定位的时间开销,速度更快
四.对象内存分配策略
- JVM自动内存管理 = 对象内存分配 + 对象内存回收
- 内存分配规则细节取决于当前使用的垃圾收集器组合及虚拟机中与内存相关的参数设置
- 下述内存分配策略适用于Serial/Serial Old 和 ParNew/Serial Old 收集器组合
1.对象优先分配在新生代Eden区
- 当Eden区空间不足,虚拟机将发起一次Minor GC
2.大对象直接进入老年代
- 大对象指需要大量连续空间的Java对象,如很长的字符串和数组
- 大对象(尤其是朝生夕灭的大对象)容易导致虚拟机提前触发GC以获取连续空间
-
-XX:PretenureSizeThreshold参数
设置大于该值的对象直接分配在老年代,以避免新生代大量内存复制操作
3.长期存活对象进入老年代
- 虚拟机给每个对象定义了一个年龄(Age)计数器
- 对象在Eden出生并经过Minor GC进入Survivor后,对象年龄为1
- 此后对象在Survivor每经历一次Minor GC,年龄+1
- 默认对象年龄达到15就将进入老年代,可通过参数 -XX:MaxTenuringThreshold设置
4.动态对象年龄判定
- 根据实际内存状况需要,虚拟机并不一定要求对象年龄达到MaxTenuringThreshold才晋升老年代
- 当 Survivor区中相同年龄所有对象的大小之和>Survivor空间的一半,>=该年龄的对象将直接进入老年代
5.空间分配担保
- 空间分配担保过程
1)Minor GC前,虚拟机将检查老年代最大可用的连续空间是否大于新生代所有对象总空间
1.1)若大于,则此次Minor GC可确保是安全的
2)若不大于,则虚拟机根据HandlePromotionFailure设置值判断是否允许空间分配担保失败
2.11)若HandlePromotionFailure不允许冒险,则将Minor GC改为Full GC
3)若HandlePromotionFailure允许冒险,则检查老年代最大可用连续空间是否大于历次晋升老年代对象的平均大小
3.1)若不大于,则将Minor GC改为Full GC
4)若大于,则进行一次有风险的Minor GC - 空间分配担保的几点说明
1)使用复制收集算法的新生代可能在Minor GC后仍有大量对象存活的情况
2)此时需老年代进行空间分配担保,保证Survivor区无法容纳的对象直接进入老年代
3)老年代进行空间分配担保的前提,是本身有足够空间容纳这些对象
4)但在Minor GC前无法确定将移动的对象大小,故取之前每次Minor GC的平均值作参照
5)如果Minor GC晋升老年代的对象大小 > 之前晋升对象大小平均值,该次空间分配担保失败,并重新发起一次Full GC
清溪非陇水,翻作断肠流