JVM系列之性能调优案例

  • Post author:
  • Post category:其他




1、调优基本问题



1.1、为什么要调优?

  • 删除线格式 防止出现OOM,进行JM规划和预调优
  • 解决程序运行中各种OOM
  • 减少Full GC出现的频率,解决运行慢、卡顿问题



1.2、调优的大方向

  • 合理地编写代码
  • 充分并合理的使用硬件资源
  • 合理地进行JVM调优



1.3、不同阶段的考虑

  • 上线前
  • 项目运行阶段
  • 线上出现OOM



1.4、调优监控的依据

  • 运行日志
  • 异常堆栈
  • GC日志
  • 线程快照
  • 堆转储快照



1.5、性能优化的步骤



1.5.1、第1步:熟悉业务场景



1.5.2、第2步(发现问题)︰性能监控

一种以非强行或者入侵方式收集或查看应用运营性能数据的活动,监控通常是指一种在生产、质量评估或者开发环境下实施的带有预防或主动性的活动。

当应用相关干系人提出性能问题却没有提供足够多的线索时。

首先我们需要进行性能监控,随后是性能分析。

监控前,设置好回收器组合,选定CPU(主频越高越好),设置年代比例,设置日志参数(生产环境中通常不会只设置一个日志文件)。比如:

-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log 
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5 
-XX:GCLogFileSize=20M 
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps 
-XX:+PrintGCCause


1.5.2.1、可能出现的情况
  • GC频繁
  • cpu load过高
  • OOM
  • 内存泄漏
  • 死锁
  • 程序响应时间较长



1.5.2、第3步(排查问题):性能分析团

  • 打印GC日志,通过GCviewer或者http://gceasy.io来分析日志管息
  • 灵活运用命令行工具,jstack, jmap, jinfo等
  • dump出堆文件,使用内存分析工具分析文件
  • 使用阿里Arthas,或jconsole,JVisualVM来实时查看JVM状态
  • jstack查看堆栈信息



1.5.4、第4步(解决问题):性能调优

一种为改善应用响应性或吞吐量而更改参数、源代码、属性配置的活动,性能调优是在性能监控、性能分析之后的活动。

  • 适当增加内存,根据业务背景选择垃圾回收器
  • 优化代码,控制内存使用
  • 增加机器,分散节点压力
  • 合理设置线程池线程数量
  • 使用中间件提高程序效率,比如缓存,消息队列等
  • 其他…



1.6、总结

调优,从业务场景开始,没有业务场景的调优都是耍流氓,无监控,不调优!

使用的是Jmeter进行压测。



1.7、面试题

  • 如何优化减少Full GC?(阿里-闲鱼)
  • 当出现了内存溢出,你怎么排错。(京东)
  • 有实际的VM性能调优案例吗?重点需要关注哪些核心参数?(滴滴)
  • OOM说一下?怎么排查?哪些会导致OOM? OOM出现在什么时候(腾讯)
  • JVM性能调优都做了什么?(支付宝)
  • 有做过JVM内存优化吗?(小米)
  • JVM的编译优化(蚂蚁金服)
  • JVM性能调优都做了什么(蚂蚁金服)
  • JVM怎样调优,堆内存栈空间设置多少合适…(蚂蚁金服)
  • JVM相关的分析工具使用过的有哪些?具体的性能调优步骤如何(蚂蚁金服)
  • 如何进行JVM调优?有哪些方法?(阿里)
  • JVM如何调优、参数怎么调?(字节跳动)
  • 每秒几十万并发的秒杀系统为什么会频繁发生GC?(京东)
  • 日均百万级交易系统如何优化JVM?(京东)
  • 线上生产系统OOM如何监控及定位与解决?(京东)
  • 高并发系统如何基于G1垃圾回收器优化性能?(京东)



2、性能优化案例1:调整堆大小提高服务的吞吐量

初始配置

export CATALINA_OPTS="$CATALINA_OPTS -Xms30m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:SurvivorRatio=8"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx120m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseParallelGC"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDetails"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MetaspaceSize=64m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDateStamps"
export CATALINA_OPTS="$CATALINA_OPTS -Xloggc:/opt/model/tomcat-8.5/logs/gc.log"

在这里插入图片描述

在这里插入图片描述

优化配置

export CATALINA_OPTS="$CATALINA_OPTS -Xms120m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:SurvivorRatio=8"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx120m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseParallelGC"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDetails"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MetaspaceSize=64m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDateStamps"
export CATALINA_OPTS="$CATALINA_OPTS -Xloggc:/opt/model/tomcat-8.5/logs/gc.log"

在这里插入图片描述

在这里插入图片描述



3、性能优化案例2:合理配置堆内存

在案例1中我们讲到了增加内存可以提高系统的性能而且效果显著,那么随之带来的一个问题就是,我们增加多少内存比较合适?如果内存过大,那么如果产生FullGC的时候,GC时间会相对比较长,如果内存较小,那么就会频繁的触发GC,在这种情况下,我们该如何合理的适配堆内存大小呢?

分析:

依据的原则是根据Java Performance里面的推荐公式来进行设置。

在这里插入图片描述



3.1、推荐配置

Java整个堆大小设置:

Xmx和Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍。

方法区(永久代 PermSize和MaxPermSize或元空间MetaspaceSize和MaxMetaspaceSize)设置为老年代存活对象的1.2-1.5倍。

年轻代Xmn的设置为老年代存活对象的1-1.5倍。

老年代的内存大小设置为老年代存活对象的2-3倍。

但是,上面的说法也不是绝对的,也就是说这给的是一个参考值,根据多种调优之后得出的一个结论,大家可以根据这个值来设置一下我们的初始化内存,在保证程序正常运行的情况下,我们还要去查看GC的回收率,GC停顿耗时,内存里的实际数据来判断,Full GC是基本上不能有的,如果有就要做内存Dump分析,然后再去做一个合理的内存分配。

我们还要注意到一点就是,上面说的老年代存活对象怎么去判定。



3.2、如何计算老年代存活对象

如何计算老年代存活对象



3.2.1、方式1:查看日志


推荐/比较稳妥!


JVM参数中添加GC日志,GC日志中会记录每次FullGC之后各代的内存大小,观察老年代GC之后的空间大小。可观察一段时间内(比如2天)的FullGC之后的内存情况,根据多次的FullGC之后的老年代的空间大小数据来预估FullGC之后老年代的存活对象大小(可根据多次FullGC之后的内存大小取平均值)。



3.2.2、方式2:强制触发FullGc

方式2:强制触发FullGC,会影响线上服务,慎用!

方式1的方式比较可行,但需要更改JVM参数,并分析日志。同时,在使用CMS回收器的时候,有可能不能触发FullGC,所以日志中并没有记录FullGC的日志。在分析的时候就比较难处理。所以,有时候需要强制触发一次FullGC,来观察FullGC之后的老年代存活对象大小。


注:强制触发FullGC,会造成线上服务停顿(STW),要谨慎!建议的操作方式为

,在强制FullGC前先把服务节点摘除,FullGC之后再将服务挂回可用节点,对外提供服务,在不同时间段触发FullGC,根据多次FullGC之后的老年代内存情况来预估FullGC之后的老年代存活对象大小


如何强制触发Full GC?


1、jmap -dump:live,format=b,file=heap.bin 将当前的存活对象dump到文件,此时会触发FullGC

2、jmap -histo:live 打印每个class的实例数目,内存占用,类全名信息…live子参数加上后,只统计活的对象数量.此时会触发FullGC

3、在性能测试环境,可以通过Java监控工具来触发FullGC,比如使用VisualVM和JConsole,VisualVM集成了JConsole,VisualVM或者JConsole上面有一个触发GC的按钮。



3.2.3、

你会估算GC频率吗?

正常情况我们应该根据我们的系统来进行一个内存的估算,这个我们可以在测试环境进行测试,最开始可以将内存设置的大一些,比如4G这样,当然这也可以根据业务系统估算来的。

比如从数据库获取一条数据占用128个字节,需要获取1000条数据,那么一次读取到内存的大小就是((128 B/1024 Kb/1024M)* 1000 = 0.122M,那么我们程序可能需要并发读取,比如每秒读取100次,那么内存占用就是0.122

100 = 12.2M,如果堆内存设置1个G,那么年轻代大小大约就是333M,那么333M

80%/12.2M =21.84s,也就是说我们的程序几乎每分钟进行两到三次youngGC。这样可以让我们对系统有一个大致的估算。



3.2.4、案例分析

现在我们通过idea启动springboot工程,我们将内存初始化为1024M。我们这里就从1024M的内存开始分析我们的GC日志,根据我们上面的一些知识来进行一个合理的内存设置。

压测接口

    @RequestMapping( " /getData")
    public List<People> getProduct(){
        List<People> peopleList = peoplesevice.getPeopleList();
        return peopleList;
    }

JVM设置如下:

-XX:+PrintGCDetails 
-XX:MetaspaceSize=64m
-Xss512K
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap/heapdump3.hprof 
-XX:SurvivorRatio=8
-XX:+PrintGCDateStamps 
-Xms1024M -Xmx1024M
-Xloggc:log/gc-oom3.log


强制Full GC

在这里插入图片描述

查看堆空间占比情况

jmap -heap 12324

在这里插入图片描述

安照官网上面推荐的老年代的内存占比,老年代应该设置为已使用的3到4倍

-XX:+PrintGCDetails 
-XX:MetaspaceSize=64m
-Xss512K
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap/heapdump3.hprof 
-XX:SurvivorRatio=8
-XX:+PrintGCDateStamps 
-Xms80M -Xmx80M
-Xloggc:log/gc-oom3.log



4、性能优化案例3:CPU占用很高排查方案


CPU占用很高问题排查过程:

  1. ps aux | grep java查看到当前java进程使用cpu、内存、磁盘的情况获取使用量异常的进程
  2. top -Hp 进程pid 检查当前使用异常线程的pid
  3. 把线程pid变为16进制如31695 – 》 7bcf 然后得到Ox7bcf
  4. jstack 进程的pid | grep -A20 Ox7bcf 得到相关进程的代码 或者导出日志为文件,根据16进制的线程号在日志文件中搜索(jstack 进程的pid >xx.log)



5、性能优化案例4:G1并发执行的线程数对性能的影响

JVM参数设置

export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC"
export CATALINA_OPTS="$CATALINA_OPTS -Xms30m"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx30m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDetails"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MetaspaceSize=64m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDateStamps"
export CATALINA_OPTS="$CATALINA_OPTS -Xloggc:/opt/tomcat8.5/logs/gc.log"
export CATALINA_OPTS="$CATALINA_OPTS -xx:ConcGCThreads=1"

说明:最后一个参数可以在使用G1GC测试初始并发GCThreads之后再加上。初始化内存和最大内存调整小一些,目的发生 FullGc,关注GC时间,关注点是:GC次数,GC时间,以及Jmeter的平均响应时间

在这里插入图片描述

在这里插入图片描述


JVM参数设置修改之后

export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC"
export CATALINA_OPTS="$CATALINA_OPTS -Xms30m"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx30m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDetails"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MetaspaceSize=64m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDateStamps"
export CATALINA_OPTS="$CATALINA_OPTS -Xloggc:/opt/tomcat8.5/logs/gc.log"
export CATALINA_OPTS="$CATALINA_OPTS -xx:ConcGCThreads=2"

在这里插入图片描述

在这里插入图片描述



6、性能优化案例5:日均百万级订单交易系统如何设置JVM参数

在这里插入图片描述



7、性能优化案例6 网站问题分析

有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器是32位的,1.5G的堆,用户反馈网站比较缓慢。因此公司决定升级,新的服务器为64位,16G的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了!



7.1、为什么原网站慢?

频繁的GC,STw时间比较长,响应时间慢!



7.2、为什么会更卡顿?

内存空间越大,FGC时间更长,延迟时间更长



732、怎么处理

垃圾回收器: parallel Gc ;ParNew + CMS ; G1

配置Gc参数:-XX:MaxGCPauseMillis 、-XX:ConcGCThreads根据log日志、dump文件分析,优化内存空间的比例

jstat jinfo jstack jmap



8、系统内存飙高,如何查找问题?

  1. 一方面:jmap -heap . jstat . …; gc日志情况
  2. 另一方面:dump文件分析



版权声明:本文为prefect_start原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。