JVM-(7)堆内存逻辑分区
JVM 堆内存逻辑分区
在 JVM内存模型 这篇文章中我们了解了JVM 内存的逻辑分区情况,如下:
在 JVM GC 这篇文章中我们了解到,如果某个对象不再被使用就会被当成垃圾,而新建对象一般分配堆空间内存(大部分情况如此,Java对象在特定条件下可以在栈上分配内存)
哪些情况Java对象可以在栈上分配内存?
分配条件
对象若满足以下条件,JVM会将其分配到栈上:1. 作用域受限:对象仅在方法内部使用,未被外部引用(如未作为返回值或被其他线程访问)。
2. 小对象:通常指几十字节以内的小对象。
3. 线程私有:仅被当前线程使用,不会跨线程共享。 优化原理
栈上分配通过逃逸分析技术实现,JVM会分析对象是否可能被外部访问。若确定不会逃逸,则将对象分配到栈中,其生命周期随方法执行结束自动销毁,无需垃圾回收,可提升性能。 适用场景
高频调用场景:如高频创建临时对象时,栈上分配可减少GC压力。
局部变量:如方法内部使用的临时对象。
堆内存逻辑分区
新生代与老年代
堆内存在逻辑上分为 新生代(young)和老年代(old/tenured)
- 新生代(young):刚new出来的对象存放于此
- 老年代(old/tenured):垃圾回收了很多次都没有把它回收掉的老对象存放于此
新生代又分为:
- eden 默认比例是8。新new出来的对象放在eden区。
- survivor(s0) 默认比例是1。当发生YoungGC时,eden区没有被清除的对象转移至 s0或s1
- survivor(s1) 默认比例是1。
比如:
① 新建对象存放在eden区,当发生YoungGC时,存活对象转移至s0,eden区内存清空
② 又有新对象,存放在eden区,当发生YoungGC时,eden区存活对象和s0区存活对象转移至s1,eden区和s0区内存清空
③ 如此往复,每次发生 GC 时对象年龄会加一,如果多次GC后对象仍然存活,就把对象放到老年代
几个GC的概念
- MinorGC/YGC :新生代空间耗尽时触发的垃圾回收。
- MajorGC/FullGC :在老年代无法继续分配空间时触发,新生代、老年代同时进行垃圾回收。
新生代和老年代使用的垃圾回收算法
新生代存活的对象较少,使用的垃圾回收算法是拷贝算法(Copying)。
老年代活着的对象较多,垃圾回收算法适用标记压缩(Mark Compact)或者标记清除(Mark Sweep)。
为什么新生代垃圾回收使用拷贝算法
① 高效处理高频死亡对象
新生代中约99%的对象会在首次垃圾回收时被清除。复制算法通过将存活对象复制到另一块内存区域,仅需处理少量存活对象即可完成回收,显著提升效率。
② 避免内存碎片
复制算法通过将内存分为两块并交替使用,每次回收时仅清空当前使用的内存块,存活对象迁移到另一块内存。这种机制有效减少内存碎片,避免后续分配大对象时出现空间不足的问题。
③ 优化内存分配
新生代内存空间较小(通常为堆内存的1/6),频繁回收可快速释放空间。复制算法通过空间换时间的方式,以较小的内存浪费(仅使用50%的内存)实现高效回收,满足高频垃圾处理需求。
④ 适应对象生命周期
新生代对象通常生命周期较短,复制算法的快速回收特性与这一特性高度匹配。而老年代对象存活率较高,更适合使用 标记-整理算法 (Mark-Compact)以优化空间利用率。
一个对象的生命历程-从出生到消亡
① 一个对象被new出来之后,首先尝试进行栈上分配,栈上如果分配不下才会进入eden区;
② eden区经过一次垃圾回收之后进入一个survivor区-s1区;
③ survivor区(s1)经过一次垃圾回收之后又进入另一个survivor区-s2区,同时eden区的某些对象也会跟着进入s2;
④ 当对象年龄到某一个值后,会进入到old区。这个值可以通过以下参数设置:
-XX:MaxTenuringThreshold
通过以下命令,可以查看MaxTenuringThreshold的默认值
java -XX:+PrintFlagsFinal | grep MaxTenuringThreshold
windows通过下面命令查看:
java -XX:+PrintFlagsFinal | findstr /i "MaxTenuringThreshold"
意思是一个存活于新生代的对象如果在发生15次 YoungGC 后还没有被清除,那么这个对象就转移到老年代
垃圾回收器
HotSpot虚拟机提供了多种垃圾回收器,根据JDK版本和应用场景不同,默认及可选回收器有所差异。以下是主要回收器分类及特性:
一、分代回收器组合
1. Serial + Serial Old
- 新生代(Serial):单线程复制算法,适用客户端模式或小内存环境
- 老年代(Serial Old):单线程标记-整理算法,作为CMS失败时的备选
- 参数:-XX:+UseSerialGC。
2. ParNew + CMS
- 新生代(ParNew):多线程版Serial,与CMS配合使用
- 老年代(CMS):并发标记-清除算法,追求低延迟,但存在内存碎片问题
- 参数:-XX:+UseParNewGC -XX:+UseConcMarkSweepGC(JDK14后移除)
3. Parallel Scavenge + Parallel Old
- 新生代(Parallel Scavenge):多线程复制算法,注重吞吐量
- 老年代(Parallel Old):多线程标记-整理算法,JDK8默认组合
- 参数:-XX:+UseParallelGC -XX:+UseParallelOldGC
二、整堆回收器
1. G1(Garbage-First)
- 分区式回收,兼顾吞吐与延迟,JDK9+默认回收器
- 将堆划分为多个Region,优先回收垃圾最多的区域
- 参数:-XX:+UseG1GC
2. ZGC/Shenandoah
- ZGC:支持TB级堆,停顿时间<10ms,JDK11+提供
- Shenandoah:低延迟,与ZGC类似但实现不同
- 参数:-XX:+UseZGC 或 -XX:+UseShenandoahGC
三、版本演进与默认选择
- JDK8:默认 Parallel Scavenge + Parallel Old
- JDK9+:默认 G1
- JDK14+:移除CMS,推荐G1或ZGC
如何查看当前 jvm 默认使用的垃圾回收器?
使用如下命令:java -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -version
-XX:+PrintGCDetails:这个选项让Java虚拟机(JVM)在控制台输出垃圾收集的详细信息。这包括每次垃圾收集的类型、发生的时间、持续时间以及堆使用情况等信息。
-XX:+PrintCommandLineFlags:这个选项让JVM在启动时打印出所有传递给JVM的命令行参数,包括默认参数和用户指定的参数。这对于调试和了解JVM是如何被配置的有很大帮助。
-version:这个选项让JVM打印出版本信息,这对于确认正在使用的Java版本是有用的。
执行命令后出现 -XX:+UseParallelGC 表示当前JVM启用了 Parallel Scavenge(新生代) + Parallel Old(老年代) 的垃圾回收器组合
参考(推荐):【GC系列】JVM堆内存分代模型及常见的垃圾回收器