深入理解JVM(三)

深入理解JVM(三)

GC算法

CMS

CMS在任何一个JAVA版本中都不是默认的垃圾回收器, 但是同时他又是一个非常重要的GC.

Serial和Parallel都是无法和工作线程同时相应, 必须垃圾收集结束才可以进行工作线程的继续. 这就导致了会有一段时间的STW, 随着内存的越来越大, STW越来越长, 以至于到了一种无法忍受的地步.因此产生了CMS.

CMS在运行中也会产生STW, 但是仅在初始标记和重新标记阶段, 而STW最耗时间的标记可以和工作线程同时工作.因此CMS的响应时间会更快.

但是为什么CMS在任何一个JAVA版本中都不是默认的垃圾回收器呢. 因为我们之前也说过, 尽管CMS有着种种优点,响应时间快, 但是他也有一些缺点, 这些缺点导致他无法作为一个默认的垃圾回收器使用.

在Java8时默认的垃圾回收器是Parallel, 到了Java9时默认垃圾回收器就变成了G1.

但是不可否认, CMS在垃圾回收器的历史上起到了承上启下的作用.

CMS四个阶段

初始标记

通过GC Roots找到根对象, 此阶段会产生STW. 但是因此只找根对象, 所以要找的内容非常少, 所以STW时间非常短.

并发标记

并发标记阶段会和工作线程同步工作, 在标记过程中, 可能工作线程也会更改他的值, 有可能会产生新的垃圾, 或者原来的垃圾变成不是垃圾了.

这个阶段是最耗时的一个阶段, 正是因为这个阶段并发执行, 所以不产生STW.

重新标记

因为并发标记阶段是GC线程和工作线程同步工作, 可能会产生新的垃圾, 或者原来的垃圾变成不是垃圾. 所以会有一个重新标记的阶段.

这个阶段会重新标记这些改变的内容, 同时他也会产生STW, 但是因为改变的内容不是很多, 所以这个阶段是STW也是非常短的.

并发清理

GC线程和工作线程并发的清理标记的垃圾.

G1

G1的意思是 Garbage First, 垃圾优先.

G1是一种服务端应用使用的垃圾收集器, 目标是用在多核, 大内存的机器上, 它在大多数情况下可而已实现指定的GC暂停时间, 同时还能保持较高的吞吐量

当G1发现需要回收垃圾的时候, 他首先收集的存活对象最少的区域.

G1之前的垃圾收集器, 不论是Serial还是Parallel或者是CMS, 内存都是连续大块的. 当内存非常大块时, 调优工作是非常难进展的.所以G1是整个内存模型的改变.

因此他使用了分而治之的思想.

G1的内存从1M开始, 2M, 4M … 2^nM.. 最大32M, 分为一个个的region.

每块region在逻辑上依然分区, Old区, Servivor区, Eden区. 还有可能会有大对象跨多个区, Humongous.

G1的特点

  • 并发收集
  • 压缩空闲空间不会延长GC的暂停时间
  • 更易预测的GC暂停时间
  • 适用不需要实现很高的吞吐量的场景

G1的内存区域不是固定的Eden区或者Old区

当一块区域回收之后, 在下一次使用的时候有可能就不再是原来的Eden区或者Old区了.

基本概念

  • card table

在YGC过程中, 可能会发生上图的情况, 即, GC Root指向了Old中的对象, Old中的对象又指向Young, 会发现, 发生一次YGC要去扫描Old内存.

card table会将对象分成一个个的card, 类似于内存页. 如果有Old区中的对象指向Young区, 会把这个card标成Dirty, 这些Dirty的card会用一个bitmap来代表.

这样在YGC的时候, 只要扫描Dirty的card, 就可以减少扫描.

  • CSet = Collection Set

一组可被回收的分区的集合.

在CSet中存活的数据会在GC过程中被移动到另一个可用分区, CSet中的分区可以来自Eden空间,Servivor空间, 或者老年代,

CSet会占用不到整个堆空间的1%大小.

  • RSet = RememberedSet

    在每个Region中都有一个表格, 记录其他Region指向本Region的引用.

    RSet的价值在于, 使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象, 只需要扫描RSet即可.

新老年代比例

5% - 60%

  • 一般不用手工指定

  • 不要手工指定, 因为这是G1预测停顿时间的基准

G1是否分代? G1垃圾回收器会产生FGC吗?

逻辑分代, 物理不分代.

当对象产生特别快, 内存分配不过来的时候就会产生FGC.

如果G1产生FGC, 应该怎么做

  1. 扩内存
  2. 提高CPU性能
  3. 降低MixedGC触发的阈值, 让MixedGC提早发生 (默认是45%)

MixedGC

G1三个阶段, YGC, MixedGC, FGC.

目标就是不要发生FGC.0-

相当于CMS

-XX:InitiatingHeapOccupacyPercent

  • 默认值45%
  • 当O超过这个值时, 启动MixedGC

MixedGC的过程

  • 初始标记 STW
  • 并发标记
  • 重新标记 STW
  • 筛选回收

并发标记算法

难点在于, 在并发标记对象过程中, 对象引用关系正在发生改变.

三色标记法

在CMS和G1中都是使用三色标记法

把对象在逻辑上分为三种颜色:

  • 黑色: 自身和成员变量均已标记完成
  • 灰色: 自身被标记, 成员变量未被标记
  • 白色: 未被标记的对象

这张图中, A是自己已经标记完成, 且成员变量也被标记完成

BC都是自己已经标记完成, 成员变量还未标记

D是还未标记的对象.

漏标

在并发标记过程中, A指向了D, B不再指向D , 重新扫描时, 不会对黑色的成员变量进行标记, 则会漏标.

会把白色D对象当做没有引用从而回收掉

产生漏标原因是因为灰色引用消失和黑色引用增加同时发生, 要解决这个问题只要破坏一个即可.

  1. incremental update

    增量更新, 关注引用的增加,把黑色重新标记为灰色, 下次重新扫描属性

    CMS使用的就是这个

  2. SATB snapshot at the begining

    关注引用的删除, 当B -> D消失时, 要把这个引用推到GC的堆栈, 保证D还能被GC扫描到.

    G1使用的是这种

-为什么G1使用SATB

如果A变成灰色, 他的引用要重新扫描, 要重新扫描BCD.

灰色→白色 引用消失时, 如果没有黑色指向白色, 引用会被push到堆栈.

重新标记时拿到这个引用, 由于有RSet的存在, 不需要扫描整个堆去查找指向白色的引用, 效率比较高.

使用SATB配合RSet.

RSet与赋值的效率

  • 由于RSet的存在, 那么每次给对象赋引用的时候, 就得做一些额外的操作.

  • 指的是在RSet中做一些额外的记录 (在GC中被成为写屏障)

  • 这个写屏障 不等于 内存屏障

GC参数

GC

  • -Xmn -Xms -Xmx -Xss

    年轻代 最小堆 最大堆 栈空间

  • -XX:+UseTLAB

    使用TLAB, 默认打开

  • -XX:+PrintTLAB

    打印TLAB的使用情况

  • -XX:TLABSize

    设置TLAB大小

  • -XX:+DisableExplictGC

    使System.gc()失效

  • -XX:+PrintGC

  • -XX:+PrintGCDetails

  • -XX:+PrintHeapAtGC

  • -XX:+PrintGCTimeStamps

  • -XX:+PrintGCApplicationConcurrentTime

    打印应用程序时间

  • -XX:+PrintGCApplicationStoppedTime

    打印暂停时长

  • -XX:+PrintReferenceGC

    记录回收了多少种不同引用类型的引用

  • -verbose:class

    类加载详细过程

  • -XX:+PrintVMOptions

    打印JVM参数

  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial

    打印XX的参数

  • -Xloggc:opt/log/gc.log

  • -XX:MaxTenuringThreshold

    升代年龄,最大值15

  • 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 … 这些不建议设置

CMS

  • -XX:+UseConcMarkSweepGC

    使用CMSGC

  • -XX:ParallelCMSThreads

    CMS线程数量

  • -XX:CMSInitiatingOccupancyFraction

    使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)

  • -XX:+UseCMSCompactAtFullCollection

    在CMS时进行压缩

  • -XX:CMSFullGCsBeforeCompaction

    多少次CMS之后进行压缩

  • -XX:+CMSClassUnloadingEnabled

  • -XX:CMSInitiatingPermOccupancyFraction

    达到什么比例时进行Perm回收

  • GCTimeRatio

    设置GC时间占用程序运行时间的百分比

  • -XX:MaxGCPauseMillis

    停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

Parallel

  • -XX:SurvivorRatio

    调整Young区比例

  • -XX:PreTenureSizeThreshold

    大对象到底多大

  • -XX:MaxTenuringThreshold

  • -XX:+ParallelGCThreads

    并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同

  • -XX:+UseAdaptiveSizePolicy

    自动选择各区大小比例

G1

  • -XX:+UseG1GC

  • -XX:MaxGCPauseMillis

    建议值,G1会尝试调整Young区的块数来达到这个值

  • -XX:GCPauseIntervalMillis

    GC的间隔时间

  • -XX:+G1HeapRegionSize

    分区大小,建议逐渐增大该值,1 2 4 8 16 32。 随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长

    ZGC做了改进(动态区块大小)

  • G1NewSizePercent

    新生代最小比例,默认为5%

  • G1MaxNewSizePercent

    新生代最大比例,默认为60%

  • GCTimeRatio

    GC时间建议比例,G1会根据这个值调整堆空间

  • ConcGCThreads

    线程数量

  • InitiatingHeapOccupancyPercent

    启动G1的堆空间占用比例


   转载规则


《深入理解JVM(三)》 echi1995 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Redis(一) Redis(一)
Redis(一)为什么要使用Redis?首先, 关系型数据库中的数据是存放在磁盘上的, 而磁盘的IO是有限制的. 就算MySQL对索引做了优化, 每次读一个索引, 当海量的请求压过来的时候可能查询到各种各样不同的数据, 会发生把磁盘IO占
下一篇 
深入理解JVM(二) 深入理解JVM(二)
深入理解JVM(二)GC我们都知道Java中是有垃圾回收机制会自动帮我们回收内存, 以让程序员将精力放在业务上. 而C, C++没有垃圾回收机制, 只能手动回收垃圾,这就有可能导致忘记回收或者多次回收的问题. 什么是垃圾 在上图中, 堆中有
  目录