Java 的垃圾回收机制

Java的垃圾回收机制(GarbageCollection,简称GC)是Java语言管理内存的核心特性之一,它使得开发者无需手动释放对象所占用的内存,系统会自动检测并回收不再使用的内存空间。下面从几个关键方面为你详细讲解Java的垃圾回收机制。

什么是垃圾回收

在Java中,当一个对象不再被引用时,它就成为了“垃圾”,即无法再从程序的任何地方访问到这个对象。垃圾回收机制的主要作用就是自动检测这些垃圾对象,并回收它们所占用的内存空间,以便后续程序可以重新使用这些内存。

如何判定对象为垃圾

引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的,即垃圾对象。不过,这种方法存在循环引用的问题,即两个对象相互引用,导致它们的引用计数器都不为0,但实际上它们已经没有被其他对象引用,无法被访问到,这样就无法被判定为垃圾对象。例如:

垃圾对象实例

可达性分析法

这是Java虚拟机(JVM)采用的判定方法。通过一系列称为“GCRoots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GCRoots没有任何引用链相连时,则证明此对象是不可用的,即垃圾对象。可以作为GCRoots的对象包括:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

垃圾回收算法

标记-清除算法

这是最基础的垃圾回收算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。该算法的缺点是会产生大量不连续的内存碎片,当程序需要分配较大对象时,可能会因为无法找到足够的连续内存而提前触发下一次垃圾回收。

标记-整理算法

标记过程和“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。这种算法避免了内存碎片的问题,但移动对象的过程会带来一定的性能开销。

复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。不过,这种算法的代价是将内存缩小为原来的一半。

分代收集算法

当前商业虚拟机的垃圾收集都采用“分代收集”算法,它根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

  • 新生代:对象朝生夕灭,每次垃圾回收都有大量对象死去,只有少量存活,所以选用复制算法,只需要付出少量存活对象的复制成本就可以完成垃圾回收。
  • 老年代:对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清除”或者“标记-整理”算法来进行回收。

垃圾回收器

不同的垃圾回收器适用于不同的场景,JVM提供了多种垃圾回收器,以下是一些常见的垃圾回收器:

  • Serial收集器:单线程的收集器,它在进行垃圾回收时,必须暂停其他所有的工作线程,直到它回收结束。它简单而高效,对于单CPU环境来说是一个不错的选择。
  • ParallelScavenge收集器:并行的多线程收集器,目标是达到一个可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),适合在后台运算而不需要太多交互的任务。
  • CMS(ConcurrentMarkSweep)收集器:以获取最短回收停顿时间为目标的收集器,非常适合对响应时间要求较高的场景。它基于“标记-清除”算法实现,整个过程分为初始标记、并发标记、重新标记、并发清除四个阶段,其中初始标记和重新标记需要“StopTheWorld”,但停顿时间都很短。
  • G1(Garbage-First)收集器:面向服务端应用的垃圾收集器,它将整个Java堆划分为多个大小相等的独立区域(Region),跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。G1收集器可以预测垃圾回收的停顿时间,避免全堆扫描。

垃圾回收的触发条件

  • 新生代空间不足:当新创建的对象无法在新生代的Eden区分配内存时,会触发MinorGC(新生代垃圾回收)。
  • 老年代空间不足:当新生代的对象晋升到老年代时,如果老年代没有足够的空间容纳这些对象,会触发FullGC(全量垃圾回收)。
  • 调用System.gc()方法:虽然调用该方法会建议JVM进行垃圾回收,但这只是一个建议,JVM可以选择忽略它。

总结

Java的垃圾回收机制通过自动管理内存,减轻了开发者的负担,提高了开发效率。了解垃圾回收机制的原理、算法和回收器的特点,可以帮助开发者更好地优化Java程序的性能,避免出现内存泄漏和内存溢出等问题。