Java 堆从 GC 的角度还可以细分为:
- 新生代(Eden 区、From Survivor(S0) 区和 To Survivor 区(S1))
- 老年代
1. 新生代
a)Eden 区
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。
当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。
b)ServivorFrom
上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
c)ServivorTo
保留了一次 MinorGC 过程中的幸存者。
d)MinorGC 的过程(复制->清空->互换)
MinorGC 采用复制算法
1:eden、servicorFrom 复制到 ServicorTo,年龄+1
首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,默认年龄达到15,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区);
2:清空 eden、servicorFrom
然后,清空 Eden 和 ServicorFrom 中的对象;
3:ServicorTo 和 ServicorFrom 互换
最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区。
2. 老年代
主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以 Major GC(也称Full GC) 不会频繁执行。在进行 MajorGC 前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。
a)Major GC 的触发条件
Major GC 的触发通常发生在以下几种情况:
- 老年代内存不足:当老年代内存满时,JVM 会触发 Major GC 来释放空间。
- 晋升失败:如果年轻代的对象因为存活时间过长而无法晋升到老年代,这也可能触发 Major GC。
- 显式调用 System.gc():虽然不推荐使用,但调用 System.gc() 方法会请求进行 Full GC。
- 类卸载:在类被卸载时(如应用程序退出或类加载器被回收),也会触发 Major GC。
b)Major GC 的过程
1:标记(Mark):首先,JVM 会标记老年代中存活的对象,找到哪些对象是活动的。
2:清理(Sweep):清理掉不再被引用的对象,释放内存空间。
3:压缩(Compact):对于老年代的碎片化,JVM 可能需要压缩内存,即将存活的对象移动到堆的另一部分,从而释放出更大的连续内存空间。这个步骤会增加垃圾回收的时间。
d)优化 Major GC
因为 Major GC 是最耗时的垃圾回收操作之一,开发者可以通过以下几种方式来减少 Major GC 的发生频率:
- 增加堆内存大小:增加 JVM 堆的大小,特别是老年代的大小,可以减少老年代频繁发生 Full GC 的概率。
- 调整年轻代大小:适当调整年轻代的大小(Eden 区和 Survivor 区),避免过多对象晋升到老年代。
- 使用 G1 GC 或其他垃圾回收器:一些现代的垃圾回收器,如 G1 GC,会通过优化垃圾回收策略来减少 Full GC 的影响,尤其是在大规模堆内存的环境中。
- 减少对象的生命周期:减少在应用程序中创建大量短生命周期的对象,从而降低年轻代内存的压力,减少 Minor GC 频率,间接减少 Major GC 的频率。
e)监控 Major GC
开发人员可以通过各种工具来监控 JVM 中的垃圾回收活动,特别是 Major GC。例如:
- JVM 日志:可以使用 -XX:+PrintGCDetails 和 -XX:+PrintGCDateStamps 等 JVM 参数来打印详细的垃圾回收日志,从中可以看到 Minor GC 和 Major GC 的发生情况
- JVisualVM / JConsole:这些工具提供了图形化界面,可以实时查看 JVM 中的垃圾回收活动、堆的使用情况等信息。
- GC 日志分析工具:可以使用 gcviewer 或其他 GC 日志分析工具来分析 Full GC 的频率和停顿时间。
f)Major GC 和 Minor GC 的区别
特性 | Minor GC | Major GC |
---|---|---|
回收的区域 | 主要回收年轻代(Young Generation)。 | 回收年轻代和老年代(Old Generation)。 |
触发条件 | 年轻代空间满,导致垃圾回收。 | 老年代空间满,或者发生对象晋升失败。 |
停顿时间 | 通常较短,影响较小。 | 通常较长,影响较大。 |
回收的对象 | 主要回收年轻代中的短生命周期对象。 | 回收老年代中的长生命周期对象。 |
发生频率 | 频繁,尤其是在对象创建较快的情况下。 | 相对较少,通常在内存压力较大时发生。 |
3. 永久代/元数据区
指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。
a)JAVA8 与元数据
在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。
元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。