GC Tuning
在前面我们学习了GC相关知识,学习了基本的垃圾收集器使用,下面来简单对JVM调优入门,了解相关概念,如果想要深入理解,必须要进行实际的生产环境的磨练才可以掌握
路漫漫其修远兮!
一、常见垃圾回收器组合参数设定:(1.8)
-
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
小型程序才会用这种单线程的Serial,默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
JDK1.8及以前:PS + PO;JDK1.9:G1
-
-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
使用PN + CMS + Serial Old;现在很少使用CMS了,因为G1完虐CMS,直接使用G1即可
-
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
-
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-
-XX:+UseG1GC = G1
-
Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC
- java +XX:+PrintCommandLineFlags -version
- 通过GC的日志来分辨
-
Linux下1.8版本默认的垃圾回收器到底是什么?
- 1.8.0_181 默认(看不出来)Copy MarkCompact
- 1.8.0_222 默认 PS + PO
二、了解JVM常用命令行参数
-
JVM的命令行参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
-
HotSpot参数分类
标准: - 开头,所有的HotSpot都支持
非标准:-X 开头,特定版本HotSpot支持特定命令
不稳定:-XX 开头,下个版本可能取消
java -version
java -X
试验用demo程序:
import java.util.List; import java.util.LinkedList; public class HelloGC { public static void main(String[] args) { System.out.println("HelloGC!"); List list = new LinkedList(); for(;;) { byte[] b = new byte[1024*1024]; list.add(b); } } }
- 区分概念:内存泄漏memory leak,内存溢出out of memory
内存泄漏是指有些无用的对象,并不能被回收,仍然占用内存,内存泄漏不一定内存溢出,如果垃圾回收一直都能保持足够用的内存空间,内存泄漏也就无所谓;但是当大量内存泄漏之后,很有可能导致内存溢出 - java -XX:+PrintCommandLineFlags HelloGC
- java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC
PrintGCDetails PrintGCTimeStamps PrintGCCauses - java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
- java -XX:+PrintFlagsInitial 默认参数值
- java -XX:+PrintFlagsFinal 最终参数值
- java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
- java -XX:+PrintFlagsFinal -version |grep GC
三、PS—GC日志详解
每种垃圾回收器的日志格式是不同的!
PS日志格式
heap dump部分:
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址
total = eden + 1个survivor
四、JVM调优
1、调优前的基础概念
吞吐量
用户代码执行时间:(用户代码执行时间 + 代码执行时间)
比例越大说明程序用来执行业务代码的时间越大,有效时间越高,吞吐量越大越好
响应时间
STW越短,系统越顺畅、不卡顿,响应时间越好
2、什么是调优?
所谓调优,首先确定需要向什么方向调优,也就是追求什么?**吞吐量优先,还是响应时间优先?**还是在满足一定的响应时间的情况下,要求达到多大的吞吐量?…
追求吞吐量:科学计算、数据挖掘(爬虫)、thrput等吞吐量优先的一般选择收集器组合:(PS + PO)
追求响应时间:电商网站、调用API第三方接口等需要响应时间优先的选择收集器组合:(PN + CMS、G1)
1、根据需求进行JVM规划和预调优
2、优化运行JVM运行环境(慢,卡顿)
3、解决JVM运行过程中出现的各种问题(JVM调优主要就是为了解决OOM问题)
3、调优,从规划开始
-
调优,从业务场景开始,没有业务场景的调优都是耍流氓
-
无监控(压力测试,能看到结果),不调优
-
步骤:
- 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
- 响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
- 吞吐量 = 用户线程时间 /( 用户线程时间 + GC时间) [PS]
- 选择回收器组合
- 计算内存需求(经验值 1.5G 16G)
- 选定CPU(越高越好)
- 设定年代大小、升级年龄
- 设定日志参数
- -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
- 或者每天产生一个日志文件
- 观察日志情况
大流量处理的原则:分而治之
- 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
五、JVM性能监控及故障处理工具
给一个系统定位问题的时候,知识、经验是关键基础,数据是依据,工具是运用知识处理数据的手段。这里说的数据有异常堆栈、JVM运行日志、垃圾收集器日志、线程快照文件、堆转储快照文件等。恰当的使用虚拟机故障处理、分析的工具可以提升我们分析数据、定位并解决问题的效率,但是我们要认识到——工具只是知识技能的一层包装,没有什么工具能 “ 包治百病 ”
1、基础故障处理工具
用于监视虚拟机运行状态和进行故障处理的工具
1、jps:虚拟机进程状况工具
JPS:JVM Process Status Tool,可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(main方法所在的类)名称以及进程的唯一ID
虽然功能单一,但是绝对是使用频率最高的JDK命令行工具,因为其他JDK工具大多需要依靠其查询得到进程ID来确定要监控的是哪一个虚拟机进程
2、jstat:虚拟机统计信息监视工具
jstat:用于监视虚拟机各种运行状态信息的命令行工具
可以显示虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据,是最常用的运行期定位虚拟机性能问题的命令行工具(非图形界面)
虽然可视化的监视工具(图形界面)展现数据更直观,但是图形化界面会更加占用系统资源,会影响系统稳定性,多数服务器管理员仍然更擅长使用命令行(更能装13)
3、jinfo:JVM配置信息工具
jinfo的作用是实时查看和调整虚拟机各项参数
4、jmap:java内存映像工具(重要)
jmap(Memory Map for Java)命令用于生成堆转储快照文件(一般称为heapdump或dump文件),通过对dump文件的分析,我们可以发现出现的问题所在
jmap的作用并不仅仅是为了获取堆转储快照,它还可以查询finalize执行队列,Java堆和方法区的详细信息,如空间使用率、当前使用的是哪一种收集器等
但是使用jmap命令的时候要注意:jmap命令dump出堆转储文件的时候,导出的文件可能高达数十G,会严重影响系统的运行,所以在生产环境中不要轻易使用 jmap 命令,在做好准备的情况下才可以dump出堆转储文件
5、jhat:JVM堆转储快照分析工具
JDK提供jhat命令与jmap来搭配使用,来分析jmap命令dump出的堆转储快照
jhat中内置了一个微型的web服务器,生产堆转储快照的分析结果后可以直接在浏览器查看
一般不会有人使用jhat命令来分析堆转储快照文件,原因如下:
1、分析工作是一个耗时且极为耗费硬件资源的过程,一般会复制到其他机器上进行分析
2、分析功能相对比较简陋,相较于 VisualVM 和其他专门用来分析堆转储快照的工具,jhat太简陋了
6、jstack:JVM堆栈跟踪工具
jstack命令用于生成虚拟机当前时刻的线程快照(一般称为 javacore 文件)
线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的通常是为了定位线程长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的原因
排查线程阻塞、CPU飙高的常用命令
2、可视化故障处理工具
JDK中除了大量的命令行工具外,还提供了功能集成度更高的可视化工具,使用这些可视化工具能够以更加简洁明了的方式进行进程故障诊断和调试工作
这类工具主要有:JConsole、VisualVM和 JMC ,除此之外阿里的 arthas 在线排查工具也很好用
1、JConsole:JVM监视与管理控制台
JConsole是一款可视化监视、管理工具,可以对系统进行信息收和参数动态调整:
- 内存监控:相当于可视化的 jstate 命令工具,用于监视被收集器管理的JVM内存的变化趋势
- 线程监控:相当于可视化的 jstack 命令工具,用于监视出现异常的线程停顿(如死循环、锁等待、请求外部资源等)
2、VisualVM:多合—故障处理工具
Visual VM是功能最强大的运行监视和故障处理程序之一,是官方主力发展的JVM故障处理工具
Visual VM有一个很大的优点:不需要被监视的程序基于特殊Agent去运行,因此它的通用性很强,对应用程序实际性能的影响也较小,因此它可以直接运行在生成环境中,这是其他工具不能媲美的
3、arthas在线排查工具
为什么需要在线排查?
在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。
对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。所以需要使用arthas在线排查工具是阿里研发,很好用
arthas常用命令:
-
jvm:观察jvm信息
-
thread:定位线程问题
-
dashboard:观察系统情况
-
heapdump + jhat:导出堆情况离线文件,离线进行分析,类似与jmap -dump,也会对系统有影响尽量少用
使用jhat命令可以查看文件中的所有对象情况,会生成端口,通过浏览器远程访问,对所有对象进行问题分析
以上的这些功能就算不使用arthas,也可以通过比较low的原生实现,只不过arthas用起来更友好
-
jad反编译,可以反编译出类的源文件(独特且强大)
1、能够对动态代理生成类的问题定位
2、能够查看第三方的类(观察代码)
3、能够定位版本问题,确定自己最新提交的版本是不是被使用
-
redefine 热替换(独特且强大)
-
有些jmap能完成的功能,arthas还不能完成,所以arthas并不能完全代替jmap
六、常见系统优化面试题
1、慢、卡顿、怎么优化?
有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了
- 为什么原网站慢?
很多用户浏览数据,很多数据load到内存,内存不足,频繁FullGC,STW长,响应时间变慢 - 为什么会更卡顿?
内存越大,FGC需要扫描处理的空间越大,STW时间越长 - 咋办?
把默认的PS + PO 调整成 PN + CMS 或者 G1
2、系统CPU经常100%,如何调优?(面试高频)
CPU经常100%,那么一定有线程在占用系统资源,把它就出来分析问题:
- 找出哪个进程cpu高(top)
- 该进程中的哪个线程cpu高(top -Hp)
- 导出该线程的堆栈 (jstack)
- 查找哪个方法(栈帧)消耗时间 (jstack)
- 工作线程占比高 | 垃圾回收线程占比高
3、系统内存飙高,如何查找问题?(面试高频)
- 导出堆转储快照文件进行离线问题分析:jmap -dump
- 对堆内存文件进行问题分析 (jhat jvisualvm mat jprofiler … )
- 或者使用在线排查工具:如arthas
一般是运维团队首先受到报警信息(CPU飙高、Memory爆满)
top命令观察到问题:内存不断增长 CPU占用率居高不下
top -Hp 观察进程中的线程,哪个线程CPU和内存占比高
jps定位具体线程,jstack定位线程状态,重点关注WAITING BLOCKED
为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称?就是为了在线程池发生问题时,能够快速定位到在哪里发生的问题,尽快进行排查
怎么样自定义线程池里的线程名称?(多线程中,自定义ThreadFactory)
4、如何定位OOM?
如果面试官问你是怎么定位OOM问题?——回答使用命令行工具
常用命令行工具:
1、jmap - histo XX| head -20
这个命令可以在线查看哪些对象出现的问题。如上面就是查找占用率对象的前二十,一般就能查找出现问题的对象
2、jmap -dump:导出堆文件,对文件进行问题分析、排查(会影响系统运行,慎用!!!)
jdump执行期间会对进程产生很大影响,线上系统内存特别大就会造成卡顿(电商不适合),解决办法(面试如何回答):
- 设定了参数HeapDump,OOM的时候会自动产生堆转储文件
- 有很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
3、然后使用MAT / jhat /jvisualvm 进行dump文件分析
https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
4、最后根据堆dump文件离线分析,找到代码的问题
七、GC案例—回答OOM如何产生?
OOM产生的原因多种多样,有些程序未必产生OOM,不断FGC(CPU飙高,但内存回收特别少) (上面案例)
1、硬件升级系统反而卡顿的问题
有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低
为什么原网站慢?
很多用户浏览数据,很多数据load到内存,到最后堆内存不足,频繁FullGC优化硬件配置之后,为什么会更卡顿?
由于堆内存变大,一旦老年代满了需要触发FullGC,而由于内存变大,FullGC造成的STW时间更长,造成的现象就是用户使用的卡顿更加严重怎么优化?
把默认的垃圾收集器组合PS + PO 调整成 PN + CMS 或者 G1
2、线程池不当运用产生OOM问题(能说)
由于在线程池的过程中,很容易出现其中某些线程在使用的时候,出现内存逃逸,无效对象一直被占用,产生巨量的无效对象,就会造成频繁GC;或者使用某些需要加锁,加锁对象一直不释放,导致大量的线程在循环等待,也会导致系统卡顿,比如CPU飙高或者不断GC
解决办法?
使用在线排查工具如arthas,或者使用jmap -dump导出堆内存文件,或者其他的 jmap 命令定位对象、线程的异常情况
3、Smile的生产环境实例
系统频繁出现GC,但是由于系统需要给各地很多地方使用,不敢也不能擅自使用jmap -dump命令导出堆文件,只能让系统处在一个很卡、但是能用的情况,只能定时重启、reboot重新部署延缓卡顿
解决办法?
加内存 + 更换垃圾回收器为G1
真正问题在哪儿?仍然不知道,无法定位,但是扩内存 + G1之后,GC次数不再频繁
4、tomcat http-header-size过大问题
在tomcat中通过参数可以调整http-header-size大小,默认的是4096,每来一个http请求,就会申请4096个字节,你可以说是不知道哪一个同事把这个参数调整的非常大,一旦申请量过大,就会导致内存溢出
解决办法?
通过命令行jmap查看哪些对象占用内存过大,通过日志查看到是Http11OutputBuffer对象占用内存特别大,请求过多导致很多Http11OutputBuffer对象产生,导致OOM,查找相关配置参数发现谁改动了配置
5、对比更优写法
严格来说不是GC相关问题,但是能说明知识点,所以列在了这里,面试有可能会用到
Object o = null;
for(int i=0; i<100; i++) {
o = new Object();
//业务处理
}
for(int i=0; i<100; i++) {
Object o = new Object();
//业务处理
}
比较一下这两段程序的异同,分析哪一个是更优的写法:
第一种:只存在一个引用,每次new出一个对象,引用就指向新的对象,原来的对象由于不存在引用就会被回收
第二种:每一个对象都会有一个引用,在循环退出的时候,所有的对象才会被当作垃圾回收
由于每次只有一个对象需要参与运算或者说业务逻辑,所以上面的写法更加节省资源
6、重写finalize引发频繁GC
小米云,HBase同步系统,系统通过nginx访问超时报警,最后排查,C++程序员重写finalize引发频繁GC问题。为什么C++程序员会重写finalize?
因为C++是手动管理内存,需要delete释放内存,需要重写析构函数,而C++析构函数和finalize同名,所以C++程序员就重写了finalize,finalize耗时比较长,很多对象一下子涌过来,处理不了就导致了在Java中频繁GC
7、并发中使用Distuptor问题
Distuptor有个可以设置链的长度,如果过大,然后对象大,消费完不主动释放,会溢出
需要对Distuptor比较熟悉,能够说明Distuptor的使用场景及使用细节,不过可以由此引出多线程知识
附录——常见面试题
-
-XX:MaxTenuringThreshold控制的是什么?
A: 对象升入老年代的年龄
B: 老年代触发FGC时的内存垃圾比例 -
生产环境中,倾向于将最大堆内存和最小堆内存设置为:(为什么?)
A: 相同 B:不同 -
JDK1.8默认的垃圾回收器是:
A: ParNew + CMS
B: G1
C: PS + ParallelOld
D: 以上都不是 -
什么是响应时间优先?
-
什么是吞吐量优先?
-
ParNew和PS的区别是什么?
-
ParNew和ParallelOld的区别是什么?(年代不同,算法不同)
-
长时间计算的场景应该选择:A:停顿时间 B: 吞吐量
-
大规模电商网站应该选择:A:停顿时间 B: 吞吐量
-
HotSpot的垃圾收集器最常用有哪些?
-
常见的HotSpot垃圾收集器组合有哪些?
-
JDK1.7 1.8 1.9的默认垃圾回收器是什么?如何查看?
-
所谓调优,到底是在调什么?
-
如果采用PS + ParrallelOld组合,怎么做才能让系统基本不产生FGC
-
如果采用ParNew + CMS组合,怎样做才能够让系统基本不产生FGC
1.加大JVM内存
2.加大Young的比例
3.提高Y-O的年龄
4.提高S区比例
5.避免代码内存泄漏
-
G1是否分代?G1垃圾回收器会产生FGC吗?
-
如果G1产生FGC,你应该做什么?
1. 扩内存 2. 提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大) 3. 降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)
-
问:生产环境中能够随随便便的dump吗?
小堆影响不大,大堆会有服务暂停或卡顿(加live可以缓解),dump前会有FGC -
问:常见的OOM问题有哪些?
栈 堆 MethodArea 直接内存