堆
# 堆
# 一. 概述
- 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。是被所有线程共享的一块内存区域。
- Java堆区在Jvm启动的时候既被创建,其空间大小也就确定了。也是JVM管理最大的内存空间。
- 堆内存的大小是可以调节的(通过 -Xmx 和 -Xms 控制(年轻代+老年代的初始内存大小))。
- 所有的对象实例以及数组都应该在运行时分配在堆上。
- 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。
- 在方法结束后,堆中对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
- 堆 是GC执行垃圾回收的重点区域
# 二. 内存结构
java 7及之前堆内存分为三部分: 新生区(年轻代)(Young/New)+养老区(老年代)(Old/Tenure)+永久区
java 8及之后堆内存分为三部分: 新生区(年轻代)(Young/New)+养老区(老年代)(Old/Tenure)+元空间

# 2.1 年轻代
年轻代是所有新对象创建的地方。年轻代分为两个区伊甸园区(Eden Memory)和Survivor区(Survivor区又分为两个Survivor0、Survivor1区/from区、to区) 两区默认比例是: 8:1:1 大多数新创建的对象都位于 Eden 内存空间中。
# 2.2 老年代
旧的一代内存包含那些经过许多轮小型 GC 后仍然存活的对象。通常,垃圾收集是在老年代内存满时执行的。老年代垃圾收集称为 主GC(Major GC),通常需要更长的时间。大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)如下第四节图中所示。这样做的目的是避免在 Eden 区和两个Survivor 区之间发生大量的内存拷贝。
# 三. 设置堆内存相关参数
# 3.1 堆内存参数设置
Java 堆用于存储 Java 对象实例,那么堆的大小在 JVM 启动的时候就确定了,我们可以通过 -Xmx 和 -Xms 来设定
- -Xms 用来表示堆的起始内存,等价于 -XX:InitialHeapSize
- -Xmx 用来表示堆的最大内存,等价于 -XX:MaxHeapSize
如果堆的内存大小超过 -Xmx 设定的最大内存, 就会抛出 OutOfMemoryError 异常。 我们通常会将 -Xmx 和 -Xms 两个参数配置为相同的值,其目的是为了能够在垃圾回收机制清理完堆区后不再需要重新分隔计算堆的大小,从而提高性能
# 3.2 新生代和老年代内存设置相关参数
- 年轻代和老年代默认比例 1:2
- -XX:NewRatin=? 修改年轻代和老年代的占比。比如-XX:NewRatin=2,表示新生代占1,老年代占2。新生代占整个的1/3
- 伊甸园区(Eden Memory)和Survivor区(Survivor0、Survivor1区) 比例为8:1:1
- XX:SurvivorRatio=? 用于设置Eden和其中一个Survivor的比值,这个值也比较重要。
# 四. 对象分配过程
为对象分配内存是一件非常严谨和复杂的任务,JVM 的设计者们不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法和内存回收算法密切相关,所以还需要考虑 GC 执行完内存回收后是否会在内存空间中产生内存碎片。

- new 的对象先放在伊甸园区,此区有大小限制。
- 当伊甸园的空间填满时,程序又需要创建对象,JVM 的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区。
- 然后将伊甸园中的剩余对象移动到幸存者 0 区。
- 如果再次触发垃圾回收,此时上次幸存下来的放到幸存者 0 区,如果没有回收,就会放到幸存者 1 区。
- 如果再次经历垃圾回收,此时会重新放回幸存者 0 区,接着再去幸存者 1 区。
- 什么时候才会去养老区呢? 默认是 15 次回收标记可以设置参数更改阈值 -XX:MaxTenuringThreshold。
- 在养老区,相对悠闲。当养老区内存不足时,再次触发 Major GC,进行养老区的内存清理。
- 若养老区执行了 Major GC 之后发现依然无法进行对象的保存,就会产生 OOM 异常
# 4.1 年轻代GC(Minor GC)触发机制:
- 当年轻代空间不足时,就会触发Minor GC,这里的年轻代满是指的是 Eden(伊甸园)区,Survivor满不会引起GC。
- 因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快
- Minor GC 会引起STW,暂停其他用户的线程,等待垃圾回收结束,用户线程才恢复运行。
# 4.2 老年代GC(Major GC)触发机制:
- 指发生在老年代的GC,对象从老年代消失时,我们说“Major GC”或“Full GC”发生了。
- 出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在ParallelScavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。
- 也就是在老年代空间不足时,会先尝试触发Minor GC。如果之后空间还不足,则触发Major GC
- Major Gc的速度一般会比Minor Gc慢1e倍以上,STW的时间更长
- 如果Major GC 后,内存还不足,就报OOM了。
- Major GC的速度一般会比Minor Gc慢1e倍以上。
# 五. Minor GC、Major GC、Full GC
JVM 在进行 GC 时,并非每次都对堆内存(新生代、老年代;方法区)区域一起回收的,大部分时候回收的都是指新生代。 针对 HotSpot VM 的实现,它里面的 GC 按照回收区域又分为两大类:部分收集(Partial GC),整堆收集(Full GC)
- 部分收集:不是完整收集整个 Java 堆的垃圾收集。其中又分为:
- 新生代收集(Minor GC/Young GC):只是新生代的垃圾收集。
- 老年代收集(Major GC/Old GC):只是老年代的垃圾收集
- 目前,只有 CMS GC 会有单独收集老年代的行为。
- 很多时候 Major GC 会和 Full GC 混合使用,需要具体分辨是老年代回收还是整堆回收。
- 混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集
- 目前只有 G1 GC 会有这种行为
- 整堆收集(Full GC):收集整个 Java 堆和方法区的垃圾
# 六. 对空间常用参数设置
- -XX:+PrintFlagsInitial 查看所有的参数的默认初始值
- -XX:+PrintFlagsFinal 查看所有的参数的最终值(可能会存在修改,不再是初始值)
- 具体查看某个参数的指令:
- jps:查看当前运行中的进程
- jinfo -flag SurvivorRatio 进程id
- 具体查看某个参数的指令:
- Xms: 初始堆空间内存(默认为物理内存的1/64 )
- Xmx: 最大雄空间内存(默认为物理内存的1/4)
- -Xmn: 设置新生代的大小。(初始值及最大值)
- -XX:NewRatio: 配置新生代与老年代在雄结构的占比
- -XX:SurvivorRatio: 设置新生代中Eden和so/s1空间的比例
- -XX:MaxTenuringThreshold: 设置新生代垃圾的最大年龄
- -XX:+PrintGcDetails: 出详细的GC处理日志
- -XX:HandlePromotionFailure: 是否设置空间分配担保
- -XX:+HeapDumpOnOutOfMemoryError 内存溢出是会生成文件 以便分析错误