当前位置: 首页 > article >正文

深入理解 JVM 垃圾回收机制

在 Java 开发领域,JVM(Java 虚拟机)的垃圾回收机制是保障程序高效稳定运行的关键环节。它自动处理内存管理中繁琐且易错的垃圾回收任务。

一、垃圾回收的基本概念

在程序运行过程中,会不断创建各种对象,这些对象占用内存空间。当某些对象不再被程序使用时,它们所占用的内存就需要被回收,以便重新分配给其他新创建的对象。垃圾回收机制就是 JVM 自动识别并回收这些 “垃圾” 对象内存的过程。

那么,JVM 如何判断一个对象是垃圾呢?这里主要有两种方法:

引用计数算法

每个对象都有一个引用计数器,当对象被引用时,计数器加 1;当引用失效时,计数器减 1。当计数器值为 0 时,表示该对象不再被引用,可以被回收。然而,这种算法存在循环引用的问题,例如:

public class ReferenceCountingGC {
    public Object instance = null;

    public static void main(String[] args) {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();

        objA.instance = objB;
        objB.instance = objA;

        // 此时,objA 和 objB 相互引用,但实际上它们已经没有外部引用,应该被回收,但引用计数算法无法处理这种情况
        objA = null;
        objB = null;
    }
}

可达性分析算法

JVM 中的垃圾回收器主要采用可达性分析算法来确定对象是否为垃圾。该算法以一系列称为 “GC Roots” 的根对象作为起始点,从这些根对象开始向下搜索,搜索所走过的路径称为引用链。如果一个对象到 GC Roots 没有任何引用链相连,那么这个对象就是不可达的,可被判定为垃圾对象。GC Roots 通常包括以下几种对象:

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

例如:

public class ReachabilityAnalysisGC {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = new Object();

        // 将 obj2 赋值给 obj1 的成员变量,形成引用关系
        obj1 = obj2;

        // 此时,之前创建的第一个 Object 对象由于没有任何引用链可达 GC Roots,

将被判定为垃圾对象
    }
}

二、常见的垃圾回收算法

标记 - 清除算法(Mark-Sweep)

这是最基础的垃圾回收算法。它分为两个阶段:首先标记出所有需要回收的对象,然后统一回收被标记的对象。其优点是简单直接,缺点也很明显。标记和清除过程效率不高,而且清除后会产生大量不连续的内存碎片,当后续需要分配较大对象时,可能无法找到足够连续的内存空间,导致不得不提前触发另一次垃圾回收。

复制算法(Copying)

将内存划分为大小相等的两块,每次只使用其中一块。当这一块内存用完后,就将存活的对象复制到另一块内存中,然后一次性清理掉使用过的那块内存。该算法的优点是简单高效,没有内存碎片问题,因为每次都是复制存活对象到新的连续内存空间。但它的代价是将可用内存缩小为原来的一半,对于内存资源紧张的情况不太友好。在新生代中,由于大部分对象都是 “朝生夕死”,存活对象较少,所以比较适合使用复制算法,例如 Sun HotSpot 虚拟机的新生代就采用了这种算法,将新生代划分为 Eden 区和两个 Survivor 区(通常比例为 8:1:1)。

标记 - 整理算法(Mark-Compact)

结合了标记 - 清除算法和复制算法的优点。首先标记出所有存活对象,然后将所有存活对象向一端移动,最后直接清理掉边界以外的内存。这样既解决了标记 - 清除算法的内存碎片问题,又不需要像复制算法那样牺牲一半的内存空间。适用于老年代,因为老年代中的对象存活率较高,如果使用复制算法成本太高。

分代收集算法(Generational Collection)

根据对象的存活周期将内存划分为不同的代,一般分为新生代和老年代。新生代中对象存活率低,采用复制算法进行垃圾回收;老年代中对象存活率高,采用标记 - 整理算法或标记 - 清除算法进行垃圾回收。这种算法充分利用了不同代对象的特点,提高了垃圾回收的效率。

三、JVM 中的垃圾回收器

JVM 中有多种垃圾回收器,它们各有特点,适用于不同的应用场景。

Serial 回收器

这是一个单线程的垃圾回收器,在进行垃圾回收时会暂停所有用户线程(“Stop The World”)。它的优点是简单高效,对于单 CPU 环境或者小型应用来说,由于没有线程切换的开销,性能表现不错。适用于客户端模式下的 JVM,例如一些简单的桌面应用程序。

ParNew 回收器

是 Serial 回收器的多线程版本,在新生代采用复制算法进行垃圾回收。它能够充分利用多 CPU 的优势,提高垃圾回收的效率。通常与 CMS 回收器配合使用,在新生代使用 ParNew 回收器,老年代使用 CMS 回收器,适用于多 CPU 环境下的服务器应用。

Parallel Scavenge 回收器

也是一个新生代垃圾回收器,采用复制算法。它的特点是关注系统的吞吐量,即单位时间内 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值。通过参数可以精确控制吞吐量的大小,适合在后台运算而不需要太多交互的任务,例如一些科学计算、数据处理等批处理任务。

Serial Old 回收器

Serial 回收器的老年代版本,是一个单线程的、采用标记 - 整理算法的垃圾回收器。主要用于在客户端模式下与 Serial 回收器搭配使用,或者在服务端模式下作为 CMS 回收器的后备预案,在并发收集发生 “Concurrent Mode Failure” 时使用。

Parallel Old 回收器

Parallel Scavenge 回收器的老年代版本,采用标记 - 整理算法。在注重吞吐量的应用场景中,与 Parallel Scavenge 回收器配合使用,能够在新生代和老年代都实现较高的吞吐量。

CMS 回收器(Concurrent Mark Sweep)

以获取最短回收停顿时间为目标的垃圾回收器。它的工作过程比较复杂,主要分为四个阶段:初始标记、并发标记、重新标记和并发清除。初始标记和重新标记阶段仍然需要暂停所有用户线程,但时间很短;并发标记和并发清除阶段与用户线程并发执行,大大减少了垃圾回收时应用的停顿时间。适用于对响应时间要求较高的应用,如 Web 服务器等。但它也有一些缺点,例如会产生内存碎片,并且在并发阶段会占用一定的 CPU 资源,影响应用的整体性能。

G1 回收器(Garbage First)

面向服务端应用的垃圾回收器,主要应用在多 CPU 和大内存的环境下。它将堆内存划分为多个大小相等的 Region,在进行垃圾回收时,优先回收垃圾最多的 Region。G1 回收器采用了标记 - 整理算法,避免了内存碎片问题,同时能够预测垃圾回收的停顿时间,通过参数可以指定一个期望的停顿时间,让垃圾回收在不影响应用响应时间的前提下进行。适用于大型的分布式系统、数据处理应用等对内存和响应时间都有较高要求的场景。

四、垃圾回收的调优策略

在实际应用中,为了让 JVM 的垃圾回收机制更好地适应业务需求,往往需要进行一些调优。

确定垃圾回收器

根据应用的类型、性能要求等因素选择合适的垃圾回收器。例如,如果是对响应时间敏感的 Web 应用,可能优先考虑 CMS 回收器或 G1 回收器;如果是注重吞吐量的批处理应用,则可以选择 Parallel Scavenge 回收器与 Parallel Old 回收器的组合。

调整堆内存大小

合理设置新生代和老年代的大小。一般来说,可以根据应用中对象的生命周期特点进行调整。如果新生代设置过小,会导致频繁的 Minor GC;如果老年代设置过小,容易引发 Full GC。可以通过参数 -Xms(初始堆大小)和 -Xmx(最大堆大小)来设置堆内存的初始值和最大值,以及 -Xmn 来设置新生代的大小。

控制对象晋升到老年代的年龄

对象在新生代中经过多次 Minor GC 后仍然存活,就会晋升到老年代。可以通过参数 -XX:MaxTenuringThreshold 来调整对象晋升到老年代的年龄阈值,根据应用中对象的存活情况进行优化,避免过早或过晚地将对象晋升到老年代。

优化 CMS 回收器

当使用 CMS 回收器时,可以调整一些参数来优化其性能。例如,-XX:CMSInitiatingOccupancyFraction 参数可以设置老年代使用达到多少百分比时触发 CMS 回收;-XX:+UseCMSCompactAtFullCollection 参数可以在 Full GC 后进行内存碎片整理,减少内存碎片的产生。

监控与分析

使用 JVM 提供的监控工具,如 jstat、jconsole、VisualVM 等,对垃圾回收的过程进行监控和分析。通过查看垃圾回收的频率、停顿时间、内存使用情况等指标,及时发现问题并进行调整。例如,如果发现 Full GC 过于频繁,可以进一步分析是内存泄漏导致对象无法回收,还是堆内存设置不合理等原因。


http://www.kler.cn/a/446487.html

相关文章:

  • ViEW生命周期
  • Edge Scdn防御网站怎么样?
  • ios 混合开发应用白屏问题
  • 手机便签哪个好用?手机桌面便签app下载推荐
  • 我的工作会被AI替代吗?
  • 个人秋招总结
  • Neo4j【环境部署 02】图形数据库Neo4j在Linux系统ARM架构下的安装使用
  • MacPorts 中安装高/低版本软件方式,以 RabbitMQ 为例
  • 《基于 Python 的网页爬虫详细教程》
  • 便捷就医新引擎:SSM 医院预约挂号系统 Vue 实现方案设计
  • wpf mvvm 数据绑定数据(按钮文字表头都可以),根据长度进行换行,并把换行的文字居中
  • 利用Python爬虫快速获取商品历史价格信息
  • SSM+Vue 驱动的电脑测评系统:诠释科技评测新高度
  • 开源云原生数据仓库ByConity ELT 的测试体验
  • [每周一更]-(第128期):CentOS源码安装PostgreSQL
  • vue-router的详细安装及配置
  • 2024年11月 蓝桥杯青少组 STEMA考试 Scratch真题
  • 12.13-12.21 刷题汇总
  • 活动预告|云原生创新论坛:知乎携手 AutoMQ、OceanBase、快猫星云的实践分享
  • 用SparkSQL和PySpark完成按时间字段顺序将字符串字段中的值组合在一起分组显示
  • mac 安装graalvm
  • 【Http,Netty,Socket,WebSocket的应用场景和区别】
  • CESS 出席华盛顿区块链政策峰会:参与国家安全与数据隐私保护专题讨论
  • BOE(京东方)“向新2025”年终媒体智享会首站落地上海 六大维度创新开启产业发展新篇章
  • 【HTML】DOCTYPE的作用?
  • SAP RESTful架构和OData协议