JVM&GC(二)对象提升规则

本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,详细解释了对象如何从Eden区出生,在Survivor区成长,直至晋升至老年代的过程。文章涵盖了对象年龄计数器的概念,大对象直接进入老年代的目的,以及动态判断对象年龄和空间分配担保策略。通过理解这些机制,开发者可以更好地优化应用程序的内存使用。

前言

虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden区出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1.对象在Survivor中每熬过一次Minor GC,年龄就将增加1岁,当他的年龄增加到一定程度(默认为15岁)时,就会被晋升到老年代中。

分配原则

  • 对象优先分配在Eden区,如果Eden区,如果Eden区没有足够的空间,虚拟机执行一次Minor GC。
  • 大对象直接进入老年代,这样做的目的是为了避免在Eden区和两个Survivor区之间发生大量的内存拷贝。
  • 长期存活的对象进入老年代。虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden区出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1.对象在Survivor中每熬过一次Minor GC,年龄就将增加1岁,当他的年龄增加到一定程度(默认为15岁)时,就会被晋升到老年代中。
  • 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可直接进入老年代。
  • 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年去的对象的平均大小,如果这个值大于老年去的剩余值大小则进行一次Full GC,如果小于则进入检查HandlePromotionFailure逻辑。判断这个逻辑,如果是True则只进行Minor GC,如果是False则进行Full GC。
    图片.png
    个人博客
    腾讯云社区
    掘金
    优快云
    简书
    GitHub
    码云
    OSCHINA
    Segmentfault
    公众号:wx.jpg
<think>嗯,用户这次问的是JVM对象分配规则,需要结合站内引用和我之前关于JVM调优的回答来详细说明。用户可能正在学习JVM内存管理,或是遇到了性能问题需要优化对象分配。 从引用[2]和引用[3]来看,用户提供的资料已经覆盖了逃逸分析、栈上分配、空闲列表和TLAB等关键概念。不过用户上次问的是调优方法,这次转向底层机制,可能需要更系统的解释。 我注意到三个重点需要展开: 首先是对象分配的主要区域,堆内存确实是主战场,但栈上分配这个特例很值得强调(引用[2]的逃逸分析部分)。其次是内存分配的具体算法,引用[3]提到的指针碰撞和空闲列表要对比说明,特别是并发处理方案如CAS和TLAB。最后是对象初始化的细节,包括零值初始化和对象头设置,这部分对理解内存布局很重要。 用户可能还隐含了性能方面的关切,比如“为什么我的应用创建对象很慢”,所以TLAB机制和逃逸分析的优化价值要突出说明。另外,上次调优回答里提到的年轻代分配策略(Eden/Survivor)和当前问题其实是上下游关系,可以自然关联。 需要警惕的是不能遗漏新对象分配的整体流程:从内存分配到初始化再到对象头设置,这个链条要完整。引用[3]里1.4和1.5两节正好用得上,但得转换成更易懂的描述。 最后考虑设计几个延伸问题:对象访问定位(直接指针vs句柄)、标量替换优化这些高级话题,既能帮用户深化理解,也和调优实战相关。毕竟能问分配规则的人,大概率下一步就要问如何优化了。</think>### JVM对象分配规则详解 #### 一、核心分配区域 1. **堆内存分配(默认策略)** 绝大多数对象在**新生代Eden区**分配: ```java Object obj = new Object(); // 默认在Eden区分配 ``` - **触发条件**:当Eden区空间不足时,触发Minor GC[^2] 2. **栈上分配(特殊优化)** 通过**逃逸分析**判断对象作用域: - 未逃逸对象对象仅当前方法访问)→ 直接在栈帧分配 - **优化效果**:避免GC压力,对象随栈帧销毁自动回收[^2] ```java void localAlloc() { Point p = new Point(1,2); // 未逃逸对象可栈分配 System.out.println(p.x); } // p随方法结束自动释放 ``` --- #### 、堆内存分配算法 | 分配方式 | 适用场景 | 实现机制 | |------------------|----------------------------|--------------------------------------------------------------------------| | **指针碰撞** | 堆内存规整(Serial/ParNew) | 移动指针划分内存:$新指针地址 = 当前指针 + 对象大小$ | | **空闲列表** | 堆内存碎片化(CMS) | 维护空闲内存块列表,查找足够大的空间分配[^3] | | **TLAB分配** | 多线程高并发场景 | 每个线程预先在Eden区分配私有缓冲区(默认Eden的1%)`-XX:TLABSize=256k`[^3] | --- #### 三、对象分配流程 1. **内存分配** - TLAB分配(优先尝试) → 失败则尝试堆分配 - 堆分配失败 → 触发Minor GC - GC后仍不足 → 老年代分配(大对象直接进入) 2. **内存初始化** 分配后立即**清零内存**(设默认值): ```c memset(obj_memory, 0, sizeof(Object)); // 所有字段置零值 ``` 3. **设置对象头** 写入元数据信息: - 对象哈希码 - GC分代年龄(4bit) - 锁状态标志 - 类型指针(指向类元数据) --- #### 四、特殊对象分配策略 1. **大对象直接进入老年代** 避免Eden区复制开销: ```bash -XX:PretenureSizeThreshold=1M # >1MB对象直接分配老年代 ``` 2. **长期存活对象晋升** **年龄计数器**机制: - 对象每存活1次Minor GC,年龄+1 - 达到阈值(默认15)晋升老年代 ```bash -XX:MaxTenuringThreshold=15 # 可调整晋升阈值 ``` 3. **动态年龄判定** 若Survivor区中**同年龄对象总大小 > Survivor区50%** 则**≥该年龄**的对象直接晋升老年代 --- #### 五、优化技术关联 | 技术 | 对分配的影响 | |-----------------|---------------------------------------------| | **逃逸分析** | 开启栈上分配/标量替换 `-XX:+DoEscapeAnalysis` | | **TLAB** | 减少线程竞争,提升分配速度[^3] | | **G1收集器** | 引入Humongous区专存>50%Region的大对象 | ```mermaid graph TD A[对象创建] --> B{逃逸分析} B -->|未逃逸| C[栈上分配] B -->|逃逸| D{大对象检测] D -->|>1MB| E[老年代分配] D -->|小对象| F[TLAB分配] F -->|成功| G[Eden区分配] F -->|失败| H[堆分配] ``` --- ### 相关问题 1. 如何通过`jmap`输出验证TLAB分配效果? 2. 逃逸分析在哪些场景下会失效? 3. 为什么G1需要单独的Humongous区处理大对象? 4. 对象头中的类型指针如何实现类信息访问? 5. 如何通过内存分析工具定位未逃逸对象被错误分配到堆的问题?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值