垃圾收集

195 阅读7分钟

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

概述

优缺点

优点:

  • 不需要考虑内存管理
  • 可以有效防止内存泄露,有效使用内存
  • 对象不再有作用域, 对象的引用有作用域

缺点:

  • 过度依赖降低解决内存溢出和内存泄露问题的能力

算法

引用计数

给每个创建的对象添加引用计数器, 被引用,计数+1, 失效时计数-1, 为0表示不能被使用。

优点:

  • 实现简单,执行效率高,很好的和程序交织

缺点:

  • 无法检测循环引用。

可达性分析

通过一系列GCRoot对象作为起始点, 从起始点开始向下搜索对象的路径, 搜索所经过的路径称为引用链。

可以作为GC Root的对象

  • 栈帧中局部变量的reference引用所引用的队列
  • 方法区中static静态引用的对象
  • 方法区中final常量引用的对象
  • JNI引用的对象
  • 虚拟机内部引用,如基础数据类型的Class,NPE, 系统类加载器等
  • 所有被同步锁持有的对象
  • 反映虚拟机内部的JMXBean, JVMTI注册的回调, 本地代码缓存等。

引用

强引用

垃圾回收器不会回收,内存不足, 抛出OOM

软引用

内存空间足够不会回收, 不足就会回收。软引用和引用队列(ReferenceQueue)联合使用, 如果对象被回收,JVM将这个软引用加入到引用队列中。

弱引用

只能生存到下一次垃圾收集发生。无论内存释放足够,都会被回收。弱引用可以和一个引用队列(ReferenceQueue)联合使用, 如果对象被回收,JVM将这个弱引用加入到引用队列中。

虚引用

在任何时刻都可能被垃圾回收器回收,主要用来跟踪对象被回收的活动。虚引用必须和引用队列联合使用, 如果回收时发现有虚引用, 在回收之前, 将这个虚引用加入到引用队列中

垃圾收集算法

分代收集理论

根据生命周期将内存划分,进行分区管理,建立在两个分代假说上面

  • 弱分代假说: 绝大多数对象都是朝生夕灭
  • 强分代假说: 熬过多次垃圾手机过程的对象就越难以消亡

根据不同区域的特点, 划分出Minor GC, Major GC, Full GC回收类型。针对不同的区域和对象存亡特征,发展出标记-复制,标记-清除, 标记-整理算法。

标记清除算法

标记-清除不足:

  • 执行效率不稳定
  • 内存碎片

标记复制算法

也成为复制算法或者半区复制。

复制算法不足:

  • 需要提前预留一半区域用来存放存活对象, 对象区域减少一半,整体GC频繁
  • 如果存活对象较多,成本上升, 效率降低

标记整理算法

移动回收后的存活对象, 不移动停顿短,移动吞吐量高。

垃圾收集器

垃圾收集器组合

JDK8中默认是 Parallel Scavenge和 Parallel Old

JDK 9默认G1

JDK14 弃用了Parallel Scavenge和 Parallel Old, 移除了CMS

组合方式有:

  • SerialGC + CMS GC ( 不建议)
  • SerialGC + Serial old
  • ParNew + CMS
  • ParNew + serial old(不建议)
  • CMS + serial old
  • Parallel Scavenge + Parallel Old
  • Parallel Scavenge + serial Old(不建议)
  • G1

性能指标

  • 吞吐量: cpu在用户代码的时间和Cpu在总消耗时间
  • 暂停时间
  • 内存占用
  • 收集频率

Serial 收集器

单线程, 使用一个CPU或1个收集线程。采用复制算法, serial old采用标记整理算法。发生STW。

-XX:+UseSerialGC
-XX:+PrintCommandLineFlags

ParNew收集器

多线程并行回收, 其余与Serial 一样。

-XX:+UseParNewGC
-XX:ParllGCThreads

Parallel Scavenge收集器

吞吐量优先收集器, 新生代收集器, 复制算法的并行多线程收集器。

  • 目标是达到一个可控制的吞吐量
  • 自适应调节策略, 自动指定年轻代,Eden,SurVivor区比例
-XX:+UseParallelGC
## 最大垃圾收集停顿时间 毫秒数 不推荐
-XX:MaxGCPauseMillis
## 吞吐量大小, 大于0小于100,垃圾收集时间占总时间的大小,默认99, 也就是说允许最大1% 
-XX:GCTimeRatio
## 年轻代线程数, 当cpu小于等于8, 默认cpu核数,超过8 设置 3+(5*CPU)/8
XX:ParllGCThreads
##  自适应
-XX:+UseAdaptiveSizePolicy

Serial Old收集器

单线程老年代, 标记整理, cms后备

-XX:+UseSerialGC

Parallel Old收集器

并发多线程老年代, 标记整理。

-XX:+UseParallelOldGC

CMS收集器

最短停顿时间垃圾收集, 标记清除

分为四个步骤:

  • 初始标记, STW
  • 并发标记
  • 重新标记, STW
  • 并发清除

三色标记

白色: 尚未访问过

黑色: 已经访问过,本对象的引用到的其他对象也已经全部访问过

灰色: 本对象已经访问过, 但是本对象引用的其他对象尚未访问完

在并发标记时,可能有多标和漏标。

多标会产生浮动垃圾。

漏标可能影响应用程序的准确性, 可以将漏标的对象记录下来,然后作为灰色对象重新访问。

缺点

  • 并发会导致应用程序变慢,吞吐量降低。CMS默认启动的回收线程数是(处理器核心+ 3)/4

  • 无法处理浮动垃圾, 可能出现Concurrent Mode Failure失败而导致Full GC 的产生

    JDK5, 使用老年代68%就会进行回收, JDK6使用92%会进行回收,可以通过-XX: CMSInitiationOccu-pancyFraction的值来调整百分比。

    并发失败一般有两个原因: 新生代晋升失败和浮动垃圾

  • 空间碎片

    +XX: +UseCMS-CompactAtFullCollection开关参数,默认开启,jdk9废弃, 在CMS 收集器不得不进行FUll GC时开启内存碎片的整理过程。 +XX: CMSFullGCsBeforeCompaction, 要求CMS收集器在执行若干次不整理空间的Full GC 后,下一次会先进行整理,默认为0,每次都整理。

G1收集器

主要针对多核CPU和大容量内存,极大概率满足GC停顿时间的同时,兼具高吞吐量

  • 内存划分为多个独立的region
  • 保留了分代思想, region的集合
  • 充分利用多CPU, 多核环境硬件优势, 尽量缩短STW
  • G1标记整理算法, 局部采取复制
  • 停顿可预测
  • 会维护优先列表,根据允许的时间回收价值最大的区域

region

使用G1, 将堆分为2048个大小相同的独立Region块, 每个Region根据堆实际大小而定, 为2的N次幂。

增加了Humongous内存区域, 如果超过1.5个region就放里面,视为老年代。

过程

提供了young GC 和Mix GC,都是STW的

  • Young GC, 选定所有年轻代Region, 通过控制年轻代Region的个数, 来控制Young GC的时间开销
  • Mixed GC: 选定所有年轻代Region,外加根据global concurrent marking统计得出的收集收益高的若干老年代Region,在用户指定的开销目标范围内尽可能选择收益高的老年代Region

四个过程:

  • 初始标记
  • 并发标记
    • 活动信息是在应用程序运行时同时计算
    • 活动信息标识在疏散暂停期间最适合回收的区域
  • 最终标记
    • 找到的空区域被删除并回收, 计算所有区域的区域活跃度
    • 使用快照算法SATB, 比CMS算法快得多
  • 筛选回收
    • 同时回收年轻代和老年代
    • 老年代根据活跃度选择
参数含义
-XX: +UseG1GC使用G1
-XX:MaxGCPauseMillis=200期望最大GC停顿时间(不保证达到)
-XX:InitiatingHeapOccupancyPercent=45mixed gc中也有阀值参数,当老年代占堆的百分比达到时触发mixed GC, 默认45
-XX:NewRatio=n新生代/老年代, 默认2
-XX:SurvivorRatio=nEden/Survivor大小比例,默认8
-XX:MaxTenuringThreshold=n老年代的临界值,默认15
-XX:ParallelGCThreads=n并行阶段回收线程数
-XX:ConcGCThreads=n并发垃圾使用线程数
-XX: G1ReservePercent=n设置堆内存保留为假天花板的总量, 降低提升失败的可能性, 默认为10
-XX:G1HeapRegionSize=n使用G1堆被分成区的大小, 默认根据堆的大小算出最优解, 最小1mb,最大32m