Java的垃圾回收机制(Garbage Collection, GC)是其内存管理的核心功能之一。通过GC,Java自动管理对象的生命周期,回收不再使用的对象所占的内存空间。本文将详细探讨GC的实现原理、不同算法的细节以及其在JVM中的应用。

垃圾回收的基本原理

垃圾回收的主要任务是识别和回收不再使用的对象。GC的基本工作过程包括:

  • 标记阶段:标记所有存活的对象。
  • 清除阶段:回收所有未标记的对象。
  • 压缩阶段(可选):整理内存碎片。

垃圾回收算法

  1. 标记-清理:当一个对象没有被引用时标记它,然后隔一段时间或者立刻清理,会造成内存碎片。
  2. 标记-整理:为了解决内存碎片问题每隔一段时间就把内存进行平移紧凑,让内存空间变得连续,但是开销很大。
  3. 复制:两块同等大小的内存空间,当左边的空间不够的时候,就把左边有效的空间连续地复制到右边并统一清理左边的空间,下次右边不够再给左边连续复制,交替使用,缺点是内存利用效率低。据网上说复制比标记-整理开销低,具体为什么低我也没搞清楚,如果有大佬知道的话可以告诉我。
  4. 分代收集(Generational Collection)算法:
  5. 分代收集算法基于对象的存活时间,将堆内存分为几代:年轻代(Young Generation)、年老代(Old Generation)和永久代(Permanent Generation)。各代使用不同的收集算法。

    • 年轻代:对象生命周期短,频繁发生GC,使用复制算法。

    • 年老代:对象生命周期长,使用标记-清除或标记-压缩算法。

    • 永久代:存储类的元数据(在Java 8及以后版本中被元空间(Metaspace)替代)。

JVM中的垃圾收集器

Java虚拟机(JVM)实现了多种垃圾收集器,不同收集器适用于不同的应用场景:

  • Serial 收集器:Serial 收集器是单线程的,适用于单处理器环境和客户端应用。
  • Parallel 收集器:Parallel 收集器是多线程的,适用于多处理器环境,需要高吞吐量的应用。
  • CMS(Concurrent Mark-Sweep)收集器:CMS 收集器是低延迟收集器,目标是最小化停顿时间,适合对响应时间要求高的应用。
  • G1(Garbage-First)收集器:G1 收集器是分区收集器,将堆划分为多个区域,优先收集垃圾最多的区域,适合大内存、多处理器的服务器应用。

JVM垃圾回收机制(GC)-CSDN博客

如图:GC把内存空间分为新生代和老年代。新生代包括伊甸区(E)、幸存1区(S0)和幸存2区(S1)。各个区的大小不一样,可以调整比例。新生代GC称为minor GC,老年代GC称为Major GC/Full GC。

new出的对象在伊甸区(E区),当空间不够时会触发复制操作复制到幸存1区(S0)并统一回收垃圾空间,下次空间再不够,S0和E区一起往幸存2区(S1)复制,并统一回收垃圾空间,之后S1和E往S0复制,S0和S1反复交替使用,如果一个对象15轮GC都没有淘汰,就会进入老年代。如果幸存区放不下幸存的对象,就会进行分配担保机制,这些多出来的对象会直接进入老年代。

老年代存放存活超过15代占用内存大的对象,当老年代空间不够时,触发老年代GC采用标记整理方法回收对象,老年代GC的时候通常伴随年轻代GC,也就是Full GC,会引发程序暂停,全力进行垃圾回收。

进入老年代的条件:

1、大对象:大对象指需要大量连续内存空间的对象,这部分对象不管是不是“朝生夕死”,都会直接进到老年代。这样做主要是为了避免在 Eden 区及 2 个 Survivor 区之间发生大量的内存复制。当你的系统有非常多“朝生夕死”的大对象时,得注意了。

2、长期存活对象:虚拟机给每个对象定义了一个对象年龄(Age)计数器。正常情况下对象会不断的在 Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中每经历一次 Minor GC,年龄就增加 1 岁。当年龄增加到 15 岁时,这时候就会被转移到老年代。当然,这里的 15,JVM 也支持进行特殊设置 -XX:MaxTenuringThreshold=10

可通过 java -XX:+PrintFlagsFinal -version | grep MaxTenuringThreshold 查看默认的阈值。

3、动态对象年龄:JVM 并不强制要求对象年龄必须到 15 岁才会放入老年区,如果 Survivor 空间中某个年龄段的对象总大小超过了 Survivor 空间的一半,那么该年龄段及以上年龄段的所有对象都会在下一次垃圾回收时被晋升到老年代,无需等你“成年”。

有点类似于负载均衡,轮询是负载均衡的一种,保证每台机器都分得同样的请求。看似很均衡,但每台机器的硬件不同,健康状况不同,所以我们可以基于每台机器接收的请求数、响应时间等,来调整负载均衡算法。

这种动态调整机制有助于优化内存使用和减少垃圾收集的频率,特别是在处理大量短生命周期对象的应用程序时。