提交并发量的方法:Java GC tuning :Garbage collector
三色算法,高效率垃圾回收,jvm调优
Garbage collector:垃圾回收器
What garbage?
没有任何引用指向它的对象
JVM GC回收算法:
- 引用计数法(ReferenceCounting)
- 给对象中添加一个引用计数器,每当有一个地方引用他时,计数器值就+1,;当引用失效时,计数器值就-1;任何时刻计数器为0的对象就是不可能在被使
-
-
优点:判定效率高
-
缺点:不会完全准确,因为如果出现两个对象相互引用的问题就不行了
-
很明显,到最后两个实例都不再用了(都等于null了),但是GC却无法回收,因为引用数不是0,而是1,这就造成了内存泄漏。也很明显,现在虚拟机都不采用此方式。
- 可达性分析算法(root seaching)
通过一系列的GC Roots的对象作为起始点,从这些根节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
说明:
(2.1)、红色代表不可达对象(可回收对象)
(2.2)、千万注意!!!!!上图并不是说方法区全可达,虚拟机栈部分可达,本地方法栈全部不可达,而只是为了说明这三个部分可以作为GC Roots!
3、可以作为GC Roots的对象包括以下几点
(3.1)、虚拟机栈(栈帧中的本地变量表)中引用的对象。
(3.2)、方法区中的类静态属性引用的对象或者常量引用的对象。
(3.3)、本地方法栈中JNI(就是native方法)引用的对象。
垃圾回收算法(标记 -清除、复制、标记-整理、分代收集)
1、标记 -清除算法(Mark-Sweep):内存碎片
“标记-清除”算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。
它的主要缺点有两个:
(1)效率问题:标记和清除过程的效率都不高;
(2)空间问题:标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,碎片过多会导致大对象无法分配到足够的连续内存,从而不得不提前触发GC,甚至Stop The World。
2、复制算法(Copying)内存浪费
为解决效率问题,“复制”收集算法出现了。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
它的主要缺点有两个:
(1)效率问题:在对象存活率较高时,复制操作次数多,效率降低;
(2)空间问题:內存缩小了一半;需要額外空间做分配担保(老年代)
From Survivor, To Survivor使用的就是复制算法。老年代不使用这种算法,
3、标记-整理(Mark-Compact)-标记压缩:效率比copy略微底
复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
4、分代收集算法(Generational Collection)
GC分代的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。
“分代收集”算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法(copy),只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法(mark comprt 或者sweep)来进行回收。
垃圾收集器搭配(很重要)
可以从老年代记起:SerialOld最厉害,可以搭配所有的新生代收集器;
ParallelOld只能搭配Parallel新生代的,也就是Parallel Scavenge;
CMS 只能搭配Serial和ParNew;
最后G1只能自己和自己玩。
服务器1.7,1.8默认parallel scavenge+Parallel old (卡顿stw 现象比较多):面试事可以说实战经验:将parallel scavenge+Parallel old替换成parallel scavenge+serial old或者parnew+serial +parnew+cms+serial old
部分参考作者地表最强菜鸡原文链接:https://blog.csdn.net/qq_19734597/article/details/80959567
Seiral 收集器
a stop-the-world ,copying collector which uses a single GC thresd.(STW)
特征:
- 是单线程的
- 在垃圾回收时,必须暂停其他所有线程的工作线程,即所谓的“Stop The World”
- jvm在Client模式下,默认的新生代收集器仍然是Serial收集器,虽然它有着上述两个重大的缺点,但也有这简单高效的优点。
- 单线程,没有线程交互开销
- 使用方法:-XX:+UseSerialGC
- 适用:运行在Client模式下的虚拟机。
Serial和Serial Old的协同工作模式
2 Serial Old收集器
特征
Serial Old是单线程,使用标记整理算法的Serial老年代版本,主要意义也是在于给Client模式下的虚拟机使用。
Server模式下用途:
在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用
当老年代使用CMS收集器出现故障时(Concurrent Mode Failure),可以作为CMS的后备选择。
Serial和Serial Old的协同工作模式
3 ParNew 收集器
特征
其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余的和Serial所有控制参数一样。
在配置为CMS 收集器的默认新生代收集器。
在多CPU的环境下可以发挥更高而效率,并且是唯一一个可以和CMS收集器搭配工作的新生代并行GC。
单CPU的环境下效率低于Serial
适用:运行在server模式下的虚拟机首选的新生代收集器。
使用方法:-XX:+UseParNewGC
4 Parallel Scavenge(并行回收)收集器
特征
新生代收集器
使用复制算法,并行的多线程收集器
控制的吞吐量
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可用高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
参数:
-XX:MaxGCPauseMillis:控制最大垃圾收起停顿时间(毫秒)。
收集器会尽可能的保证每次垃圾收集耗费的时间不超过这个设定值。但是如果这个这个值设定的过小,那么Parallel Scavenge收集器为了保证每次垃圾收集的时间不超过这个限定值,会导致垃圾收集的次数增加和增加年轻代的空间大小,垃圾收集的吞吐量也会随之下降。
注意: XX:MaxGCPauseMillis设置的越小,吞吐量则必然越小。
-XX:GCTimeRatio: 设置吞吐量大小(>0,<100)。默认值为99,即最大允许1%的垃圾收集时间。
是应用程序运行时间和垃圾收集时间的比值。如果把值设置为19,即系统运行时间 : GC收集时间 = 19 : 1,那么GC收集时间就占用了总时间的5%【1 / (19 + 1) = 5%】,
-XX:+UseAdaptiveSizePolicy:
当这个参数打开之后,就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。
只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用MaxGVPauseMillis参数或GCTimeRation参数给虚拟机设立一个优化目标,JVM会自动调节其他优化参数.
自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别
Parallel Scavenge要和Parallel Old一起使用
Parallel Scavenge/Parallel Old运行示意图
5 Parallel Old 收集器
特征
是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
参数控制: -XX:+UseParallelOldGC
只能和Parallel Scavenge配合使用,这个组合常用于注重吞吐量以及CPU资源敏感的场合。
Parallel Scavenge/Parallel Old运行示意图
6 CMS(Concurrent Mark Sweep)收集器:卡顿次数与减少
特征
一种以获取最短回收停顿时间为目标的收集器。
适用于 :重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验的应用。
CMS收集器是基于“标记-清除”算法实现的,
运作过程分为4个步骤:
粗分:
1:初始标记(CMS initial mark)
需要“Stop The World”,仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。
2.并发标记(CMS concurrent mark)
就是进行GC Roots Tracing(跟踪)的过程。(就是判断哪些对象不可达的过程)。
与用户线程一起工作。
3.重新标记(CMS remark)
需要“Stop The World”,是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
4.并发清除(CMS concurrent sweep)
与用户线程一起工作。
总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。
细分:
1、CMS initial mark:初始标记root,STW
2、CMS concurrent mark:并发标记
3、CMS-concurrent-preclean:并发预清理
4、CMS remark:重新标记,STW
5、CMS concurrent sweep:并发清除
6、CMS-concurrent-reset:并发重置
————————————————
jdk1.4开始引入,不是默认配置,内存32G,20个G在old区
优点:并发收集‘,低停顿 低延时,老年代收集器
缺点:CPU敏感,浮动垃圾(并发清理阶段),空间碎片(不能适应太大内存)
缺点:
CMS收集器对CPU资源非常敏感。对于并发实现的收集器而言,虽然可以利用多核优势提高垃圾收集的效率,但是由于收集器在运行过程中会占用一部分的线程,这些线程会占用CPU资源,所以会影响到应用系统的运行,会导致系统总的吞吐量降低。CMS默认开始的回收线程数是(cpu数量 + 3) / 4,所以,当机器的CPU数量为4个以上的时候,垃圾回收线程将占用不少于%25的CPU资源,并且随着CPU数量的增加,垃圾回收线程占用的CPU资源会减少。但是,当CPU资源少于4个的时候,垃圾回收线程占用的CPU资源的比例会增大,会影响到系统的运行,假设有2个CPU的情况下,垃圾回收线程将会占据超过50%的CPU资源。所以,在选用CMS收集器的时候,需要考虑,当前的应用系统,是否对CPU资源敏感。
垃圾收集的过程中,无法处理浮动垃圾,所以可能会出现Concurrent Mode Failure问题而导致触发一次Full GC。浮动垃圾:是由于CMS收集器的并发清理阶段,清理线程是和用户线程一起运行,如果在清理过程中,用户线程产生了垃圾对象,由于过了标记阶段,所以这些垃圾对象就成为了浮动垃圾,CMS无法在当前垃圾收集过程中集中处理这些垃圾对象。由于这个原因,CMS收集器不能像其他收集器那样等到完全填满了老年代以后才进行垃圾收集,需要预留一部分空间来保证当出现浮动垃圾的时候可以有空间存放这些垃圾对象。在JDK 1.5中,默认当老年代使用了68%的时候会激活垃圾收集,这是一个保守的设置,如果在应用中老年代增长不是很快,可以通过参数 -XX:CMSInitiatingOccupancyFraction 控制触发的百分比,以便降低内存回收次数来提供性能。在JDK 1.6中,CMS收集器的激活阀值变成了92%。如果在CMS运行期间没有足够的内存来存放浮动垃圾,那么就会导致Concurrent Mode Failure失败,这个时候,虚拟机将启动后备预案,临时启动Serial Old收集器来对老年代重新进行垃圾收集,这样会导致垃圾收集的时间边长,特别是当老年代内存很大的时候。所以对参数-XX:CMSInitiatingOccupancyFraction的设置,过高,会导致发生Concurrent Mode Failure,过低,则浪费内存空间。
使用的"标记-清除"算法会出现很多内存碎片。过多的内存碎片会影响大对象的分配,会导致即使老年代内存还有很多空闲,但是由于过多的内存碎片,不得不提前触发垃圾Full GC。为了解决这个问题,CMS收集器提供了一个"-XX:+UseCMSCompactAtFullCollection"参数(默认是开启的),用于CMS收集器在必要的时候对内存碎片进行压缩整理。由于内存碎片整理过程不是并发的,所以会导致停顿时间变长。虚拟机还提供了一个-XX:CMSFullGCsBeforeCompaction"参数,来控制进行过多少次不压缩的Full GC以后,进行一次带压缩的Full GC,默认值是0,表示每次在进行Full GC前都进行碎片整理。
参数
-XX:+UseConcMarkSweepGC:使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection: Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长。
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理。
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量) 。
CMS收集器示意图
7G1收集器(面向服务端)
特点:
并行于并发:使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间,其他需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。它能够采用不同的方式去处理新创建的对象和已经存活了一段时间,熬过多次GC的旧对象以获取更好的收集效果。
空间整合:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。但无论如何,都意味着G1运作期间不会产生内存空间碎片,收集后能够提供规整的可用内存,这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同关注点, 但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
存储内存结构
G1会将堆划分为固定大小的多个区域,名称为Region,每一个Region都有对应一个remembered set(避免全堆扫描,记录每个对象是否可达),依然存在eden,S0,S1,old这些概念,不同的是是采用逻辑区分,而不是物理区分.每个heap区(Region)的大小在JVM启动时就确定了. JVM 通常生成 2000 个左右的heap区, 根据堆内存的总大小,一个Region的大小可以通过参数-XX:G1HeapRegionSize设定,范围允许为 1Mb 到 32Mb,且是2的指数倍。
优先列表
G1跟踪各个region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
运行示意图
G1 Collector
G1垃圾收集器介绍:https://zhuanlan.zhihu.com/p/22591838
简介:
The first focus of G1 is to provide a solution for users running applications that require large heaps with limited GC latency.This means heap sizes of around 6GB or larger,and a stable and predictable pause time below 0.5 senconds.
G1属于新生代和老生代收集器
G1的几个概念
Region
SATB:Snapshot-At-The-Beginning,它是通过Root Tracing得到的,GC开始时候存活对象的快照。
RSet:记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)
YoungGC
新对象进入Eden区
存活对象拷贝到Survivor区
存活时间达到年龄阈值是,对象晋升到Old区
MixedGC
不是FullGC,回收所有的Young和部分Old
global concurrent marking
global concurrent marking
1、initial marking phase:标记GC Root,STW
2、Root region scanning phase:标记存活Region
3、Concurrent marking phase:标记存活的对象
4、Remark phase:重新标记,STW
5、Cleanup phase:部分STW
MixedGC时机
initiationHeapOccupancyPercent:堆占有率达到这个数值则触发global concurrent marking,默认45%
G1HeapWastePercent:在global concurrent marking结束之后,可以知道区有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC
MixedGC相关参数
G1MixedGCLiveThresholdPercent:Old 区的region被回收时候的存活对象占比
G1MixedGCCountTarget:
一次global concurrent marking之后,最多执行Mixed GC的次数
G1OldCSetRegionThresholdPercent:一次Mixed GC中能被选入CSet的最多old区的region数量
常用参数
-XX:+UseG1GC 开启G1
-XX:G1HeapRegionSize=n,region的大小,1~32M,2048个
-XX:MaxGCPauseMilles=200最大停顿时间
-XX:G1NewSizePercent,-XX:G1MaxNewSizePercent
-XX:G1ReservePercent=10, 保留防止to space溢出
-XX:ParallelGCThreads=n STW线程数
-XX:ConcGCThreads=n 并发线程数=1/4*并发
JVM参数:
-标准参数,-x非标准参数,调优用-xx参数
查看虚拟机默认可设置的参数命令:Java -xx:PrintFlags -version
Java最小堆最大堆设同一个值,是为了防止抖动(拓展知识点)
GC TUNING 解决方式:
1:系统上线前:预估预优化JVM的各种垃圾回收选择
2:系统上线后:优化运行jvm运行环境解决jvm运行中出现的问题
3:各时间段重启
4系统垃圾回收机配置parallel scavenge+Parallel old替换成parallel scavenge+serial old或者parnew+serial +parnew+cms+serial old or G1
※内存突高,定位问题:
命令:查看java进程 :
jps -mlVv
或
ps -ef| grep java
gc (GC堆状态) jstat -gc id or jstat -gc id 1000 ->1000 毫秒刷新
jvisualvm jdk1.8自带本地图形化监控工具 可参考:https://blog.csdn.net/u012550080/article/details/81605189
arthas 阿里开源Java诊断工具:https://alibaba.github.io/arthas/
jad+redefine:线上热编译
查找定位问题方法:
arthas thread :查看占内存的thread
jmap -histo ID |head -20 占内存的对象 https://www.cnblogs.com/atomicbomb/p/7646662.html
最佳实践:
年轻代大小
避免使用-Xmn、-XX:NewRatio等显示设置Young区大小,会覆盖停顿时间目标
暂停时间目标
暂停时间不要太苛刻,其吞吐量目标是90%的应用程序的时间和10%的垃圾回收时间,太苛刻会直接影响到吞吐量
关于MixedGC调优
-XX:InitiatingHeapOccupancyPercent
-XX:G1MixedGCLiveThresholdPercent、-XX:G1HeapWastePercent
-XX:G1MixedGCCountTarget
-XX:G1OldCSetRegionThresholdPercent
MixedGC调优推荐:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations
G1垃圾收集器介绍:https://zhuanlan.zhihu.com/p/22591838
是否需要切换到G1
50%以上的堆被存活对象占用
对象分配和晋升的速度变化非常大
垃圾回收的时间特别长、超过了一秒
可视化gc日志分析工具
打印日志相关参数
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-Xloggc:$CATALINA_HOME/logs/gc.log :打印日志的位置
-XX:+PrintHeapAtGC
-XX:+PrintTenuringDistribution
cms日志
https://blogs.oracle.com/poonam/understanding-cms-gc-logs
g1日志
https://blogs.oracle.com/poonam/understanding-g1-gc-logs
在线工具:http://gceasy.io/
GCViewer
https://github.com/chewiebug/GCViewer
直接启动java -jar 启动就可以了
主要关注吞吐量和停顿时间这些值
Tomcat的gc调优实战
调优步骤:
打印gc日志
根据gc日志得到关键性指标
分析gc原因,调优jvm参数
初始化参数:
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps -XX:+DisableExplicitGC
-Xloggc:$CATALINA_HOME/logs/gc.log :打印日志的位置
-XX:+PrintHeapAtGC
-XX:+PrintTenuringDistribution
关于DisableExplicitGC这个,有必要了解一下,如果启动了这个参数,不能显示调用gc,System.gc()这个方法就没用了,那么在某一些使用到堆外内存的框架如netty中,就会存在内存泄漏的风险。
参考:https://blog.csdn.net/aitangyong/article/details/39403031
Parallel collector调优
parallel gc调优指南:
除非确定,否则不要设置最大堆内存
优先设置吞吐量目标
如果吞吐量目标达不到,调大最大内存,不能让os使用swap,如果仍然达不到,降低目标。
吞吐量能达到,GC时间太长、设置停顿时间的目标。
在官方文档的这个地方:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/ergonomics.html#sthref15
介绍了parallell gc调优的两种方式:
1、设置最大停顿时间XX:MaxGCPauseMillis
2、设置吞吐量XX:GCTimeRatio
这两个参数只适用于paraller collector
而在介绍Parallel collector的的文章这里:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/parallel.html#CHDCFBIF
说了另外两种方式:
3、设置内存动态变化:XX:YoungGenerationSizeIncrement、XX:TenuredGenerationSizeIncrement、XX:AdaptiveSizeDecrementScaleFactor,每次gc之后,都会看gc前后的内存,从而增加或者减少内存
4、通过查看gc的情况,设置内存大小,可以设置Xms(initial heap size) 、Xmx(maximum heap size)和Xmn(新生代的大小)还有MetaspaceSize和MaxMetaspaceSize
CMS GC调优
由于jdk1.8之后更加推荐使用G1,所以cms的调优自己看官网:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html#concurrent_mark_sweep_cms_collector
G1 GC调优
g1gc调优指南:
年轻代大小
避免使用-Xmn、-XX:NewRatio等显示设置Young区大小,会覆盖停顿时间目标
暂停时间目标
暂停时间不要太苛刻,其吞吐量目标是90%的应用程序的时间和10%的垃圾回收时间,太苛刻会直接影响到吞吐量
关于MixedGC调优
-XX:InitiatingHeapOccupancyPercent
-XX:G1MixedGCLiveThresholdPercent、-XX:G1HeapWastePercent
-XX:G1MixedGCCountTarget
-XX:G1OldCSetRegionThresholdPercent
其实这里的介绍就是官网的介绍
官网:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations
这里介绍了调优的三种方式:
1、避免设置固定的内存例如Xms(initial heap size) 、Xmx(maximum heap size)和Xmn(新生代的大小)还有MetaspaceSize和MaxMetaspaceSize,但是可以必要条件下可以调这些参数
2、设置最大停顿时间例如;MaxGCPauseMillis=100
3、对于mixed gc参数的设置,如:
-XX:InitiatingHeapOccupancyPercent、
-XX:G1MixedGCLiveThresholdPercent、
-XX:G1HeapWastePercent、
-XX:G1MixedGCCountTarget 、
-XX:G1OldCSetRegionThresholdPercent