对象是如何创建的及是如何分配内存的?
作者:徐梦旗,发布于:2023年09月09日 18:00,字数:1.4k,预计阅读:5分钟
1. 对象的生命周期
1.1. 对象是如何创建的?
- 通过
new
指令或反射等方式创建新的对象; - 若该对象对应的类从未被加载过,则进行类的加载;
- 在堆中(或栈上分配)为对象分配内存空间;
- 初始化对象的内存空间为零值;
- 为对象设置必要的信息,如类的元数据,对象的哈希码,GC分代年龄等;
- 执行对象的构造器方法
<init>
。
1.2. 对象的内存分配方式有哪些?
- 栈上分配:如果经过逃逸分析后发现该对象是非逃逸的小对象,可以尝试在栈帧中分配内存,以提高内存分配的效率。
- 碰撞指针:适合连续的内存空间,如堆中的新生代。
- 空闲列表:适合不连续的内存空间,如堆中的老年代。
虚拟机参数:
-server
:开启服务端模式。-XX:+DoEscapeAnalysis
:开启逃逸分析。-XX:+EliminateAllocations
:开启标量替换。
1.3. 如何解决内存分配的线程安全问题?
- 首先会从线程本地分配缓存区(Thread Local Allocation Buffer,TLAB)中分配内存,这块内存是每个线程私有的,不会有线程安全问题;
- 如果在TLAB中分配不到内存,则使用CAS(Compare And Swap)乐观锁去堆内存中分配。
虚拟机参数:
-XX:+UseTLAB
:开启线程本地分配缓存区分配。
2. 对象的内存分配
2.1. 新对象在堆中是如何分配内存的?
- 首先判断新对象是否是大对象,如果是则直接在老年代为新对象分配内存(内存不足会进行一次Full GC,还不足会OOM);
- 否则判断新生代的Eden区的可用是否大于新对象大小,如果是则在新生代的Eden区为新对象分配内存;
- 否则判断老年代的最大可用的连续空间是否大于新生代所有对象的总大小或老年代的最大可用的连续空间是否大于历代晋升老年代对象的平均大小,如果是则进行Minor GC,否则进行Full GC;
- 最后再在新生代的Eden区为新对象分配内存(内存不足会OOM)。
2.2. 新生代的对象什么时候进入老年代?
- 大对象直接进入老年代。
- 超过最大年龄阈值的对象会进入老年代。
- 如果Survivor区中相同年龄的对象的总大小大于Survivor区总大小的50%,大于等于该年龄的对象会进入老年代。
- Minor GC后,Survivor区容纳不了新生代存活的对象时会进入老年代。
虚拟机参数:
-XX:PretenureSizeThreshold
:指定大对象的阈值,默认为1MB。-XX:MaxTenuringThreshold
:指定进入老年代的年龄阈值,默认为15。-XX:TargetSurvivorRatio
:指定动态年龄判断的比例,默认为50。
2.3. 什么是空间分配担保?
空间分配担保是指在Minor GC前,会判断老年代的最大可用的连续空间是否大于新生代所有对象的总大小,或老年代的最大可用的连续空间是否大于历代晋升老年代对象的平均大小,如果是则进行Minor GC;否则空间分配担保失败,需要进行Full GC。
空间分配担保的目的是确保Minor GC是安全的,避免在Minor GC后,存活的对象在老年代中没有足够的空间无法存放导致Full GC。
2.4. 什么时候发生Minor GC和Full GC?
发生GC通常的原因是没有足够的可用内存来存放存活的对象,需要回收垃圾对象以释放内存空间。
- 新生代空间不足且空间分配担保成功时,会发生Minor GC。
- 新生代空间不足且空间分配担保失败时,会发生Full GC。
- 老年代空间不足时,会发生Full GC。
- 调用
System#gc
方法时,可能会发生Full GC。
2.5. 频繁发生Full GC的原因是什么?
频繁发生Full GC的本质原因是老年代的可用内存不足,可能有以下几种情况:
- 程序创建了过多的大对象。
- 程序中存在内存泄漏,有大量需要回收但是未被回收的对象。
- 程序中存在代码死循环,不断地创建对象。
- 调用了
System#gc
方法。
可用通过转储堆的内存快照文件来分析堆中对象的信息,以及可以观察GC日志来分析原因。
虚拟机参数:
-XX:+HeapDumpBeforeFullGC
:开启在Full GC前转储堆的内存快照文件。-XX:+PrintGCDetails
:开启打印GC详细信息。
3. 对象的内存布局
3.1. 对象的内存布局是怎样的?
- 对象头
- MarkWord:包括对象的hashcode,GC分代年龄,锁状态等。
- 类型指针:指向类的元数据的指针,占8个字节(开启指针压缩的情况下占4个字节)。
- 数组长度:只有数组对象才有,占4个字节。
- 实例数据
- 对齐填充
虚拟机参数:
-XX:+UseCompressedClassPointers
:开启类指针压缩。-XX:+UseCompressedOops
:开启普通对象指针压缩。
3.2. 如何访问一个对象?
- 句柄访问:通过句柄池中的指针访问对象,对象移动时,只需要修改句柄指向的指针。
- 直接指针:通过指针直接访问对象,比句柄访问更快,也是Hotspot访问对象的方式。