Java核心 - 垃圾回收


垃圾回收与垃圾收集器

垃圾回收(Garbage Collection):JVM清除无用对象,释放内存空间的抽象定义

  • Minor GC:年轻代GC
  • Major GC:老年代GC

垃圾收集器(Garbage Collector):垃圾回收的具体实现

两者都简称GC


STW(Stop The World)

JVM在执行某些整堆操作前的动作,挂起几乎所有线程(正在进行GC的线程除外)造成的全局停止现象
JVM通过在线程中添加SafePoint,达到STW的目的

SafePoint

在线程的特定位置插入通知线程停止的指令
位置

  • 循环末尾
  • 方法返回前
  • 调用方法后
  • 抛出异常的位置

造成STW的动作

  • GC

  • VM Threads 的某些特殊的 VM Operation
    1,JIT相关操作
    2,Class Redefinition(热加载类,如AOP的动态代理)
    3,Biased Lock Revocation(取消偏斜锁)

     -XX:-UseBiasedLocking,禁用偏斜锁
    

    4,Various Debug Operation

常用参数

参数描述备注
-XX:+PrintGCApplicationStoppedTimeGC日志内打印JVM所有停顿时长JDK1.7.40版本前,不打印时间戳
-XX:+PrintGCApplicationConcurrentTimeGC日志内打印JVM两次停顿间的运行时长JDK1.7.40版本前,不打印时间戳
-XX:+PrintSafepointStatistics打印SafePoint,VM Operation,线程数等相关信息
-XX:+PrintSafepointStatisticsCount打印到达SafePoint时各阶段所消耗时长重点关注 vmop:执行VM Operation时长

引用

  • Java引用分为强引用,软引用,弱引用,幻象引用(虚引用)
  • 所有引用队列均是Java.lang.ref.Refrence的子类

强引用(对应强可达)

  • 最常见的对象引用,GC不会回收强引用的对象
  • 当超出了引用的作用域或显示的设置为null时,引用关系解除,表示对象可以被GC
    在这里插入图片描述

软引用(对应软可达)

  • 软引用的对象不会直接被GC,只有当JVM认为内存不足时才会尝试回收软引用对象
  • JVM会确保在OOM之前进行回收
  • 软引用通常可用来做内存敏感的缓存
    在这里插入图片描述

弱引用(对应弱可达)

  • 弱引用仅用作提供一种对象的访问途径,无法阻止对象被GC
  • 通过弱引用获取对象时,如果对象还在则直接使用,否则重新实例化
    在这里插入图片描述

幻象引用(对应幻象可达)

  • 必须配合引用队列使用,通过引用get只能返回null
  • 一般用于监控对象的创建、销毁
    在这里插入图片描述

引用队列(ReferenceQueue)

  • 引用队列可以配合软引用、弱引用、幻象引用使用,当引用对象要被JVM回收时,会将其加入到引用队列中
  • 通过引用队列,可以了解JVM GC的情况

吞吐量(Throughput)

  • CPU运行用户程序时间(User Program Time,UPT)占CPU总运行时间的比例
  • 在GC场景下,吞吐量 = UPT / (UPT + GC)

对象存活判断

引用计数

  • 对象新增引用则引用计数+1,释放引用则引用计数-1,引用计数为0时表示对象可以被GC
  • 通过引用计数来判断对象存活,无法解决循环引用问题

可达性分析

  • JVM随机选取GC Roots对象,从GC Roots对象开始标记其余对象的可达性
  • 任何GC Roots不可达的对象表示可以被GC

GC Roots对象选取
1,虚拟机栈,本地方法栈引用的对象
2,静态属性引用的对象
3,常量

可达性
1,强可达:对象不可被回收
2,软可达:对象在Full GC前会被回收
3,弱可达:对象可被回收
4,幻象可达:对象已被执行finalize


垃圾回收算法

标记 - 清除

  • 标记清除算法分为“标记”“清除”两步,“标记”可以被GC的对象,“清除”已被“标记”的对象
  • 对象被“清除”后会产生内存碎片,内存碎片会导致内存空间的利用率不足(对象分配的内存需要是连续的内存空间)

标记 - 压缩

  • 即标记 - 清除 - 压缩
  • 在标记清除算法上添加了内存压缩算法。“压缩”已被清除的内存空间,移动所有存活的对象使被使用的内存空间保持连续
  • “压缩”非常耗时

复制

  • 即标记 - 清除 - 复制
  • 复制算法逻辑上将内存划分为两块,同一时间仅使用其中一块。GC时先进行标记清除,随后将存活的对象全部复制到另一块空闲的内存中,复制后对象自然使用的是连续的内存空间
  • 复制算法省去了“压缩”移动对象的时间消耗,但内存空间利用率有所降低

垃圾收集器(Garbage Collector)

  • Java目前主要在用的GC有Serial,ParNew,Paralle,CMS,G1,ZGC
  • JDK9中CMS已经被标记废弃,默认使用G1

Serial

  • 单线程串行GC,GC时是STW的
  • 分为作用于年轻代的 Serial 和作用于老年代的 Serial Old

Serial

  • 使用复制算法,即Eden + Survivor From => Survivor To

Serial Old

  • 使用标记压缩算法,使内存空间利用率最大化

常用参数

参数描述备注
-XX:+UseSerialGC启用 Serial + Serial Old GC

ParNew

  • 作用于年轻代的GC
  • Serial 的多线程版本,除多线程执行GC外,其余特性与 Serial 基本一致(共用了大部分代码)

常用参数

参数描述备注
-XX:+UseParNewGC启用 ParNew GC
-XX:ParallelGCThreads设置并行线程数量,默认:≈CPU数N = cpus <= 8 ? cpus : 3 + (cpus * 5 / 8)

Paralle

  • 分为使用复制算法作用于年轻代的 Paralle Scavenge 和使用标记整理算法作用于老年代的 Paralle Old
  • 是一种基于目标的多线程GC,以吞吐量为目标参数。吞吐量越大GC时间越短,GC触发越频繁

Paralle Scavenge

  • 与 ParNew 相似,最显著的区别在于 Paralle Scavenge 关注的是吞吐量而非STW。通过目标参数可直接设置吞吐量要求
  • 一般应用在与用户交互不多对STW要求不是特别高的后台计算程序上(如批处理,科学计算等)
  • 可启用GC自适应调节策略

GC自适应调节策略

  • JVM运行时根据系统运行情况自动调整 -Xmn,-XX:SurvivorRation,-XX:PretenureSizeThreshold
    等参数
  • 通过参数(UseAdptiveSizePolicy )开启,启用后只需要设置好 -Xmx 和目标吞吐量即可,其他运行时细节调整均由JVM负责完成

Paralle Old

  • 多线程的 Serial Old,同样关注的是吞吐量

常用参数

参数描述备注
-XX:+UseParallelGC启用 Parallel Scavenge GC
-XX:+UseParallelOldGC启用 Parallel Old GC
-XX:MaxGCPauseMillisGC最大STW时间(ms),JVM尽量保证每次GC时间不超过该参数值与Paralle的吞吐量目标背道而驰,不建议使用
-XX:GCTimeRatio目标吞吐量(0<N<100),默认:9999%的UPT
-XX:+UseAdptiveSizePolicy启用GC自适应调节策略

CMS(Concurrent Mark Sweep,JDK1.9被标记废弃)

  • 并发标记清除GC,年轻代和老年代采用不同的GC算法,以尽可能短的STW为设计目标
  • 年轻代GC近似ParNew
  • 老年代GC主要由初始标记、并发标记、重新标记,并发清除等阶段完成。初始标记、重新标记阶段是STW的
  • CMS不会直接压缩内存,需配置参数启用并设置压缩周期,否则易触发Full GC

老年代GC

1,初始标记(STW)

  • 单线程标记与GC Roots、年轻代对象直接关联的老年代对象
  • JDK1.8后可启用多线程并行(-XX:+CMSParallelInitialMarkEnabled)
    图片来自于网络

2,并发标记

  • 并行标记所有可达的对象
  • 由于不是STW的,对象的可达性可能在被标记后发生变更
    图片来自于网络

3,预清理(二次并发标记)

  • 重新标记可达性发生变更的对象

4,可中断预清理

  • 尽可能多的执行Minor GC,以降低重新标记阶段STW的时间

阶段跳过
Eden使用空间 和 使用率均超过阈值

参数描述备注
-XX:CMSScheduleRemarkEdenSizeThresholdEden使用空间阈值,默认:2MB
-XX:CMSScheduleRemarkEdenPenetrationEden使用率阈值,默认:50%

阶段退出
超过Minor GC执行次数 或 超过最大执行时间

参数描述备注
-XX:CMSMaxAbortablePrecleanLoopsMinor GC执行次数阈值,默认:0不限次数
-XX:CMSMaxAbortablePrecleanTime阶段最大执行时间,默认:5000ms

5,重新标记(STW)

  • 重新标记年轻代、GC Roots、并发标记阶段发生变更的对象
参数描述备注
-XX:+CMSScavengeBeforeRemark执行重新标记前,至少执行1次Minor GC建议启用

6,并发清除

  • 清除所有不可达的对象

7,并发重置

  • 重置CMS内部状态、数据结构,等待下一次触发

Full GC

  • 在CMS执行周期内,老年代、方法区内存空间不足时会触发Full GC

并发模式失败(Concurrent mode failure)

  • 老年代内存空间不足,无法分配大对象、新晋升的年轻代对象时抛出

晋升失败(Promotion failed)

  • 执行Minor GC时,对象晋升至老年代,但老年代内存不足
  • 抛出并发模式失败错误

常用参数

参数描述备注
-XX:+UseConcMarkSweepGC启用CMS GC
-XX:ParallelGCThreads设置年轻代GC线程数参考ParNew
-XX:ParallelCMSThreads设置老年代GC线程数,默认:≈1/4 年轻代GC线程数(ParallelGCThreads + 3) / 4
-XX:+CMSParallelInitialMarkEnabled初始标记启用多线程并行> JDK1.8
-XX:CMSInitiatingOccupancyFraction老年代内存使用比例超过参数值时触发GC,默认:92%
-XX:+UseCMSInitiatingOccupancyOnly启用动态检查
-XX:+UseCMSCompactAtFullCollection启用Full GC后内存压缩功能
-XX:+CMSFullGCsBeforeCompaction执行N次Full GC后,执行一次内存压缩,默认:0每次都压缩

动态检查

  • JVM根据最近的回收历史,估算下一次老年代内存耗尽的时间
  • 接近估算时间时触发GC

G1(Garbage First,JDK1.9默认)

  • 目标(STW时间,MaxGCPauseMillis)导向的分区复制GC,年轻代和老年代采用不同的GC算法
  • 移除了年轻代、老年代物理上的内存划分
  • 年轻代GC近似ParNew
  • 老年代GC由并发周期混合GC两阶段完成,整堆(年轻代+老年代)内存使用率超过阈值(InitiatingHeapOccupancyPercent)时触发
  • 并发周期采用SATB(逻辑快照)算法降低重新标记阶段的耗时
  • G1的GC Roots对象选取与其他GC不同(详见根分区扫描

G1模型
分区(Region)

  • 默认将内存划分为2048个大小相等的分区,可设置参数(G1HeapRegionSize)调整分区大小
  • 分区不明确服务于年轻代、老年代,G1可动态调整
    图片来自于网络
参数描述备注
-XX:G1HeapRegionSize分区大小,1MB ~ 32MB之间必须是2的幂次方

卡片(Card Table)

  • 每个分区划分为多个512B的卡片,卡片为内存最小可用粒度
  • 所有卡片由Card Table字节数组统一维护

RSet(Remembered Set)

例:Old分区R1内对象A,引用了分区R2内对象B。则分区R1的RSet同时记录A引用B
  • 记录老年代分区引入对象的信息
  • 老年代分区标记时扫描RSet,降低耗时

巨型对象(Humongous Object)

  • 超过分区大小50%的对象被认定为巨型对象,直接分配在老年代
  • 超过分区大小的巨型对象分配在连续的多个分区上(开始巨型 + 连续巨型 * N),且独占分区
  • 连续分区不足时,触发Full GC

CSet(Collection Set)

  • 清理阶段记录需要回收的分区,供混合GC阶段使用
  • 存活对象占比大于阈值(G1MixedGCLiveThresholdPercent)则不记录CSet
参数描述备注
-XX:+G1MixedGCLiveThresholdPercent设置不记录CSet存活对象占比阈值,默认:85JDK1.6、1.7,默认:65

SATB(Snapshot-At-The-Beginning,逻辑快照算法)

粗浅介绍,了解其大概用意。详细算法逻辑(P + N两段标记、bitmap、内存屏障等)可自行了解
  • 逻辑不可变快照,假定快照内容不会变更
  • 在分区末尾插入TAMS标记,并发标记阶段仅标记TAMS前的对象
  • TAMS后分配 和 TAMS前发生变更的对象,通过日志将对象旧值记录在SATB缓冲区内

三色标记
黑色:已被标记可达的对象(如根对象)
灰色:黑色对象直接引用但其分叉尚未扫描完成的对象
白色:尚未被标记的对象(并发周期结束,仍是白色的则是可回收对象)

并发周期

  • 标记出可被垃圾回收的老年代对象
  • 主要由初始标记、并发标记、重新标记,清理等阶段完成
  • 初始标记、重新标记、清理阶段是STW的

初始标记(initial-mark,STW)

  • 设置SATB的TAMS标记
  • 由于需要STW遂与年轻代GC合并,初始标记前先执行Minor GC

根分区扫描(root-region-scan)

  • 由于已执行过Minor GC,Survivor To内都是强可达对象,将其全部设置为GC Roots对象
  • 标记所有GC Roots直接引用的对象
  • 该阶段禁止Minor GC

并发标记(concurrent-mark)

  • 并行标记所有可达的对象
  • 过程中发生变更的对象记录在SATB缓冲区内

重新标记(remarking,STW)

  • 重新标记SATB缓冲区内的对象

清理(cleanup,STW)

  • 将需回收分区记录CSet,并按存活度(存活对象占比)升序排列
  • 直接回收巨型对象
  • 空闲分区识别(直接释放整个分区并加入到空闲队列)
  • 将无用的类从Metaspace(元数据区、方法区)中卸载

混合GC(Mixed GC,混合收集,STW)

  • 执行多次(G1MixedGCCountTarget),每次执行回收后将分区内存活对象复制到另一个空闲的分区上
  • Minor GC与CSet回收同时进行,直到CSet被全部(几乎)回收为止
  • 回收结束后,恢复正常年轻代GC
  • 可回收对象百分比小于堆废物百分比(G1HeapWastePercent),则跳过混合GC
    图片来自于网络
参数描述备注
-XX:G1MixedGCCountTarget设置执行混合GC次数,默认:8若混合GC单次STW时间过长,可增加该参数
-XX:G1HeapWastePercent设置堆废物百分比,默认:5

疏散失败

  • Minor GC清除完成时,没有足够的空闲空间复制对象

常用参数

参数描述备注
-XX:+UseG1GC启用G1 GC
-XX:MaxGCPauseMillis设置最大STW时间,越大GC频率越低但吞吐量也会下降,默认:200msJVM会根据目标值动态调整内存模型
-XX:InitiatingHeapOccupancyPercent设置并发周期触发百分比,默认:45(%)一轮GC结束后,堆使用空间小于该值以最优
-XX:ParallelGCThreads设置年轻代GC线程数参考ParNew
-XX:ConcGCThreads设置老年代GC线程数,默认:≈1/4 年轻代GC线程数(ParallelGCThreads + 3) / 4
-XX:G1ReservePercent设置整堆预留空闲空间百分比,降低疏散失败风险,默认:10%

G1调优

  • 在避免疏散失败和Full GC的前提下,以尽可能短的STW提供较高的吞吐量

1,不要设置Xmn、NewRaio参数,会导致目标(MaxGCPauseMillis)失效
2,主要通过目标(MaxGCPauseMillis)作为调优手段,90%的GC可以达到该时间为最优
3,提高并行线程数(ConcGCThreads),加快GC速度,但CPU压力也会随之增加
4,降低并发周期触发百分比(InitiatingHeapOccupancyPercent),提前启动GC
5,提高CSet对象存活占比阈值(G1MixedGCLiveThresholdPercent),使CSet记录更多的分区以提高GC的效果
6,提高混合GC执行次数(G1MixedGCCountTarget),降低单次执行的STW时长

ZGC(Z Garbage Collector,> JDK11)

  • 升级版的G1
  • STW不超过10ms(128G内存,STW 1.68ms)
  • 支持Numa架构
  • 目前仅支持64位的Linux系统

GC示例图

Minor GC(年轻代GC)
图片来自于网络

Copy To(复制)
图片来自于网络

Promotion(晋升)

  • 示例年龄:9,JVM默认:15
    图片来自于网络

Major GC(老年代GC)
图片来自于网络

Mixed GC(混合GC)

  • 同时回收年轻代、老年代

方法区回收

  • 非即时的回收废弃常量、无用的类
  • 设置参数(ClassUnloadingWithConcurrentMark)启用动态代理类即时回收
参数描述备注
-XX:+ClassUnloadingWithConcurrentMark启用动态代理类即时回收(自定义类加载器本身被回收时回收其加载的类)JDK8u40后默认启用

废弃常量

  • 运行时常量池内无任何引用的字面量

无用的类

  • 类所有实例已被回收
  • 类加载器已被回收
  • 没有任何地方访问类的Class对象

逃逸分析(Escape Analysis)

  • 通过逃逸分析,确认不会逃逸的对象直接在线程栈上分配(Java对象默认分配在堆上)
  • 线程结束的同时销毁对象,降低GC压力

方法逃逸

  • 方法内定义的对象,作为返回参数逃逸出方法的作用域

线程逃逸

  • 对象赋值给线程共享的变量
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值