谈谈JVM垃圾回收

 死亡对象判断

在Java虚拟机(JVM)中,判断对象是否已经“死亡”,即不再被任何线程访问的状态,是垃圾回收(GC)过程中的关键步骤。JVM采用两种主要方法来判定对象是否可回收:可达性分析(Reachability Analysis)和引用计数(Reference Counting)。主流的Java虚拟机实现,如HotSpot,主要使用可达性分析来判断对象是否存活。

1. 可达性分析(Reachability Analysis)

可达性分析是现代JVM使用的主要方法。在这种方法中,一系列称为“GC Roots”的对象作为起点进行搜索,如果某个对象到GC Roots没有任何引用链相连(即从GC Roots到这个对象不可达),则证明此对象是不可达的。

GC Roots 包括:
  • 虚拟机栈(局部变量表)中引用的对象:也就是当前局部变量表中的引用对象。
  • 方法区中类静态属性引用的对象:如Java类的引用类型静态变量。
  • 方法区中常量引用的对象:如字符串常量池(String Intern Pool)里的引用。
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

从这些GC Roots开始,JVM通过引用链向下搜索,如果一个对象到GC Roots没有任何引用链相连,则证明对象不可达。可达性分析算法的搜索路径并不是完全即时的,它需要一定的时间来执行,因此在判断对象是否存活时,所有通过引用链可达的对象都被初步认为是存活的。

2. 引用计数(Reference Counting)

引用计数是一种较早的垃圾回收算法。每个对象实例有一个引用计数器属性。当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1。任何时刻计数器为0的对象就是不再被使用的。

虽然引用计数法实现简单,判断效率较高,但它有一个致命的问题——无法处理循环引用。例如,对象A内部持有对象B的引用,对象B内部持有对象A的引用,即使它们已经不再被其他存活对象引用,但它们的引用计数永远不会是0,导致内存泄漏。

由于引用计数法的局限性,主流的JVM(如HotSpot)并不采用它作为垃圾回收的判断依据。相反,可达性分析以其准确性成为了JVM垃圾回收的主要判断方法。在可达性分析的基础上,现代JVM还引入了更复杂的算法,如分代收集(Generational Collection)和各种不同的垃圾收集器来优化内存回收过程,以适应不同的应用场景和性能需求。

垃圾回收算法

垃圾回收(Garbage Collection, GC)算法是垃圾回收机制中的核心,用于自动管理计算机程序的内存。以下是一些主要的垃圾回收算法及其特点:

1. 标记-清除(Mark-Sweep)

这是最基本的垃圾收集算法,分为两个阶段:标记和清除。

  • 标记阶段:遍历所有的GC Roots,标记所有从Roots可达的对象。
  • 清除阶段:扫描整个堆,回收所有未被标记的对象所占用的内存。

优点:实现简单,不需要移动对象。 缺点:两大问题是执行效率不高(特别是堆内存大时)和清除后会产生大量内存碎片。

2. 复制(Copying)

该算法将可用内存分为大小相等的两块,每次只使用其中一块。当这一块的内存用尽时,就将当前活动的对象复制到另一块上去,然后再清理已使用过的内存空间。

优点:没有标记-清除算法的内存碎片问题,且垃圾回收时只查看活动对象。 缺点:内存利用率只有一半,对系统资源要求较高。

3. 标记-整理(Mark-Compact)

该算法是对标记-清除算法的改进,分为标记、整理两个阶段。

  • 标记阶段:与标记-清除算法相同,标记所有从GC Roots可达的对象。
  • 整理阶段:将所有存活的对象压缩到内存的一端,然后直接清理边界以外的内存。

优点:解决了内存碎片问题,不牺牲内存利用率。 缺点:需要移动对象,更新对象的引用地址,耗时可能比标记-清除更长。

4. 分代收集(Generational Collection)

这不是一种单独的算法,而是基于一个事实:不同生命周期的对象应该采取不同的收集策略。JVM堆被分为年轻代(Young Generation)、老年代(Old Generation)和永久代(PermGen,Java 8之前)或元空间(Metaspace,Java 8及之后)。

  • 年轻代:大多使用复制算法,因为年轻代中对象存活率低。
  • 老年代:通常使用标记-清除或标记-整理算法,因为老年代中对象存活率高,且没有足够的空间进行复制收集。

垃圾回收器

现在常见的垃圾收集器有如下几种:

新生代收集器:Serial、ParNewPar、allel Scavenge
老年代收集器:Serial Old、CMS、Parallel Old
堆内存垃圾收集器:G1

在垃圾回收器中,主要是CMS和G1比较重要

CMS

CMS(Concurrent Mark Sweep)垃圾回收器是HotSpot JVM中一种以获取最短回收停顿时间为目标的回收器,适用于多核服务器环境中的应用,尤其是那些对响应时间有较高要求的应用。CMS主要用于收集老年代(Old Generation)的垃圾。

工作流程

CMS的工作过程比较复杂,主要分为以下几个阶段:

  1. 初始标记(Initial Mark)

    • 这个阶段需要“停止世界”(Stop-The-World),但是停顿时间很短。它标记了所有与GC Roots直接相连的对象,以及从年轻代对象可达的对象。
  2. 并发标记(Concurrent Mark)

    • 在这个阶段,CMS回收器遍历老年代,标记所有从初始标记阶段找到的对象可达的对象。这个阶段是并发执行的,不需要暂停应用线程。
  3. 重新标记(Remark)

    • 为了修正并发标记期间因应用程序继续运行而导致的变动,需要再次“停止世界”。使用多线程进行快速重新标记,是整个过程中最长的一次停顿,但通过算法优化(如增量更新)和多核处理器,尽量缩短这个阶段的停顿时间。
  4. 并发清除(Concurrent Sweep)

    • 在标记完成后,进行实际的垃圾清理工作,回收未被标记对象占用的空间。这个阶段也是并发执行的,不会停止应用线程。
  5. 并发重置(Concurrent Reset)

    • 清理内部数据结构,为下一次GC循环做准备。

特点

  • 优点

    • 并发收集:大部分工作与应用线程同时进行,减少了应用停顿时间。
    • 适合读多写少的应用场景,如Web服务器或查询密集型应用。
  • 缺点

    • 由于并发执行,CMS对CPU资源较为敏感,对于CPU资源受限的环境不是很适合。
    • 并发执行时,由于应用线程仍在运行,可能会产生浮动垃圾(Floating Garbage),即在GC过程中新产生的垃圾。
    • 容易产生内存碎片。CMS没有压缩过程,随着时间推移可能需要执行Full GC来压缩老年代空间。

使用场景

CMS适用于那些对停顿时间敏感、要求系统响应速度的应用,尤其是后端服务或者Web应用,这些应用需要尽可能减少GC带来的停顿影响。

G1

G1(Garbage-First)垃圾回收器是一种面向服务端应用的垃圾回收器,特别适用于多处理器和大容量内存环境。它于JDK 7中引入,旨在以高概率满足垃圾收集(GC)停顿时间目标,同时保持良好的吞吐量。G1是一种分代收集器,但与传统分代收集器(如Parallel GC和CMS)不同的是,它将堆内存划分为多个区域(Region),并以增量方式进行清理,从而达到更可预测的GC停顿时间。

G1工作原理

G1收集器的核心是基于区域的堆内存布局和停顿时间模型。它将Java堆划分为多个大小相等的独立区域(Region),每个区域可以是Eden区、Survivor区或Old区。G1通过维护一个优先列表,对区域中的垃圾多少进行排序,以决定哪些区域应该被优先回收。

G1的主要操作阶段包括:
  1. 初始标记(Initial Mark):这是第一个停止-世界(STW)阶段,标记从GC根直接可达的活动对象。它在一个Minor GC期间发生。

  2. 根区域扫描(Root Region Scanning):在这个阶段,G1 GC扫描从生存区域到老年代区域的引用。此阶段并发执行,不需要暂停应用线程。

  3. 并发标记(Concurrent Marking):G1 GC并发地标记整个堆中的活动对象。这个过程同应用线程同时运行,不会导致明显的停顿。

  4. 最终标记(Final Mark):这是另一个STW阶段,处理自上一个阶段以来变化的引用。

  5. 筛选回收(Evacuation):根据之前阶段的收集数据,G1会决定哪些Region是回收价值最大的,并在此STW阶段回收这些区域的空间。这个阶段也被称为复制阶段,因为它会将存活的对象复制到新的区域,同时清理和回收旧区域。

G1的特性

  • 可预测的停顿:G1允许用户指定期望停顿时间的目标(通过-XX:MaxGCPauseMillis参数),G1将努力遵守这个目标。

  • 并行与并发:G1能够利用多核心环境,同时执行多线程垃圾回收。一些阶段是并发执行的,与应用线程一起运行,减少停顿时间。

  • 分代收集:虽然G1跨越整个堆进行操作,但它仍然遵循分代垃圾收集的概念,特别关注年轻代和老年代的收集。

  • 空间整合:通过复制存活对象到新的区域,G1在每次垃圾收集后都能执行一定程度的空间整合,从而避免内存碎片问题。

使用G1的理由

G1垃圾回收器适用于需要大堆内存(超过4GB)和希望更可控、更短暂GC停顿时间的应用。对于希望在系统响应时间和吞吐量之间取得良好平衡的服务端应用,G1提供了一种高效的解决方案。

G1与CMS的区别:

  1. 设计目标

    • G1旨在提供一个更平衡的解决方案,针对大堆内存环境下提供高吞吐量的同时,保持可控的停顿时间。
    • CMS设计目标是尽可能减少垃圾收集时的停顿时间。
  2. 工作方式

    • G1是基于分区的垃圾收集器,可以并行和并发地进行垃圾回收,同时关注整个Java堆的垃圾回收。
    • CMS主要关注老年代的垃圾回收,采用标记-清除算法,存在内存碎片的问题。
  3. 停顿时间控制

    • G1允许用户指定停顿时间目标,并通过动态选择回收集的方式来尽量满足这个目标。
    • CMS努力减少停顿时间,但在遇到“并发模式失败”时会退化成全堆的Stop-The-World式的垃圾回收,停顿时间无法预测。
  4. 内存碎片处理

    • G1通过压缩存活对象来整理内存碎片,这一点在进行老年代回收时尤为明显。
    • CMS使用空闲列表来管理Java堆内存,容易产生碎片,可能需要执行Full GC来整理内存。
  5. 应用场景

    • G1适用于多核处理器和大内存环境,需要平衡高吞吐量与低延迟。
    • CMS适用于对响应时间有严格要求的应用,但在处理大堆或要求低停顿时间上不如G1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值