【JVM】垃圾收集器与GC日志(一)

垃圾收集器与GC日志

串行、并行、并发

串行:一个GC线程运行

并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上

STW(Stop The World)

即GC线程与用户线程无法并发运行,GC线程执行期间需要暂停用户线程。

比如:你妈给你打扫房间,需要把你从房间归纳出去,不然她一边打扫垃圾,你一边制造垃圾,没完没了了

垃圾收集器

目前JVM中的收集器有九种,了解5个,详解2个。因为并发、分区管理式的收集器才是未来的趋势。

注意:标记阶段标记的是存活对象,回收未被标记的对象。

Serial收集器

串行收集器,即GC线程与用户线程先后运行,即GC时需要STW(暂停所有用户线程),直至GC结束才恢复用户线程的运行。专注于收集年轻代,底层时赋值算法,相关参数:-XX:+UseSerialGC

ParNew收集器

Serial收集器的多线程版本,唯一能与CMS收集器搭配使用的新生代收集器。

相关参数:

-XX:+UseConctMarkSweepGC:指定使用CMS后,会默认使用ParNew作为新生代收集器

-XX:+UseParNewGC:强制指定使用ParNew

-XX:ParallelGCThreads:指定垃圾收集的线程数量,ParNew默认开启的收集器线程与CPU的数量相同

Parallel收集器

关注吞吐量的收集器。吞吐量 = 运行用户代码时间/ (运行用户代码时间 + 垃圾收集时间)

相关参数:

-XX:MaxGCPauseMills:是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过设定值。不过大家不要异想天开地认为如果把这个参数的值设置得稍微小一点就能使得系统地垃圾收集速度变得更快,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的。系统把新生代调小一些,收集300MB新生代肯定比收集500MB快吧,这也直接导致垃圾收集发生得更频繁游戏额,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次,每次停顿70毫秒。停顿的时间的确在下降,但吞吐量也降下来了。

-XX:GCTimeRatio:一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率。如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1/(1+19)),默认值为99,就是允许最大1%(1/(1+99))的垃圾收集时间

-XX:+UseAdaptiveSizePolicy:一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、今生老年代年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)

SerialOld

Serial收集器的老年代版本。基于标记-整理算法

有两个用途:

  • 1.与Serial收集器、Parallel收集器搭配使用
  • 2.作为CMS收集器的后备方案

Parallel Old收集器

Parallel收集器的老年代版本。基于标记-整理算法

CMS收集器(并发)

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java引用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户较好的体验,CMS收集器就非常符合这类应用的需求

聚焦低延迟。基于标记-清除算法实现。由于CMS收集器是并发收集器,即再运行阶段用户线程依然在运行,会产生对象,所以CMS收集器不能等到老年代满了才触发,而是要提前触发,这个阈值是92%。这个阈值可以通过参数-XX:CMSInitiatingOccupancyFraction设置

CMS的工作过程可以分为4个步骤

  • 1.初始标记(CMS initial mark)
    仍然需要STW,初始标记仅仅是标记以下GC Roots能直接关联到的对象,速度很快
  • 2.并发标记(CMS concurrent mark)
    不会STW.GC线程与用户线程并发运行.进行GC Roots Tracing的过程
  • 3.重新标记(CMS remark)
    会STW.为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间段
  • 4.并发清除(CMS concurrent sweep)
    GC线程与用户线程并发运行,清理未被标记到的对象。默认启动的回收线程数= (处理器核心数 + 3 ) / 4

CMS的缺点:

显然CMS收集器依然不是完美的,不然后面就不会出现G1、ZGC等

  • 1.运行期间会与用户线程抢夺CPu资源。当然这是所有并发收集器的缺点
  • 2.无法处理浮动垃圾(标记结束后创建的对象)
  • 3.内存碎片

G1收集器(Garbage First, 低延迟)

G1收集器与之前的所有收集器都不一样,它将堆分成了一个一个Region,这些Region用的时候才被赋予角色:Eden、From、To、Humongous。一个Region只能是一个角色,不存在一个Region即是Eden又是From.如果这次Region内的对象被回收,那么它的角色可以跟之前的不一样。每个Region的大小可通过参数-XX:G1HeapRegionSize设置,取值范围是2-32M.一个对象的大小超过Region的一半则被认定为大对象,会用N个连续的Region来存储。Region个数在源码当中是一个常量2048

G1收集器的特点:
  • 1.并行与并发
    G1能充分利用多CPU、多核环境下的硬件又是,使用多个CPU来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行
  • 2.分代收集
    与其他收集器一样,分代概念在G1中依然得到保留。虽然G1可以不需要其他收集器配合就可以独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果
  • 3.空间整合
    与CMS的"标记-清除"算法不同,G1从整体来看是基于"标记-整理"算法实现的收集器,从局部(两个Region之间)上来看是基于"复制"算法实现的。但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC
  • 4.可预测的停顿
    这是G1相对于CMS的另一大又是,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java的垃圾收集器的特征了

缺点:

  • 1.需要10%~20%的内存来存储G1收集器运行需要的数据,如cset、rset、卡表等
  • 2.运行期间会与用户线程抢夺CPU资源。当然,这是所有并发收集器的缺点
G1运行示意图
  • 1.初始标记
    会STW,仅仅标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短。TAMS以上的值为新创建的对象,默认标记为存活对象,即多标
  • 2.并发标记
    耗时较长,GC线程与用户线程并发运行,从GC Roots能直接关联到的对象开始遍历整个对象图
    从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这解读那耗时较长,但可与用户线程并发执行
  • 3.最终标记
    会STW,遍历写屏障+SATB记录下的旧的引用对象图。
    为了修正在并发标记期间因用户线程继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到RememberedSet中,这个阶段需要STW
  • 4.筛选回收
    需要STW.更新Region的统计数据,对各个Region的回收价值进行计算并排序,然后根据用户设置的期望暂停时间的期望值生成回收集。然后开始执行清除操作。将旧的Region中的存活对象移动到新的Region中,清理这个旧的Region。这个阶段需要STW.因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率
G1垃圾收集分类

YoungGC

YoungGC并不是说现有的Eden去放满了就会马上触发,G1会计算下现在Eden区回收大概需要多久时间,如果回收时间远远小于参数-XX:MaxGCPauseMills设定的值,那么增加年轻代的Region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数-XX:MaxGCPauseMills设定的值,那么就会触发YoungGC

MixedGC

不是FullGC,老年代的堆占有率达到参数(-XX:InitialHeapOccupancyPercent)设定的值则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个Region种存活的对象拷贝到别的Region里区,拷贝过程种如果发现没有足够的空Region能够承载拷贝对象就会触发一次FullGC

FullGC

停止系统程序,然后采用单线程进行标记、清理和压缩,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。(Shenandoah优化成多线程收集了)

相关参数:

-XX:G1HeapRegionSize:设置Region的大小

-XX:MaxGCPauseMills:设置GC回收时允许的最大停顿时间(默认200ms)

-XX:+UseG1GC:开启G1

-XX:ParallelGCThreads:STW期间并行执行的GC线程数

-XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个年龄对象)总和超过了Survivor区域的50%,此时就会把含年龄n(含)以上的对象都放入老年代

-XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合收集(MixedGC).比如前面说的堆默认有2048个Region,如果有接近1000个Region都是老年代的Region,则可能就要触发MixedGC了

-XX:G1MixedGCLiveThresholdPercent(默认85%):region中的存户哦对象低于这个值时才会回收该Region,如果超过这个值,存活对象越多,回收的意义不大

-XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一会儿,然后暂停回收,恢复系统运行,一会儿再开始回收,这样可以让系统不至于单词停顿时间过长

G1垃圾收集器优化建议

假设参数-XX:MaxGCPauseMills设置的值很大,导致系统运行很久,年轻代可能都占用了堆内存的60%了,此时才触发年轻代GC.那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象,就会进入老年代中。或者是你年轻代过后,存活下来的对象过多,导致进入Survivor区域后触发了动态年龄判定规则,达到了Survivor区域的50%,也会快速导致一些对象进入老年代。

所以这里核心还是在于调节-XX:MaxGCPauseMills这个参数的值,在保证它的年轻代GC别太频繁的同时,还得考虑GC过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发mixed gc

相关推荐
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7894 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~5 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
程序媛小果5 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot