【JVM】详解Java中的垃圾回收

概念

在传统的编程语言中,内存的管理通常需要由开发人员手动进行分配和释放。这种手动管理容易导致一系列问题,例如内存溢出(内存不足时继续申请内存)和内存泄漏(已不再使用的内存未被释放)。这些问题可能导致程序的不稳定,甚至崩溃。

然而,Java通过引入垃圾回收(Garbage Collection,简称GC)机制,实现了自动内存管理。垃圾回收机制主要针对堆内存中的对象进行管理,其核心功能是自动分配和回收不再使用的对象的内存。

通过GC,Java虚拟机(JVM)在程序运行时可以自动识别并清理那些不再使用的对象,从而使开发人员无需手动管理内存。这不仅提高了开发效率,减少了内存管理相关的错误,还增强了程序的可靠性和稳定性。

垃圾判断算法

要判断对象是否为垃圾,通常有两种主流算法:引用计数法和可达性分析算法。

1.引用计数法

引用计数法为对象添加一个引用计数器,每当有一个地方引用该对象时,计数器加1;当引用失效时,计数器减1。计数器为0的对象即被认为是垃圾,可以被回收。这种方法简单直观,但无法处理循环引用的问题,即两个对象互相引用,但不再被其他对象引用的情况。

2.可达性分析算法

可达性分析算法通过一系列称为"GC Roots"的根对象开始,沿着这些对象的引用链进行遍历。凡是能被GC Roots引用到的对象都是"可达"的,不会被回收;而那些不可达的对象则被认为是垃圾,将被清理。这种算法解决了引用计数法的循环引用问题,是Java中垃圾回收的主要算法。

垃圾收集算法

垃圾收集算法是具体执行垃圾回收任务的方法,常见的算法包括:

1. 标记-清除算法

原理与步骤:

标记-清除(Mark-Sweep)算法是垃圾回收中最基础的算法之一。它的工作原理分为两个阶段:

  • 标记阶段:从根集合(GC Roots)出发,遍历堆中的所有对象,标记所有仍在使用中的对象。
  • 清除阶段:遍历堆中的所有对象,清除那些未被标记的对象,即认为它们是垃圾对象,可以回收其占用的内存。

优点:

  • 简单直观:标记-清除算法实现简单,只需对对象进行标记和清除操作,无需考虑对象的复制或移动。
  • 无须移动对象:该算法不移动对象的位置,因此适用于堆中有大量存活对象的情况。

缺点:

  • 产生内存碎片:由于未被标记的对象直接被清除,剩余的存活对象可能分散在堆的各个位置,导致内存碎片的产生。这些碎片可能导致大对象无法分配足够的连续内存空间。
  • 效率不高:标记和清除阶段都需要遍历整个堆,效率较低,尤其是在对象较多的情况下。

2. 复制算法

原理与步骤:

复制算法(Copying)将堆内存分为两个相同大小的区域------通常称为"From空间"和"To空间":

  • 分配内存:所有新创建的对象最初分配在"From空间"。
  • GC操作:当"From空间"用尽时,垃圾收集器将仍在使用的对象复制到"To空间",然后清空"From空间"。接下来,原来的"To空间"成为新的"From空间",进行下一轮分配和回收。

优点:

  • 避免内存碎片:通过将存活对象复制到另一块连续的空间,复制算法可以有效避免内存碎片问题。
  • 速度快:由于每次只处理存活对象,且无需在堆中遍历所有对象,复制算法在回收大量短命对象时效率很高。

缺点:

  • 内存利用率低:由于内存分为两个区域,每次只使用其中一个区域,这意味着实际内存利用率只有50%。
  • 不适合老年代:在老年代中,存活对象较多,复制操作成本较高,内存浪费严重。因此,复制算法主要用于新生代回收。

3. 标记-整理算法

原理与步骤:

标记-整理(Mark-Compact)算法结合了标记-清除和复制算法的优点:

  • 标记阶段:与标记-清除算法相同,从根集合出发标记存活对象。
  • 整理阶段:不同于标记-清除算法,标记-整理算法在清除阶段并不直接清理未标记的对象,而是将所有存活的对象向堆的一端移动,按顺序排列。最后,清理掉边界外的所有内存。

优点:

  • 避免内存碎片:通过将对象整理到堆的一端,标记-整理算法有效避免了内存碎片问题,使得堆内存更加紧凑。
  • 内存利用率高:标记-整理算法避免了复制算法的内存浪费问题,最大限度地利用可用内存。

缺点:

  • 效率较低:虽然避免了碎片问题,但移动对象的操作增加了算法的开销,尤其在堆中对象较多的情况下,标记和整理阶段都需要较长时间。

4. 分代回收算法

原理与背景:

分代回收(Generational Collection)算法基于"对象生命周期假说",即大部分对象在内存中的生命周期是很短的,而少部分对象则会长期存活。因此,JVM将堆内存划分为几代:新生代(Young Generation)、老年代(Old Generation)和永久代(在Java 8后被元空间取代)。

  • 新生代:大多数对象在新生代分配。当新生代的内存耗尽时,会触发"Minor GC",通常使用复制算法来回收内存。由于新生代中的对象大多数都是短命的,因此复制算法的效率很高。
  • 老年代:存活时间较长的对象会从新生代晋升到老年代。老年代的GC称为"Major GC"或"Full GC",通常使用标记-整理算法,因为老年代中存活对象较多,适合这种算法。
  • 永久代/元空间:永久代存放类的元数据,Java 8之后被移到元空间(Metaspace),不再由堆管理。

优点:

  • 适应对象生命周期:分代回收算法根据对象的生命周期特点优化了内存管理,避免了一刀切的回收策略。
  • 高效回收:新生代使用复制算法快速回收短命对象,老年代使用标记-整理算法处理长命对象,提高了整体GC的效率。

缺点:

  • 复杂性高:分代回收的策略需要对对象的生命周期进行判断和管理,增加了垃圾收集的复杂性。
  • 老年代GC成本高:虽然新生代GC频繁且快速,但老年代GC较为昂贵,可能导致应用程序停顿较长时间。

垃圾收集器

垃圾收集器是垃圾收集算法的具体实现,JVM中常见的垃圾收集器包括:

1.Serial收集器(复制算法)

Serial收集器是新生代的单线程收集器,所有的垃圾收集工作都是由一个线程完成的。虽然简单且在单核CPU上效率较高,但在多核环境下性能有限。

2.ParNew收集器(复制算法)

ParNew收集器是Serial收集器的多线程版本,适用于多核CPU环境,在并行执行垃圾收集时比Serial收集器更高效。

3.Parallel Scavenge收集器(复制算法)

Parallel Scavenge收集器也是新生代的并行收集器,但其目标是最大化吞吐量,即最小化垃圾回收时间与应用程序运行时间的比例。它是JDK1.8 默认收集器 ,适用于对响应时间要求不高的后台任务。

4.Serial Old收集器(标记-整理算法)

Serial Old收集器是Serial收集器在老年代的实现,依旧是单线程,但适用于老年代对象的垃圾回收。

5.Parallel Old收集器(标记-整理算法)

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,同样追求吞吐量优先,适用于大规模并发任务。

6.CMS(Concurrent Mark Sweep)收集器(标记-清除算法)

CMS收集器的目标是最小化垃圾回收的停顿时间,它通过多线程并行执行标记和清除,适用于需要低停顿时间的应用,如交互式应用。

7.G1(Garbage First)收集器(标记-整理算法)

G1收集器是JDK 1.7引入的并行垃圾收集器,基于标记-整理算法,避免了内存碎片问题。G1的一个显著特点是,它可以回收整个Java堆(包括新生代和老年代),适用于需要处理大堆内存的应用程序。

通过合理选择垃圾收集器和算法,可以有效提升Java应用程序的性能,满足不同应用场景的需求。

相关推荐
guangzhi06332 小时前
JVM垃圾回收器
jvm
guangzhi06332 小时前
JVM本地方法栈
java·jvm·面试
健康平安的活着2 小时前
JVM 调优篇7 调优案例4- 线程溢出
jvm
懵懵懂懂程序员2 小时前
JVM堆外泄露分析&解决
jvm
✞༒小郑同学༒✞10 小时前
简单了解 JVM
jvm
Flying_Fish_roe10 小时前
JVM 性能优化与调优-ZGC(Z Garbage Collector)
jvm·性能优化
一般路过糸.15 小时前
【JVM】判断对象能否回收的两种方法:引用计数算法,可达性分析算法
java·jvm·算法
程序员清风15 小时前
JVM面试真题总结(八)
jvm·面试·职场和发展
玉成22615 小时前
JVM: JDK内置命令 - JPS
java·开发语言·jvm
吴半杯15 小时前
jvisualvm工具使用-jvm本地调优(一)
java·linux·jvm