Java核心篇-故事里的JVM性能调优

引言

在上篇Java核心篇-GC垃圾收集器中,我们学到了如何选择一个合适垃圾收集器,去满足我们吞吐量或者是用户体验的需求,虽然说大多数情况下,使用垃圾收集器默认的机制一般都能满足我们的需求,但是万一它就不给力了咋办???别着急,JVM还提供了一大批参数帮助我们进行调优(只有你想不到,没有他们给不了的)。下面我将用一个故事为大家展开今天的内容,以下分成3部分:

我遇到了一个问题

我如何去分析问题

我怎么去解决问题

我遇到了一个问题

有一个这样的系统

一个做申请处理的系统,会返回给调用方处理的结果,我们和某呗合作(你们懂的),如果我们没有在规定时间内返回处理结果,他们就不等了,直接找下家(大厂就是牛)

故事是这样的

今天高高兴兴来上班,突然业务给我打了一个电话,他跟我说最近上游系统调用我的系统有很多超时件,只能拒件了,目前已达到了6%,希望我能尽快处理一下。

我的心里咯噔一下,麻烦了,我说最近测试环境的系统启动和处理都越来越慢,但是想着生产环境也没啥问题,也就迟迟没去处理,看样子,该去看看了。

我如何去分析问题

一般系统处理慢的情况下,首要检查数据库的sql,以及第三方接口调用的,但是考虑到我只使用了配置数据库,内部也有缓存机制,最近也没怎么去动,同时也没有调用第三方接口,应该不会是这方面原因。所以我还是决定现场看看原因。

我跑到生产环境去,根据业务提供的收编,找到了一些超时的件,查看了一下代码段的日志时间,并没有发现超时的代码段里有循环递归等执行需要很长时间的代码。

考虑无果之后,于是我拿着这几笔件先回到开发环境,准备复现一下这个问题。

倒霉的是,我并没有发现问题,于是我考虑到是不是请求量大了,导致性能出现问题,于是我是用jmeter(压测神器)对测试环境进行压测,模拟生产环境,尝试去发现问题,当然这个时候我要查看一些性能参数,帮助我定位问题了。

到此,我排除了数据库,调用第三方,复杂逻辑代码的因素,开始准备对性能数据进行分析(进入正题了,兄弟们,打起精神来)。

使用界面化工具分析问题

公司目前没有性能监控工具,于是我选择使用JVM提供的Java Visual VM查看工具,通过在CMD中执行命令启动

查看JVM参数

在左侧选择需要查看的应用,我们能在左侧看到JVM的相应参数,如下图所示:

注:注意如果出现下面框框中的参数,就像我给出的图片一样,你就要关注一下,是不是把内存设置小了,如果设置小了,所以整个系统经常gc,导致程序变慢,我们可以直接通过增大内存空间去增加性能。当然我们还需要通过查看gc等参数才能确定。

查看各项指标数据

我们可以发现,垃圾回收增多,CPU这个时候使用量也在增大,堆内存使用量也在增大,其实我们就有理由怀疑,可能是GC过多,导致的处理请求变慢,不过我们还需要查看一下对象及内存使用情况,确定有没有哪个对象太多

查看对象及内存使用情况

点击抽样器,点击内存,我们能看到整个堆当中我们有哪些类,每个类都有哪些实例,这里我们能分析出哪些实例占用的内存比较多,可以以此分析代码中创建类有些问题,以下图为例:

但是如果你的代码已经很优化了,这些类对象创建也很有必要,我们就要尝试去进行垃圾收集器的调优了。

可能开发环境没有那么好的模拟效果,还是需要连测试环境再继续分析一下。

远程连接工具

这个时候,我们可以使用Java Visual VM左侧边栏中的远程按钮,远程连接测试环境服务器。先关防火墙,然后再输入测试环境开放的端口以及ip,连接服务器,如下图所示:(其实我是想连生产环境看的,但是生产环境不会开放端口和防火墙给我呀)。

最后,懒惰的我还是放弃了这个想法,万一测试环境还是没有效果呢?还是去生产用命令行好好分析一下把。

使用命令分析问题

查看进程

由于JVM大多数命令都是需要用到进程id的,所以我们需要先查看一下我们系统的进程id,通过使用,输出所有java相关进程。如下图所示:

这里我们可以根据类名,大致确认出我们的系统进程是什么

注:代表的是输出应用程序main class的完整package名或者应用程序的jar文件完整路径名

查看JVM参数

通过运行查看jvm的参数,如下图所示:

通过运行查看java系统参数,如下图所示:

查看各项指标数据

通过运行完成类加载统计

通过运行完成垃圾回收统计

通过运行完成堆内存统计

通过运行完成新生代垃圾回收统计

通过运行完成新生代内存统计

通过运行完成老年代垃圾回收统计

通过运行完成老年代内存统计

通过运行完成老年代内存统计

通过运行数据总览

查看对象及内存使用情况

通过运行类及其对象所占内存数据,如下图:

通过运行堆信息,如下图:

可能有人会问,万一遇到OOM(OutOfMemoryError)怎么办呢,那看看下面的把

对内存溢出分析

OOM目前有2种常见的办法,都是使用dump,打印OOM时的堆信息来分析原因的

1.出现 OOME 时生成堆 dump: JVM Heap Dump(堆转储文件)的生成,提前在系统参数种加入两个参数:

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/home/fits/jvmlogs/jvm.jump #错误输出地址

2.发现程序异常前通过执行指令,直接生成当前JVM的dmp文件,6214是指JVM的进程号

jmap -dump:format=b,file=serviceDump.dat6214

由于第一种方式是一种事后方式,需要等待当前JVM出现问题后才能生成dmp文件,实时性不高,第二种方式在执行时,jvm是处在假死状态的,只能在服务瘫痪的时候为了解决问题来使用,否则会造成服务中断。一般情况下,建议第一种。

拿到日志后可以导入Java VisualVM,从菜单栏的文件->装入,主要查看类信息,如下图:

这个时候是可以点击类查看值是什么的,如下:

我怎么去解决问题

通过以上的分析,我们能发现其实是大量的gc造成了我们程序变慢,因此我们需要进行性能调优(在这里,因为我们已经学过了垃圾回收集的选择了,我也就默认我们垃圾收集器是合适的,直接进入参数调优的步骤啦)

调优的目标

首先我们要明确调优的目标,主要有以下两方面:

减少停顿时间:垃圾收集器做垃圾回收中断应用执行的时间。 可以通过参数进行设置,以毫秒为单位,至少大于1

提高吞吐量:垃圾收集的时间和总时间的占比:1/(1+n),吞吐量为1-1/(1+n) 。通过参数进行设置,99的话代表吞吐量为99%

调优步骤的概括

打印GC日志,通过参数

分析日志

修改参数验证结果(什么叫调优,就是调来调去选最优)

注:

-XX:+PrintGCDetails代表打印详细GC日志

-XX:+PrintGCTimeStamps:代表打印GC时间戳(以基准时间的形式)

-XX:+PrintGCDateStamps:代表打印GC时间戳(以日期格式)

-Xloggc代表打印gc日志地址

各垃圾收集器日志分析

Parallel Scavenge收集器日志分析

使用jdk8或者加入参数-XX:+UseParallelGC即可,得到日志文件如下:

图中第一部分是参数,第二部分是年轻代gc的参数,年轻代内存从多少变成多少(年轻代总内存),堆内存从多少变成多少(堆内存总量);第三部分是fullgc,包括年轻代,老年代,和元空间的内存变化

CMS收集器日志分析

加入参数-XX:+UseConcMarkSweepGC,默认年轻代使用ParNew收集器,得到日志文件如下:

图中第一部分是参数,第二部分是年轻代gc的信息,和Parallel Scavenge类似,使用的ParNew收集,第三部分是CMS的fullgc的信息,可以大致看出它的五个步骤,初始标记,并发标记,重新标记,并发清除以及并发重置

G1收集器日志分析

加入参数-XX:+UseG1GC,得到日志文件如下:

图中第一部分是参数,第二部分是年轻代gc的信息,这里面不太能直观看出步骤,主要显示多个线程各个操作的最大最小平均不同的时间

日志分析工具

即使我们能看懂日志,但从这样一个日志中得出结论对于我们新手来说还是不容易的,所以我们要学会使用工具(学会偷懒),目前我知道的有两种比较方便的

gceasy,在线gc分析工具,网址:http://gceasy.io/

gcviewer,离线gc分析工具,在github上下载使用,网址: https://github.com/chewiebug/GCViewer

这里我就讲讲如何使用gceasy如何使用(别问我为什么,我就觉得它好看),如下图:

我们可以直接将生成的log文件选中进行分析,这里选取Parallel Scavenge的日志文件(大多公司都是用默认的JDK8的收集器了)进行查看,如下图:

这里面它是会提供给你一些调优的建议的,一般还是比较准的,初学可以直接参考它的来改试试效果,这里是建议我们加元空间配置(默认–XX:MetaspaceSize值为21MB,垃圾回收后会动态扩展)

这一部分是吞吐量和gc时间,也就是我们的两个指标。除此之外,这个网站还提供了堆内存数据分析,垃圾回收统计等等一系列的数据帮助我们分析。想知道的话,就去看看哈。

垃圾收集器性能调优

在这里,我以Parallel Scavenge为例,向大家举例,在上节中,我们不怎么会调优的话可以先参考网站所给出的建议,增加元空间的配置,重启后的结果如下:

我们能发现,这里没有什么建议了,但其实也算差不多了,这里的吞吐量变小了,但是每次gc的时间变短了,这样相对来说用户体验会好一些,也算是有好的变化。

当然网站里面给的建议也可能达不到我们要的效果,这个时候我们可以选择适当的提高内存值,或者-XX:MaxGCPauseMillis最大毫秒数以及-XX:GCTimeRatio吞吐量的配置,让gc自适应帮我们调整一下,看看有没有效果,如果还是不行的话,那就需要对各个调优指令好好学习一下了,然后不断尝试了

后记

1.我这里讲的调优只是比较简单的调优方式,但是正常情况下,还是够用的,如果我们需要更多更强大的调优的话,建议大家把大多数的调优参数好好学习一下

2.不要以为改了参数就能达到预期的效果,有些时候会有多方面原因,导致达不到参数中的效果,特别是吞吐量,你要求它有99,这个其实很苛刻了,它自适应调节很难达到这样的效果,只能说接近,如果你原有吞吐量是70,你写个80,达到的概率还是很大的。

3.正常情况下,虚拟机默认会帮我们做的很好,就直接够用了,只有我们有性能要求,或者性能有所下降的时候再去考虑优化,这个时候我们进行优化的话,建议是一点点的优化,不要一次调的差距很大,很有可能会有多方面原因的

4.本故事改编自我的故事,如有雷同,说不定我们是同事哦

如果我有写的不对的地方,欢迎大家来指正,你们的评论和点赞是对我们最大的支持,让我们在互联网架构师之路上越走越远,感谢大家的陪伴,谢谢大家啦~

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181102G0PHOP00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券