【JVM】Java虚拟机(二)——垃圾回收

目录

一、如何判断对象可以回收

(一)引用计数法

(二)可达性分析算法

二、垃圾回收算法

(一)标记清除

(二)标记整理

(三)复制

(四)分代垃圾回收

[(五)相关 VM 参数](#(五)相关 VM 参数)

三、垃圾回收器

(一)串行(Serial)

(二)吞吐量优先

(三)响应时间优先

(四)G1

核心特点

[(一)G1 垃圾回收阶段](#(一)G1 垃圾回收阶段)

(二)核心阶段详解

[(1) 年轻代GC(Young GC)](#(1) 年轻代GC(Young GC))

[(2) 并发标记周期](#(2) 并发标记周期)

[(3) 混合回收(Mixed GC)](#(3) 混合回收(Mixed GC))

(三)G1关键调优参数

基础配置

区域配置

并发周期控制

四、垃圾回收调优

(一)调优领域

(二)确定目标

[(三)最快的 GC](#(三)最快的 GC)

(四)新生代调优

(五)老年代调优


一、如何判断对象可以回收

(一)引用计数法

给对象中添加一个引用计数器:

  • 每当有一个地方引用它,计数器就加 1;
  • 当引用失效,计数器就减 1;
  • 任何时候计数器为 0 的对象就是不可能再被使用的。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间循环引用的问题。

所谓对象之间的相互引用问题,如下面代码所示:除了对象 objAobjB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。

java 复制代码
public class ReferenceCountingGc {
    Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGc objA = new ReferenceCountingGc();
        ReferenceCountingGc objB = new ReferenceCountingGc();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
    }
}

(二)可达性分析算法

Java通过GC Roots对象作为起点,向下搜索引用链。若对象与GC Roots无引用链相连,则判定为可回收。

GC Roots包括

  • 虚拟机栈中引用的对象(如局部变量)

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 本地方法栈中JNI引用的对象

  • 同步锁持有的对象

  • 活跃线程对象

  • 引用类型与回收策略

    引用类型 回收条件 示例
    强引用 永不回收(除非显式置为null Object obj = new Object()
    软引用(Soft) 内存不足时回收 SoftReference<Object> ref
    弱引用(Weak) 下次GC必然回收 WeakReference<Object> ref
    虚引用(Phantom) 仅用于回收跟踪 PhantomReference<Object> ref
  • 对象死亡过程

  1. 强引用
  • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
  1. 软引用(SoftReference)
  • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
  • 可以配合引用队列来释放软引用自身
  1. 弱引用(WeakReference)
  • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
  • 可以配合引用队列来释放弱引用自身
  1. 虚引用(PhantomReference)
  • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队, 由 Reference Handler 线程调用虚引用相关方法释放直接内存
  1. 终结器引用(FinalReference)
  • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象 暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象

二、垃圾回收算法

(一)标记清除

定义: Mark Sweep

  • 速度较快
  • 会造成内存碎片

(二)标记整理

定义:Mark Compact

  • 速度慢
  • 没有内存碎片

(三)复制

定义:Copy

  • 不会有内存碎片
  • 需要占用双倍内存空间

(四)分代垃圾回收

  • 年轻代(Young Generation)

    • 对象分配:新对象在Eden区创建

    • 回收算法:复制算法(Minor GC)

    • 过程

      1. Eden(伊甸园) + S0(From)存活对象复制到S1(To)

      2. 清空Eden(伊甸园)和S0(From)

      3. S0(From)与S1(To)角色交换

    • 晋升条件:对象年龄(经历GC次数)达到阈值(默认15),或Survivor区空间不足

  • 老年代(Old Generation)

    • 存放长期存活对象

    • 回收算法:标记-清除或标记-整理(Major GC/Full GC)

    • 触发条件:年轻代晋升失败或空间不足

  • 对象首先分配在伊甸园区域
  • 新生代空间不足时,触发 Minor GC,伊甸园和 From 存活的对象使用copy复制到 To 中,存活的对象年龄加1并且交换 From 和 To
  • Minor GC 会引发 Stop The World,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
  • 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
  • 当老年代空间不足,会先尝试触发 Minor GC,如果之后空间仍不足,那么触发 Full GC,STW的时间更长
算法 原理 优点 缺点
标记-清除 1. 标记所有活动对象 2. 清除未标记对象 实现简单 内存碎片化,分配效率低
复制 内存分为两块,每次使用一块。GC时将存活对象复制到另一块,清空当前块。 无碎片,高效 内存利用率仅50%
标记-整理 1. 标记活动对象 2. 将存活对象向内存一端移动 3. 清理边界外内存 无碎片,内存利用率高 对象移动开销大
分代收集 结合多种算法,按对象生命周期分区管理 综合性能最优(主流方案) 实现复杂

(五)相关 VM 参数

含义 参数
堆初始大小 -Xms
堆最大大小 -Xmx 或 -XX:MaxHeapSize=size
新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )
幸存区比例(动态) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
幸存区比例 -XX:SurvivorRatio=ratio
晋升阈值 -XX:MaxTenuringThreshold=threshold
晋升详情 -XX:+PrintTenuringDistribution
GC详情 -XX:+PrintGCDetails -verbose:gc
FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC

三、垃圾回收器

  1. 串行(Serial)
  • 单线程
  • 堆内存较小,适合个人电脑
  1. 吞吐量优先(Parallel Scavenge)
  • 多线程
  • 堆内存较大,多核 cpu
  • 让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高
  1. 响应时间优先(CMS)
  • 多线程
  • 堆内存较大,多核 cpu
  • 尽可能让单次 STW 的时间最短 0.1 0.1 0.1 0.1 0.1 = 0.5

(一)串行(Serial)

-XX:+UseSerialGC = Serial + SerialOld

(二)吞吐量优先(Parallel Scavenge)

-XX:+UseParallelGC ~ -XX:+UseParallelOldGC

-XX:+UseAdaptiveSizePolicy

-XX:GCTimeRatio=ratio

-XX:MaxGCPauseMillis=ms

-XX:ParallelGCThreads=n

(三)响应时间优先(CMS)

-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld

-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads

-XX:CMSInitiatingOccupancyFraction=percent

-XX:+CMSScavengeBeforeRemark

(四)G1

G1 (Garbage-First) 是Java 7引入、Java 9成为默认的垃圾回收器,专为大内存、低延迟场景设计,替代了传统的CMS回收器。

核心特点

  • 分区模型:将堆划分为多个等大小的Region(默认约2048个)

  • 分代收集:保留新生代/老年代概念,但物理不连续

  • 可预测停顿 :通过-XX:MaxGCPauseMillis设置目标停顿时间

  • 并发标记:与应用程序线程并行工作

  • 混合回收:同时清理新生代和老年代区域

(一)G1 垃圾回收阶段

Young Collection

  • 会 STW

Young Collection + CM

  • 在 Young GC 时会进行 GC Root 的初始标记
  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定

-XX:InitiatingHeapOccupancyPercent=percent (默认45%)

Mixed Collection

会对 E、S、O 进行全面垃圾回收

  • 最终标记(Remark)会 STW
  • 拷贝存活(Evacuation)会 STW

-XX:MaxGCPauseMillis=ms

(二)核心阶段详解

(1) 年轻代GC(Young GC)
  • 触发条件:Eden区满时

  • 过程

    1. STW(Stop-The-World)开始

    2. 构造回收集(Collection Set = Eden + Survivor)

    3. 复制存活对象到新Survivor区

    4. 年龄达到阈值(默认15)的对象晋升老年代

(2) 并发标记周期
  • 触发条件:老年代占用超过IHOP阈值(默认45%)

  • 阶段组成

    • 初始标记(STW):伴随Young GC进行

    • 根区域扫描:扫描Survivor区引用

    • 并发标记:标记存活对象(与应用并发)

    • 最终标记(STW):处理SATB缓冲区

    • 清理(STW):统计完全空闲Region

(3) 混合回收(Mixed GC)
  • 回收包含年轻代Region + 高收益的老年代Region

  • 根据垃圾比例排序Region(Garbage-First原则)

  • 多次执行直到满足回收目标

(三)G1关键调优参数

基础配置
参数 默认值 说明
-XX:+UseG1GC - 启用G1回收器
-Xms / -Xmx - 堆初始/最大大小(建议设相同值)
-XX:MaxGCPauseMillis 200ms 目标最大停顿时间
区域配置
参数 默认值 说明
-XX:G1HeapRegionSize 1-32MB Region大小(2的幂次)
-XX:G1NewSizePercent 5% 新生代最小占比
-XX:G1MaxNewSizePercent 60% 新生代最大占比
并发周期控制
参数 默认值 说明
-XX:InitiatingHeapOccupancyPercent 45% IHOP阈值(触发并发标记)
-XX:G1MixedGCLiveThresholdPercent 85% Region存活对象低于此值才回收
-XX:G1HeapWastePercent 5% 可回收垃圾占比阈值(停止Mixed GC)

四、垃圾回收调优

(一)调优领域

  1. 内存
  2. 锁竞争
  3. cpu
  4. 占用 io

(二)确定目标

  • 【低延迟】还是【高吞吐量】,选择合适的回收器
  • CMS,G1,ZGC
  • ParallelGC
  • Zing

(三)最快的 GC

答案是不发生 GC

查看 FullGC 前后的内存占用,考虑下面几个问题

数据是不是太多?

  • resultSet = statement.executeQuery("select * from 大表 limit n")

数据表示是否太臃肿?

  • 对象图
  • 对象大小 16 Integer 24 int 4

是否存在内存泄漏?

  • static Map map =
  • 第三方缓存实现

(四)新生代调优

  • 新生代的特点

    • 所有的 new 操作的内存分配非常廉价
      • TLAB thread - local allocation buffer
    • 死亡对象的回收代价是零
    • 大部分对象用过即死
    • Minor GC 的时间远远低于 Full GC
  • 越大越好吗?

  • 新生代能容纳所有【并发量*(请求 - 响应)】的数据

  • 幸存区大到能保留【当前活跃对象 + 需要晋升对象】

  • 晋升阈值配置得当,让长时间存活对象尽快晋升

    • -XX:MaxTenuringThreshold = threshold
    • -XX:+PrintTenuringDistribution
    java 复制代码
    Desired survivor size 48286924 bytes, new threshold 10 (max 10)
    
    age 1: 28992024 bytes, 28992024 total
    age 2: 1366864 bytes, 30358888 total
    age 3: 1425912 bytes, 31784800 total ...

(五)老年代调优

以 CMS 为例

  • CMS 的老年代内存越大越好
  • 先尝试不做调优,如果没有 Full GC 那么已经...,否则先尝试调优新生代
  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
    • -XX:CMSInitiatingOccupancyFraction=percent
相关推荐
pan_junbiao30 分钟前
Spring框架的设计模式
java·spring·设计模式
远方160931 分钟前
0x-2-Oracle Linux 9上安装JDK配置环境变量
java·linux·oracle
北执南念36 分钟前
CompletableFuture+线程池使用案列
java
黄交大彭于晏1 小时前
发送文件脚本源码版本
java·linux·windows
钮钴禄·爱因斯晨1 小时前
Java 面向对象进阶之多态:从概念到实践的深度解析
java·开发语言·数据结构
鸽子炖汤1 小时前
Java中==和equals的区别
java·开发语言·jvm
hstar95272 小时前
二、即时通讯系统设计经验
java·架构
风象南2 小时前
SpringBoot的4种死信队列处理方式
java·spring boot·后端
互联网全栈架构3 小时前
遨游Spring AI:第一盘菜Hello World
java·人工智能·后端·spring
优秀的颜4 小时前
计算机基础知识(第五篇)
java·开发语言·分布式