JVM 中的完整 GC 流程

一、引言

在 Java 应用程序的运行过程中,垃圾回收是一个至关重要的环节。它负责自动管理内存,回收不再被使用的对象,以确保应用程序的稳定运行。了解 JVM 中一次完整的 GC 流程对于优化 Java 应用的性能、减少内存占用以及避免内存泄漏至关重要。本文将深入探讨 JVM 中的 GC 流程。

二、JVM 内存结构概述

(一)堆内存

  1. 新生代(Young Generation)
    • Eden 区:新创建的对象首先分配在 Eden 区。
    • Survivor 区:分为 From Survivor 和 To Survivor 两个区域,用于存放经过一次 Minor GC 后仍然存活的对象。
  2. 老年代(Old Generation):存放经过多次 Minor GC 后仍然存活的对象。

(二)方法区(Metaspace)

存储类信息、常量、静态变量等数据。

(三)程序计数器、虚拟机栈和本地方法栈

用于存储线程的执行状态和局部变量等信息。

三、GC 类型

(一)Minor GC

  1. 触发条件
    • 当 Eden 区满时,触发 Minor GC。
  2. 作用范围
    • 主要清理新生代中的垃圾对象。

(二)Major GC/Full GC

  1. 触发条件
    • 老年代空间不足时触发 Major GC 或 Full GC。
    • 永久代(在 Java 8 后被 Metaspace 替代)空间不足时也可能触发 Full GC。
    • 显示调用 System.gc () 时可能触发 Full GC,但不建议在生产环境中使用。
  2. 作用范围
    • 清理整个堆内存,包括新生代和老年代。

四、Minor GC 流程

(一)标记阶段

  1. 可达性分析
    • 从根对象(如线程栈中的局部变量、静态变量等)开始,通过引用链遍历所有可达的对象。
    • 不可达的对象被标记为垃圾。
  2. 三色标记法
    • 白色:表示未被访问过的对象。
    • 灰色:表示对象已经被访问过,但它的引用还没有被完全处理。
    • 黑色:表示对象已经被访问过,并且它的引用也已经被完全处理。

(二)复制阶段

  1. 将 Eden 区和 From Survivor 区中存活的对象复制到 To Survivor 区。
  2. 如果对象的年龄达到一定阈值(默认是 15),则将其晋升到老年代。

(三)清理阶段

  1. 清理 Eden 区和 From Survivor 区中的垃圾对象。
  2. 将 From Survivor 区和 To Survivor 区互换角色,为下一次 Minor GC 做准备。

五、Major GC/Full GC 流程

(一)标记阶段

  1. 与 Minor GC 的标记阶段类似,采用可达性分析和三色标记法对整个堆内存中的对象进行标记。
  2. 由于老年代中的对象通常比较多,标记过程可能会比较耗时。

(二)整理阶段

  1. 对于老年代中的垃圾对象,进行清理。
  2. 可能会对存活的对象进行整理,以减少内存碎片。整理的方式可以是移动存活的对象,使它们连续存储。

(三)Metaspace 的清理(如果需要)

  1. 如果 Metaspace 空间不足,也可能触发 Full GC,此时会对 Metaspace 中的无用类信息等进行清理。

六、GC 触发条件的详细分析

(一)堆内存使用情况

  1. 新生代空间不足
    • 当 Eden 区和 Survivor 区中的对象占用空间超过一定比例时,触发 Minor GC。
    • 可以通过调整 JVM 参数来控制新生代的大小和比例,如 -Xmn 用于设置新生代的大小。
  2. 老年代空间不足
    • 当老年代中的对象占用空间超过一定比例时,触发 Major GC 或 Full GC。
    • 可以通过调整 JVM 参数来控制老年代的大小,如 -Xms 和 -Xmx 用于设置堆内存的初始大小和最大大小。

(二)对象的生命周期

  1. 对象的年龄增长
    • 对象在新生代中经过一次 Minor GC 后仍然存活,它的年龄会增加。当对象的年龄达到一定阈值时,会被晋升到老年代。
    • 可以通过调整 JVM 参数 -XX:MaxTenuringThreshold 来控制对象晋升到老年代的年龄阈值。
  2. 大对象直接进入老年代
    • 如果创建的对象占用空间较大,可能会直接进入老年代。可以通过调整 JVM 参数 -XX:PretenureSizeThreshold 来控制大对象的大小阈值。

(三)其他触发因素

  1. System.gc () 的调用
    • 在代码中显式调用 System.gc () 可能会触发 Full GC,但不建议在生产环境中使用,因为它会影响应用程序的性能。
  2. JVM 自身的策略
    • JVM 可能会根据一些内部策略触发 GC,如为了避免内存溢出等情况。

七、GC 算法详解

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

  1. 算法原理
    • 标记阶段:通过可达性分析标记出所有存活的对象。
    • 清除阶段:清理所有未被标记的对象,释放内存空间。
  2. 优缺点
    • 优点:实现简单。
    • 缺点:会产生内存碎片,可能导致后续分配大对象时需要进行额外的整理操作。

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

  1. 算法原理
    • 标记阶段:与标记 - 清除算法相同。
    • 整理阶段:将所有存活的对象移动到一端,然后清理另一端的垃圾对象,从而避免内存碎片的产生。
  2. 优缺点
    • 优点:不会产生内存碎片。
    • 缺点:整理过程比较耗时,可能会影响应用程序的性能。

(三)复制算法(Copying)

  1. 算法原理
    • 将内存分为两块相等的区域,如新生代中的 Eden 区和 Survivor 区。当进行垃圾回收时,将存活的对象复制到另一块区域,然后清理原来的区域。
  2. 优缺点
    • 优点:实现简单,不会产生内存碎片。
    • 缺点:需要双倍的内存空间,当对象存活率较高时,复制操作会比较耗时。

八、实际案例分析

(一)案例背景

假设有一个 Java 应用程序,在运行过程中出现了频繁的 Full GC,导致应用程序性能下降。

(二)问题分析

  1. 通过监控工具(如 JVisualVM、jstat 等)观察堆内存的使用情况,发现老年代空间不足是触发 Full GC 的主要原因。
  2. 进一步分析发现,应用程序中存在一些大对象的创建,这些大对象直接进入老年代,导致老年代空间快速增长。
  3. 同时,应用程序中的某些对象的生命周期较长,经过多次 Minor GC 后仍然存活,最终晋升到老年代,也加剧了老年代空间的压力。

(三)解决方案

  1. 调整 JVM 参数
    • 增大堆内存的大小,如 -Xms 和 -Xmx,可以缓解老年代空间不足的问题,但要注意不要设置得过大,以免导致系统资源浪费。
    • 调整新生代和老年代的比例,如 -XX:NewRatio,可以适当增大新生代的空间,减少对象晋升到老年代的频率。
    • 调整对象晋升到老年代的年龄阈值,如 -XX:MaxTenuringThreshold,可以根据应用程序的实际情况适当降低年龄阈值,避免对象过早晋升到老年代。
  2. 优化对象创建
    • 避免创建不必要的大对象,如果确实需要创建大对象,可以考虑采用分块处理的方式,减少大对象对老年代的压力。
  3. 对象生命周期管理
    • 对于生命周期较长的对象,可以考虑采用对象池等技术,避免频繁地创建和销毁对象,减少对象晋升到老年代的机会。

九、GC 优化策略

(一)合理设置 JVM 参数

  1. 根据应用程序的特点和需求,合理设置堆内存的大小、新生代和老年代的比例、对象晋升年龄阈值等参数。
  2. 可以通过压力测试和性能监控来调整 JVM 参数,找到最适合应用程序的参数组合。

(二)对象生命周期管理

  1. 尽量减少不必要的对象创建,避免创建大量短期存活的对象,减少 Minor GC 的频率。
  2. 对于生命周期较长的对象,可以采用对象池等技术进行管理,提高对象的复用率。

(三)避免内存泄漏

  1. 及时释放不再使用的对象引用,避免内存泄漏。
  2. 对资源的使用(如数据库连接、文件流等)要及时关闭,防止资源泄漏导致内存占用过高。

(四)选择合适的 GC 算法

  1. 根据应用程序的特点选择合适的 GC 算法。例如,如果应用程序对响应时间要求较高,可以选择并发收集器;如果应用程序对吞吐量要求较高,可以选择并行收集器。
  2. 在 Java 8 及以上版本中,可以使用 G1 收集器,它在兼顾吞吐量和响应时间方面表现较好。

十、总结

JVM 中的垃圾回收是一个复杂而重要的过程。了解一次完整的 GC 流程对于优化 Java 应用程序的性能至关重要。通过合理设置 JVM 参数、管理对象生命周期、避免内存泄漏以及选择合适的 GC 算法,可以有效地减少 GC 的频率和时间,提高应用程序的性能和稳定性。

相关推荐
魔道不误砍柴功44 分钟前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
冰芒猓1 小时前
SpringMVC数据校验、数据格式化处理、国际化设置
开发语言·maven
失落的香蕉1 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v1 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge1 小时前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@1 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄1 小时前
SpringBoot
java·spring
红中马喽1 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
数据小小爬虫1 小时前
如何用Java爬虫“偷窥”淘宝商品类目API的返回值
java·爬虫·php
暮春二十四1 小时前
关于用postman调用接口成功但是使用Java代码调用却失败的问题
java·测试工具·postman