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

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

目录

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

(一)引用计数法

(二)可达性分析算法

二、垃圾回收算法

(一)标记清除

(二)标记整理

(三)复制

(四)分代垃圾回收

(五)相关 VM 参数

三、垃圾回收器

(一)串行(Serial)

(二)吞吐量优先

(三)响应时间优先

(四)G1

核心特点

(一)G1 垃圾回收阶段

(二)核心阶段详解

(1) 年轻代GC(Young GC)

(2) 并发标记周期

(3) 混合回收(Mixed GC)

(三)G1关键调优参数

基础配置

区域配置

并发周期控制

四、垃圾回收调优

(一)调优领域

(二)确定目标

(三)最快的 GC

(四)新生代调优

(五)老年代调优


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

(一)引用计数法

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

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

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

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

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引用的对象

  • 同步锁持有的对象

  • 活跃线程对象

  • 引用类型与回收策略

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

1. 强引用

  • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

2. 软引用(SoftReference)

  • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
  • 可以配合引用队列来释放软引用自身

3. 弱引用(WeakReference)

  • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
  • 可以配合引用队列来释放弱引用自身

4. 虚引用(PhantomReference)

  • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队, 由 Reference Handler 线程调用虚引用相关方法释放直接内存

5. 终结器引用(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)

  • 单线程
  • 堆内存较小,适合个人电脑

2. 吞吐量优先(Parallel Scavenge)

  • 多线程
  • 堆内存较大,多核 cpu
  • 让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高

3. 响应时间优先(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:MaxGCPauseMillis200ms目标最大停顿时间
区域配置
参数默认值说明
-XX:G1HeapRegionSize1-32MBRegion大小(2的幂次)
-XX:G1NewSizePercent5%新生代最小占比
-XX:G1MaxNewSizePercent60%新生代最大占比
并发周期控制
参数默认值说明
-XX:InitiatingHeapOccupancyPercent45%IHOP阈值(触发并发标记)
-XX:G1MixedGCLiveThresholdPercent85%Region存活对象低于此值才回收
-XX:G1HeapWastePercent5%可回收垃圾占比阈值(停止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
    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

相关文章:

  • jvm 垃圾收集算法 详解
  • WebRTC通话原理与入门难度实战指南
  • 探索C++标准模板库(STL):String接口的底层实现(下篇)
  • LinkedList、Vector、Set
  • Parameter ‘XXX‘ not found. Available parameters are [list, param1]
  • 【选配电脑】CPU核显工作机控制预算5000
  • 复制与图片文件同名的标签文件到目标路径
  • 广东餐饮服务中高级证备考指南:高效学习与应试技巧
  • 光学字符识别(OCR)理论概述与实践教程
  • 移除元素-JavaScript【算法学习day.04】
  • Redis 持久化机制深度解析
  • 第9篇:数据库中间件的容错机制与高可用架构设计
  • UOS无法安装deb软件包
  • ​​Android 如何查看CPU架构?2025年主流架构有哪些?​
  • 本地主机部署开源企业云盘Seafile并实现外部访问
  • 开源之夏·西安电子科技大学站精彩回顾:OpenTiny开源技术下沉校园,点燃高校开发者技术热情
  • 自动驾驶系统研发系列—从LSS到BEVFormer:视觉BEV感知算法的演进与实战部署思考
  • 判断一个或者多个软件是否安装,如果没有则自动安装
  • 嵌入式里的时间魔法:RTC 与 BKP 深度拆解
  • 《MODEM HOST INTERFACE》,第6章,MHl register interface
  • 遵义公司做网站找哪个公司好/谷歌推广效果怎么样
  • 福州网站制作建设/易搜搜索引擎
  • 嘉兴免费做网站/第三方网络营销平台有哪些
  • 购物网站开发案例教程/电子商务seo名词解释
  • 网站的三大标签/长春网站优化流程
  • 想找做拼接屏的公司去哪个网站/百度识图在线识别