Jvm性能调优

目录

1.概述

2.虚拟机参数调优

3.常用性能监控工具

4.线上jvm故障处理方法

5.总结

一.概述
一般的java开发工程师来说,开发不需要做任何jvm设置,环境变量配置好,其他的什么都不用管,配置开发环境、框架,直接进行开发,开发测试完成,自动化打包,运维发布到线上直接了事,有问题了,核心工程师去解决。通常不会出什么问题,因为很多业务都是运行了很长时间了,在流量没有明显的提升时,基本上来说不用优化,开发业务功能就行了。这就导致很多朋友用的最多的功能就是ctrl+c,ctrl+v,业务代码很溜,但不知道具体是为什么,其实我们写代码是为业务服务,当然业务代码要写好,满足需求,但是一定要保证线上不出问题,一旦出问题就JJ了,慌得一批。

知其然还要知其所以然,在了解jvm的基本概念之后,还要会调优,那么调优我们能够做些什么呢,jvm本身我们改变不了什么,那个难度非常大,不是我们干的,我们能够做的就是根据业务的具体情况,配置最优的参数,线上如果出现的问题,我们知道该怎么去解决。

特别是线上爆出的一些错误,比如OutOfMemoryError,StackOverFlowError等,重启可以临时的解决问题,但是不能根本上解决问题,所以我们要从根本上解决问题,从而保证以后不再出现重复的问题,除了配置合适的参数以外,我们还要知道一些工具来排查问题。今天我们就来聊聊这方面的问题。

二、虚拟机配置参数调优
需要配置哪些参数了,参数有什么用处,什么时候配置,配置什么合适?带着这些疑问,在此一一道来

解释两个符号+表示启用,-表示禁用。

1.设置堆内存
-Xmx 最大堆内存

-Xms 最小堆内存

设置堆内存的大小,实践中这两个值设置成一样,到底设置成多大了,大了浪费内存了,服务器需要成本的,小了肯定不行,我的建议,根据我们预估的qps,在测试环境进行压测,比压测峰值大1/3就可以了,以对应业务增长的变化,当然监控要做好,如果检测到内存吃紧,随时准备增加这个值。

-Xmn设置新生代的大小,设置这个值了,还有两个参数表示相等,-XX:NewSize 表示新生代初始内存的大小,应该小于-Xms的值,-XX:MaxNewSize 表示新生代可被分配的内存的最大上限,当然这个值应该小于-Xmx的值;

-XX:SurvivorRatio 设置eden:from:to的比例 默认是8 实际默认并不一定,用parallel scanvenge 回收器设置为 UseAdaptiveSizePolicy模式会自动调整

-XX:NewRatio 新生代与老年代的比率 默认是2,老年代是新生代的2倍

如果没有特殊的情况,采用默认值就可以了,如果可以预估我们创建的存活时间,就需要调整了,比如我们使用了内存缓存,spring初始化了非常多的对象,很多的静态变量,那么老年代就要设置大一些。如果实例只在线程的可见范围,那么可以把新生代设置大一些。具体根据业务情况以及定期的采样观察得到最佳值。

2.设置非堆空间的大小
非堆空间也是持久代,方法区,jdk8以后的元数据区。

-XX:PermSize 初始永久代的大小

-XX:MaxPermSize 最大永久代的大小 注意永久代就是通常指的方法区,保存类信息,常量表等,在jdk8中已经废弃了永久代,而是用元数据去代取,默认的非常大可以用:

持久代的空间与我们的类的大小有很大的关系,类加载后放着持久代,如果不是频繁使用动态字节码技术、发射、OSGI等技术,持久代的大小基本上可以预估的,测试环境是多少,上线后基本没有差别。

-XX:MaxMetaspaceSize配置最大元数据区的大小

3.直接内存的配置
-XX:MaxDirectMemorySize 最大直接内存,默认是-Xmx,内容溢出仍然会引起系统的OOM

4.栈内存的配置
-Xss 栈的大小,表示每个线程可用的栈空间的大小,默认是1M,如果深度知道程序中执行的深度比较大,这个值需要设置大一些,通常在递归中才会深度不断加深,其他很少深度很大。

5. 打印信息
-XX:+PrintGC 打印GC信息

-XX:+PrintGCDetails 打印GC详情

-XX:+PrintHeapAtGC 全面打印堆的信息

-XX:+PrintGCTimeStamps 打印GC时额外输出时间

-XX:+PrintGCApplicationConcurrentTime 打印应用程序执行的时间

-XX:+PrintGCApplicationStopedTime打印应用程序由于GC停顿的时间

-XX:+PrintReferenceGC 跟踪引用的信息

-Xloggc:/opt/log/gc.log 输出文件的路径

-verbose:class 跟踪类加载与卸载的信息

-XX:+TraceClassLoading 跟踪类加载信息

-XX:+TraceClassUnloading 跟踪类卸载信息

-XX:+PrintVMOptions 打印虚拟机的信息

-XX:+PrintCommandLineFlags 打印参数

这些打印信息知道有这么回事,玩玩的时候可以用用,亦或者出现诡异的问题,没有办法了,试探性的用用,看能不能找到一些灵感。

-XX:+HeapDumpOnOutOfMemoryError dump OOM的整个堆信息,通过

-XX:HeapDumpPath 指定OOM堆信息的打印路径

最后两个参数在出现过OOM的应用jvm中配置上,将OOM信息自动导出,方便排查

6.启用线程栈内分配内存,减少垃圾回收
-XX:+UseTLAB 启用本地线程分配

-XX:TLABSize 设置本地线程分配的空间大小

-XX:+PrintTLAB 打印分配

-XX:+DoEscapeAnalysis启动逃逸分析,未发生逃逸的对象在栈上分配

在某些方法中,实例的生命周期在方法周期内,不会返回,不改表全局变量,这部分实例分配在栈中可以提升效率。

7.垃圾回收器的选择
1)串行收集器
-XX:+UseSerialGC 利用串行GC 对应Serial 与serial old

串行收集器简单有效,对短停顿没有太大要求的可以使用,比如client模式下,并发量低的管理系统也可以,在互联网服务端可定新生代收集器不能用串行收集器。

2)大吞吐量收集器搭配
-XX:UseParallelGC Parallel Scavenge 关注吞吐量,可以指定停顿时间,可以自动调整

-XX:MaxGCPauseMillis 最大停顿时间

-XX:GCTimeRatio 花费的收集时间占比, 一个0-100的数字,默认99, 1/(1+n)

-XX:+UseAdaptiveSizePolicy 自动调整策略,新生代,eden/survivor的比例自动调整

老年代搭配使用:-XX:+UseParallelOldGC 老年代并行收集器

3)CMS收集器
CMS是一个真正并行收集器,搭配使用ParNewGC

-XX:UseConcMarkSweepGC

-XX:CMSInitiatingOccupancyFraction 回收阈值 默认68 如果增长较慢可以设置大一点,增长快设置小一些

-XX:CMSFullGCsBeforeCompaction 在多少次回收之后进行一次整理

-XX:+UserCMSCompactAtFullCollection cms回收完成后进行一次内存整理

-XX:+CMSClassUnloadingEnabled

-XX:ConcGCThreads 并发线程数

-XX:ParallelCMSTHreads cms的收集的线程

4)G1收集器
G1收集器是新生代老年代的收集器,是最近垃圾收集器的最新成果,在管理大内存有很大的优势,何为大内存了,个人觉得jvm的内存至少也在4G以上的内存。

-XX:+UseG1GC 开启G1收集器

总结起来选择的问题:客端户模式用serial收集器,小内存低并发的选择-XX:UseParallelGC

大内存用G1

8.jvm运行的模式
-client 客户端模式运行

-server 服务端模式运行 , 服务端运行比客户端运行要慢,需要收集更多的系统性能信息,对复杂算法的程序进行优化, 系统进入稳定期以后,server模式比客户端模式要快很多

三、常用性能监控工具
在线上出现故障或对jvm进行优化的过程中,需要哪个抓手来解决问题,我们有哪些性能工具可以使用了?有些命令有截图,有些没有,但是尽量自己多动手,多练习,看多了,做多了,自然就会了。但是在此不会详细说每个参数,直说怎么用这些工具去做优化,每个工具的参数非常的多,还需要各自实践,不知道的用-help。

1.jps
java进程查看工具,查看当前指定的机器有哪些java进程在运行,我们直接运行jps命令:

49f213343a54a75b707cb9f2ba8963c1.png
可以看到,本机运行了一个java进行,进行号是2117,运行的主类是Bootstrap,这是一个Tomcat进程。这个工具我们通常只是检测进程是否启动起来了,通常要看哪个应用的进行,会用到:ps -aux|grep application_name 

因为我的机器上跑的Tomcat容器,感觉起来这个工具比较鸡肋,但是如果是直接java应用,那么这个命令就比较管用。

应用场景:判断java应用进程是否成功启动,服务器上只有单个java应用比较有效,多应用需要二次定位,没有必要用这个工具。

2.jstat
查看java进程运行时的状态信息,命令:

jstat - [-t] [-h] [ []]

应用场景:查看某个进程id虚拟的运行状态,根据不同的option参数,获取不同的参数,主要有获取当前虚拟机的内存容量情况以及使用情况,具体有:新生代的总容量、eden区总容量以及使用了多少、from/to survivor的总容量以及使用情况、老年代的容量以及使用情况、元数据区的总容量以及使用情况、GC采样收集的时间等

**查看内存状态的利器**

Jstat的参数非常多,结果代表的意义也非常多,如果查看容量情况:

d99258179ef09dd645749e91ea9e07fe.png
每一个参数都有这样的,头上的简写真坑,需要什么就查什么,不必要每个都记住,要记住还是要花点心思 

3.jinfo
查看应用程序运行的扩展参数,例如在Tomcat容器中catalina.sh中的catalina_opts

b07b548d4e6150412d0426f882356230.png
在ide开发过程中配置的虚拟机启动参数:

8621196df964c3877ff6afe1125dbcad.png 
运行help查看帮助:

a818a1e1421be9b2a7ca7ae60ca25b79.png
Option的重要参数flags是最长用的,代表所有的vm参数设置的值,来走一个:

jinfo -flags 2117

3b98d9f8c257f951d0ed753b75dfd01c.png
这可以看到当然虚拟机初始配置的所有参数内容,包括一些默认的参数,这个很重要,我们要优化线上的参数,得知道当前配置的哪些参数,我经常用的这个命令,很有用。可以看到我们配置的堆大小,使用何种垃圾回收器。 

4.jmap
jmap是一个堆内存查看工具,查看堆的使用情况,堆实例统计信息,dump一个堆文件,查看classLoader信息。

通常会用这个工具查看堆内存使用情况,在这一块上,比jstat要直观


关于堆的容量以及使用情况直观的可以看出来,非常方便,经常用到。可以记住这个命令:jmap -heap

还有一个可以快速的查看堆内实例数量统计的命令,在查看哪些对象占用了内存的时候,很有用:jmap -histo ,直接运行这个命令由于对象比较多,看不全,我们需要导出一个文件,然后查看文件:jmap -histo >/opt/logs/histo.log,在打开这个文件,截取我导出的前几行看看

a30b19d207f28dc8b183ca9bd182f90a.png
截取前18行,可以看到哪些类的数量、占用的空间,在这个地方占用最高前三的是[B,[C,[I,这是什么鬼??这是编译后java文件的对byte数组,char数组,int数组的表示,[代表一维数组,[[代表二维数组…看到这些是不是还是搞不清,到底哪儿使用这么高是吧,那就得考虑应用程序中哪儿会用到这些类型,如果用到连接池、rpc、netty等网络传输,可能会用的byte[],char[]数组很可能在由于String引起的,因为String是由char数组实现的,这样逐步的分析到底哪些对象占用了高内存。

 

Jmap的最重要的一个功能,也就是大家常说的dump,将堆全面使用情况的以文件的形式输出。jmap -dump:format=b,file=/opt/logs/dump.hprof 2117, 导出2117进程的内存快照,这个文件可能很大,我这儿导出有1.3G,所以线上的话,需要dump文件查看工具,比如:自带工具jhat。Jhat /opt/logs/dump.hprof 就可以访问:http://服务器ip:7000

Jmap是我们线上解决OOM的重要的工具,非常重要。

5.jstack
线程堆栈信息查看,jstack -l ,所有线程的状态,

566af1387b8998864f120d7169b346f0.png
截图为证,其实很多的线程都是在等待状态,在处理线程死锁的问题时比较有用,我们查看正在运行的哪些线程,线程的状态。

 

Jdk中还有一些工具,就不一一介绍,平时用的很少,知道也没啥用,了解了解也可以,比如jcmd,一个大而全的命令行,基本干所有的事,查看内存,堆,dump什么都能,但是得记住很多命令啊,不好办,不喜欢。

如果方面远程连接服务器,那么有可视化的工具是一个不错的选择,jConsole,Visual VM等,JConsole自带的,功能一般,Visual VM,更加全面一些。

6. JProfiler
可视化工具上我推荐使用JProfiler,功能相当的强悍,我用的最顺手的一个可视化工具,在服务器配置linux版本的jprofiler.sh,然后在引用启动参数中增加-agentpath:/opt/jprofiler11/bin/linux-x64/libjprofilerti.so=port=8849,nowait,然后就在windows中可以根据ip:端口连接,截图界面看看,是不是比其他的界面分析工具优秀。

b512b26ac5579d458373feb09ab678f5.png
所有的远程连接都与网络有很大的关系,如果查看dump信息,类信息等,这些信息需要从远程传送回来,可能很慢,我推荐有条件的话,在线上打建一个windows环境做测试,调试,非常有帮助。

 

四、线上jvm故障处理思路
首先说明:在此不是解决上线bug或者容器崩溃的错误,而是解决OutOfMemoryError与StackOverflowError,出现了这个错误,虚拟机不能用,但是要里面恢复,针对这样的问题,我们来聊聊一些处理的办法,结合我们前面学习的虚拟机的配置与工具的使用,进行实战整理。

1. 线上出现访问很慢、超时、或者有的直接报错运行时的错误,我们看到错误信息,根据状态去定位。检查是否是网络问题 (cdn,dns,nginx),运维应该快速的排查。

2. 检查jvm时候正常工作,检查日志是否有遭难性的错误,如果jvm进程正常,应用中出现了灾难性的,那就应用程序的性能问题或者bug,赶紧加班去吧。

3. 前面这个过程很快要完成,估计在问题出现的30’就的搞定,搞不定估计老老大要出面了,如果问题确定是开发的问题,日志给开发,重启应用服务,开发解决问题,发布bug版本。

4. 如果出现的是OutOfMemoryError,StackOverflowError,应用中没有明显的提示

保存现场:

Jstat -gc -pid > /opt/logs/stat.log

Jstack -pid > /opt/logs/stack.log

Jmap -heap -pid > /opt/logs/heap.log

Jmap -dump:format=b,file=/opt/logs/dump.log -pid

修改启动配置,如果是OOM,增大堆内存,元数据空间,栈空间,配置上jprofiler参数,打印-XX:+HeapDumpOnOutOfMemoryError 自动dump出现错误,并写好日志。

5. 根据日志信息排查问题

根据stat与heap区情况,可以大概的定位到是哪个区域发生了,通常老说是老年代,为什么,因为新生代在空间不足的情况下会触发GC,如果一直不足,一直GC,那么分配的对象很快就进入老年代,而又得不到释放。那么我们就需要检查哪些对象比较大。根据jstack信息,找到运行的线程以及可能报错的线程位置,Dump文件查看哪些对象较多较大

Jprofiler持续跟踪重启后的内存使用,对象分配,线程运行情况,基本上来说可以定位到错误的地方。

这样如果还找不到问题,那可能太隐蔽了,需要把相关的人召集起来,针对问题,集中解决,知道问题最终解决。

6. 解决问题后重新调整虚拟机的参数

五、结束语
优化主要涉及到的都提到了,但是还有很多细节的方法没有深入,因为每个点都有很多深入的地方,这只是一个引导性介绍,可以作为解决问题的参考,可能在一些场景下解决问题,上线问题多种多样,只有不断积累才能以不变应万变,大家觉得不令人满意的地方可以评论去讨论,有帮助请收藏,想进一步了解细节问题,留言哦~

相关资源:imgJVM调优实战.pdf_jvm新生代和老年代的比例-cocos2D代码类资源...

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐