尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【JVM】(7)JVM 调优工具详解及调优实战

【JVM】(7)JVM 调优工具详解及调优实战 前置说明事先启动一个 Web 应用程序用jps查看其进程 id接着用各种 JDK 自带命令优化应用。Jmapjmap命令可以用来查看内存信息实例个数以及占用内存大小。打开log.txt文件内容如下num序号instances实例数量bytes占用空间大小class name类名称[Cis achar[][Sis ashort[][Iis aint[][Bis abyte[][[Iis aint[][]二维 int 数组堆信息堆内存 dumpjmap-dump:formatb,fileeureka.hprof14660也可以设置内存溢出自动导出 dump 文件内存很大的时候可能会导不出来-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath./# 路径示例代码publicclassOOMTest{// JVM 设置// -Xms10M -Xmx10M -XX:PrintGCDetails -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPathD:\jvm.dumppublicstaticListObjectlistnewArrayList();publicstaticvoidmain(String[]args){ListObjectlistnewArrayList();inti0;intj0;while(true){list.add(newUser(i,UUID.randomUUID().toString()));newUser(j--,UUID.randomUUID().toString());}}}可以用jvisualvm命令工具导入该 dump 文件分析Jstack用jstack加进程 id 查找死锁见如下示例。死锁示例DeadLockTestpublicclassDeadLockTest{privatestaticObjectlock1newObject();privatestaticObjectlock2newObject();publicstaticvoidmain(String[]args){newThread(()-{synchronized(lock1){try{System.out.println(thread1 begin);Thread.sleep(5000);}catch(InterruptedExceptione){}synchronized(lock2){System.out.println(thread1 end);}}}).start();newThread(()-{synchronized(lock2){try{System.out.println(thread2 begin);Thread.sleep(5000);}catch(InterruptedExceptione){}synchronized(lock1){System.out.println(thread2 end);}}}).start();System.out.println(main thread end);}}jstack输出中的关键信息含义Thread-1线程名prio5优先级 5tid0x000000001fa9e000线程 idnid0x2d64线程对应的本地线程标识 nidjava.lang.Thread.State: BLOCKED线程状态还可以用jvisualvm自动检测死锁找出占用 CPU 最高的线程堆栈信息packagecom.tuling.jvm;/** * 运行此代码cpu 会飙高 */publicclassMath{publicstaticfinalintinitData666;publicstaticUserusernewUser();publicintcompute(){// 一个方法对应一块栈帧内存区域inta1;intb2;intc(ab)*10;returnc;}publicstaticvoidmain(String[]args){MathmathnewMath();while(true){math.compute();}}}排查步骤使用命令top -p pid显示你的 Java 进程的内存情况pid 是你的 Java 进程号比如19663。按H获取每个线程的内存情况。找到内存和 CPU 占用最高的线程 tid比如19664。转为十六进制得到0x4cd0此为线程 id 的十六进制表示。执行jstack 19663 | grep -A 10 4cd0得到线程堆栈信息中4cd0这个线程所在行的后面 10 行从堆栈中可以发现导致 CPU 飙高的调用方法。查看对应的堆栈信息找出可能存在问题的代码。远程连接 jvisualvm启动普通的 jar 程序 JMX 端口配置java\-Dcom.sun.management.jmxremote.port8888\-Djava.rmi.server.hostname192.168.50.60\-Dcom.sun.management.jmxremote.sslfalse\-Dcom.sun.management.jmxremote.authenticatefalse\-jaryour-app.jarPS-Dcom.sun.management.jmxremote.port为远程机器的 JMX 端口-Djava.rmi.server.hostname为远程机器 IPTomcat 的 JMX 配置在catalina.sh文件里最后一个JAVA_OPTS的赋值语句下一行增加如下配置行JAVA_OPTS$JAVA_OPTS-Dcom.sun.management.jmxremote.port8888 -Djava.rmi.server.hostname192.168.50.60 -Dcom.sun.management.jmxremote.sslfalse -Dcom.sun.management.jmxremote.authenticatefalseJinfo查看正在运行的 Java 应用程序的扩展参数查看 JVM 的参数查看 Java 系统参数Jstatjstat命令可以查看堆内存各部分的使用量以及加载类的数量。命令的格式如下jstat[-命令选项][vmid][间隔时间(毫秒)][查询次数]注意使用的 JDK 版本是 JDK 8。垃圾回收统计jstat -gc pid最常用可以评估程序内存使用及 GC 压力整体情况。S0C第一个幸存区的大小单位 KBS1C第二个幸存区的大小S0U第一个幸存区的使用大小S1U第二个幸存区的使用大小EC伊甸园区的大小EU伊甸园区的使用大小OC老年代大小OU老年代使用大小MC方法区大小元空间MU方法区使用大小CCSC压缩类空间大小CCSU压缩类空间使用大小YGC年轻代垃圾回收次数YGCT年轻代垃圾回收消耗时间单位 sFGC老年代垃圾回收次数FGCT老年代垃圾回收消耗时间单位 sGCT垃圾回收消耗总时间单位 s堆内存统计NGCMN新生代最小容量NGCMX新生代最大容量NGC当前新生代容量S0C第一个幸存区大小S1C第二个幸存区的大小EC伊甸园区的大小OGCMN老年代最小容量OGCMX老年代最大容量OGC当前老年代大小OC当前老年代大小MCMN最小元数据容量MCMX最大元数据容量MC当前元数据空间大小CCSMN最小压缩类空间大小CCSMX最大压缩类空间大小CCSC当前压缩类空间大小YGC年轻代 GC 次数FGC老年代 GC 次数新生代垃圾回收统计S0C第一个幸存区的大小S1C第二个幸存区的大小S0U第一个幸存区的使用大小S1U第二个幸存区的使用大小TT对象在新生代存活的次数MTT对象在新生代存活的最大次数DSS期望的幸存区大小EC伊甸园区的大小EU伊甸园区的使用大小YGC年轻代垃圾回收次数YGCT年轻代垃圾回收消耗时间新生代内存统计NGCMN新生代最小容量NGCMX新生代最大容量NGC当前新生代容量S0CMX最大幸存 1 区大小S0C当前幸存 1 区大小S1CMX最大幸存 2 区大小S1C当前幸存 2 区大小ECMX最大伊甸园区大小EC当前伊甸园区大小YGC年轻代垃圾回收次数FGC老年代回收次数老年代垃圾回收统计MC方法区大小MU方法区使用大小CCSC压缩类空间大小CCSU压缩类空间使用大小OC老年代大小OU老年代使用大小YGC年轻代垃圾回收次数FGC老年代垃圾回收次数FGCT老年代垃圾回收消耗时间GCT垃圾回收消耗总时间老年代内存统计OGCMN老年代最小容量OGCMX老年代最大容量OGC当前老年代大小OC老年代大小YGC年轻代垃圾回收次数FGC老年代垃圾回收次数FGCT老年代垃圾回收消耗时间GCT垃圾回收消耗总时间元数据空间统计MCMN最小元数据容量MCMX最大元数据容量MC当前元数据空间大小CCSMN最小压缩类空间大小CCSMX最大压缩类空间大小CCSC当前压缩类空间大小YGC年轻代垃圾回收次数FGC老年代垃圾回收次数FGCT老年代垃圾回收消耗时间GCT垃圾回收消耗总时间各区域使用比例summaryS0幸存 1 区当前使用比例S1幸存 2 区当前使用比例E伊甸园区使用比例O老年代使用比例M元数据区使用比例CCS压缩使用比例YGC年轻代垃圾回收次数FGC老年代垃圾回收次数FGCT老年代垃圾回收消耗时间GCT垃圾回收消耗总时间JVM 运行情况预估用jstat gc -pid命令可以计算出如下一些关键数据有了这些数据就可以采用之前介绍过的优化思路先给自己的系统设置一些初始性的 JVM 参数比如堆内存大小、年轻代大小、Eden 和 Survivor 的比例、老年代的大小、大对象的阈值、大龄对象进入老年代的阈值等。年轻代对象增长的速率可以执行命令jstat -gc pid 1000 10每隔 1 秒执行 1 次命令共执行 10 次通过观察 EUEden 区的使用来估算每秒 Eden 大概新增多少对象。如果系统负载不高可以把频率 1 秒换成 1 分钟甚至 10 分钟来观察整体情况。注意一般系统可能有高峰期和日常期所以需要在不同的时间分别估算不同情况下对象增长速率。Young GC 的触发频率和每次耗时知道年轻代对象增长速率我们就能根据 Eden 区的大小推算出 Young GC 大概多久触发一次Young GC 的平均耗时可以通过YGCT / YGC公式算出。根据结果我们大概就能知道系统大概多久会因为 Young GC 的执行而卡顿多久。每次 Young GC 后有多少对象存活和进入老年代假设已经大概知道 Young GC 的频率比如每 5 分钟一次那么可以执行命令jstat -gc pid 300000 10观察每次结果 Eden、Survivor 和老年代使用的变化情况。在每次 GC 后 Eden 区使用一般会大幅减少Survivor 和老年代都有可能增长这些增长的对象就是每次 Young GC 后存活的对象同时还可以看出每次 Young GC 后进入老年代大概多少对象从而可以推算出老年代对象增长速率。Full GC 的触发频率和每次耗时知道了老年代对象的增长速率就可以推算出 Full GC 的触发频率了Full GC 的每次耗时可以用公式FGCT / FGC计算得出。优化思路简单来说就是尽量让每次 Young GC 后的存活对象小于 Survivor 区域的 50%都留存在年轻代里尽量别让对象进入老年代尽量减少 Full GC 的频率避免频繁 Full GC 对 JVM 性能的影响。系统频繁 Full GC 导致系统卡顿是怎么回事机器配置2 核 4GJVM 内存大小2G系统运行时间7 天期间发生的 Full GC 次数和耗时500 多次200 多秒期间发生的 Young GC 次数和耗时1 万多次500 多秒大致算下来每天会发生 70 多次 Full GC平均每小时 3 次每次 Full GC 在 400 毫秒左右每天会发生 1000 多次 Young GC每分钟会发生 1 次每次 Young GC 在 50 毫秒左右。JVM 参数设置如下-Xms1536M-Xmx1536M-Xmn512M-Xss256K-XX:SurvivorRatio6-XX:MetaspaceSize256M-XX:MaxMetaspaceSize256M-XX:UseParNewGC-XX:UseConcMarkSweepGC-XX:CMSInitiatingOccupancyFraction75-XX:UseCMSInitiatingOccupancyOnly大家可以结合「对象挪动到老年代那些规则」推理下我们这个程序可能存在的一些问题。经过分析感觉可能会由于对象动态年龄判断机制导致 Full GC 较为频繁。为了给大家看效果我模拟了一个示例程序见课程对应工程代码jvm-full-gc打印了jstat的结果如下jstat-gc13456200010000对于对象动态年龄判断机制导致的 Full GC 较为频繁可以先试着优化下 JVM 参数把年轻代适当调大点-Xms1536M-Xmx1536M-Xmn1024M-Xss256K-XX:SurvivorRatio6-XX:MetaspaceSize256M-XX:MaxMetaspaceSize256M-XX:UseParNewGC-XX:UseConcMarkSweepGC-XX:CMSInitiatingOccupancyFraction92-XX:UseCMSInitiatingOccupancyOnly优化完发现没什么变化Full GC 的次数比 Minor GC 的次数还多了我们可以推测下 Full GC 比 Minor GC 还多的原因有哪些元空间不够导致的多余 Full GC显式调用System.gc()造成多余的 Full GC这种一般线上尽量通过-XX:DisableExplicitGC参数禁用如果加上了这个 JVM 启动参数那么代码中调用System.gc()没有任何效果老年代空间分配担保机制。最快速度分析完这些我们推测的原因以及优化后我们发现 Young GC 和 Full GC 依然很频繁了而且看到有大量的对象频繁地被挪动到老年代。这种情况我们可以借助jmap命令大概看下是什么对象查到了有大量User对象产生这个可能是问题所在但不确定还必须找到对应的代码确认。如何去找对应的代码代码里全文搜索生成User对象的地方适合只有少数几处地方的情况如果生成User对象的地方太多无法定位具体代码我们可以同时分析下占用 CPU 较高的线程。一般有大量对象不断产生对应的方法代码肯定会被频繁调用占用的 CPU 必然较高。可以用上面讲过的jstack或jvisualvm来定位 CPU 使用较高的代码最终定位到的代码如下importjava.util.ArrayList;importjava.util.List;RestControllerpublicclassIndexController{RequestMapping(/user/process)publicStringprocessUserData()throwsInterruptedException{// 一次查询出大量示例约 500MUser 对象未做分页/限制ListUserusersqueryUsers();for(Useruser:users){// 业务处理...对象朝生夕死却大量进入老年代}returnsuccess;}}注以上IndexController为根据原文可见片段及上下文重建的示意代码原导出文档此处被截断仅保留了import、类定义与queryUsers()调用部分。同时Java 的代码也是需要优化的一次查询出 500M 的对象出来明显不合适。要根据之前说的各种原则尽量优化到合适的值尽量消除这种朝生夕死的对象导致的 Full GC。内存泄漏再给大家讲一种情况一般电商架构可能会使用多级缓存架构就是 Redis 加上 JVM 级缓存。大多数同学可能为了图方便对于 JVM 级缓存就简单使用一个HashMap于是不断往里面放缓存数据但是很少考虑这个 map 的容量问题结果这个缓存 map 越来越大一直占用着老年代的很多空间时间长了就会导致 Full GC 非常频繁——这就是一种内存泄漏对于一些老旧数据没有及时清理导致一直占用着宝贵的内存资源。时间长了除了导致 Full GC还有可能导致 OOM。这种情况完全可以考虑采用一些成熟的 JVM 级缓存框架来解决比如 Ehcache 等自带一些 LRU 数据淘汰算法的框架来作为 JVM 级的缓存。参考链接http://note.youdao.com/noteshare?id5cc182642eb02bc64197788c7722baaesubE333076E3DCC4A6C9E01CA6BD05DD6A0
返回列表