JVM性能调优
- 性能调优分为架构调优,代码调优,JVM调优,数据库调优,操作系统调优等;
- 性能调优的基本步骤是:
- 明确优化目标 --》发现性能瓶颈 --》性能调优 --》通过监控及数据统计工具获得数据 --》确认是否达到目标。
-
何时进行JVM调优
- 堆内存(老年代)空间持续上涨达到设置的最大内存值;
- Full GC次数频繁;
- GC停顿时间过长(超过1s);
- 应用出现OutOfMemory等内存异常;
- 应用中有使用本地缓存且占用大量内存空间;
- 系统吞吐量与响应性能不高或下降。
-
JVM调优的基本原则
- 在进行GC优化之前,需要确认项目的架构和代码等已经没有优化的空间;大多数导致GC问题的原因是代码层面的问题导致;
- 代码层面,减少创建对象的数量;减少使用全局变量和大对象;
- 分析GC情况优化代码,比优化JVM参数更好;不要为了调优而调优;
- GC优化必须建立在深入理解各种垃圾回收器的基础上。
-
JVM调优的步骤
- 分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;
- 确定JVM调优量化目标;
- 依次调优内存、延迟、吞吐量等指标;
- 不断的分析和调整,直到找到合适的JVM参数配置;
-
JVM判断对象是否已死亡
- 引用计数法
- 给对象中添加一个引用计数器,每当对象被引用,计数器加1;当引用失效,计数器减1;任何时候计数器为0,则对象就不可再被使用。无法解决对象之间相互循环引用问题。
- 可达性分析算法
- 通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
- GC Roots 的对象包括:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈(Native 方法)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 所有被同步锁持有的对象
- 引用计数法
JVM性能调优工具
-
JVM参数
参数名称 | 含义 | 说明 |
-Xms | 堆最小值 | 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制. |
-Xmx | 堆最大值 | 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制 |
-Xmn | 年轻代大小 | 注意:此处的大小是(eden+ 2 survivor space).整个堆大小=年轻代大小 + 老年代大小 + 持久代(永久代)大小.增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 。 |
-XX:NewSize | 设置年轻代最小值 | |
-XX:MaxNewSize | 设置年轻代最大值 | |
-XX:MetaspaceSize | 设置元数据区最小值 | jdk17 -XX:PermSize |
-XX:MaxMetaspaceSize | 设置元数据区最大值 | jdk1.7 -XX:MaxPermSize |
-Xss | 线程栈空间大小 | 如果栈不是很深, 应该是128k够用的,大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。 |
-XX:NewRatio | 堆内存中,年轻代和老年代的比值 | -XX:NewRatio=4,表示年轻代和老年代所占比值为1:4,年轻代占整个堆栈的1/5. |
-XX:SurvivorRatio | Eden区与Survivor区的大小比值 | -XX:Survivor=8,则两个Survivor区与一个Eden区的比值为2:8。一个Survivor区占整个年轻代的1/10. |
-XX:+DisableExplicitGC | 关闭System.gc() | |
-XX:PretenureSizeThreshold | 对象超过多大时,直接在老年代中分配 | |
-XX:MaxTenuringThreshold | 设置分代年龄的阈值 | 经过一次Minor GC的对象仍然存活,则分代年龄+1,超过阈值则进入老年代 |
-XX:ParalleGCThreads | 并行收集器的线程数 | 并行执行GC的线程数;此值最好配置与处理器数目相等 同样适用于CMS |
-XX:MaxGCPauseMills | 每次年轻代垃圾回收的最长时间(最长暂停时间) | 如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值. |
-XX:+UseSerialGC | 串行垃圾收集器 | 单线程,采用复制算法实现,主要用于单CPU环境。简单,效率高; -XX:UseG2GC |
-XX:+UseParNewGC | 并行多线程垃圾收集器 | 采用复制算法实现,主要用于多CPU环境,可充分利用CPU资源,减少回收时间; -XX:ParallelGCThreads 指定线程数 |
cms | CMS垃圾收集器 | |
-XX:UseG1GC | 将整个Java堆划分为多个大小相等的独立区域(Region),可独立的管理整个堆内存,Region之间是基于复制算法实现,整体上是基于标记-整理算法实现,采用Remembered Set记录每个region的引用信息避免全堆扫描 | 特点:
|
-XX:+PrintGCDetails | 打印GC的详细信息 | |
-XX: +PrintCommandLineFlags | 打印当前使用的JVM参数 |
-
JDK 命令行调优工具
说明:(在JDK安装目录的/bin目录下);vmid-应用进程Id
jps | JVM Process Status 查看所有Java进程的启动类、传入参数和Java虚拟机参数等信息 | jps -q 输出进程的本地虚拟机唯一ID jps -l 输出主类的全名 jps -v 输出虚拟机进程启动时JVM参数 jps -m 输出传递给Java进程main()函数的参数 |
jstat | 监视虚拟机各种运行状态信息。可以显示本地或者远程虚拟机进程中的类信息、内存、垃圾收集、JIT编译等运行数据。是运行期间定位虚拟机性能问题的首选工具(无GUI界面工具时) 命令格式: jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]] 比如: jstat -gc -h3 31736 1000 10 表示分析进程 id 为 31736 的 gc 情况,每隔 1000ms 打印一次记录,打印 10 次停止,每 3 行后打印指标头部。 | jstat -class vmid 显示ClassLoader的相关信息 jstat -compiler vmid 显示JIT编译的相关信息 jstat -gc vmid 显示与GC相关的堆信息 jstat -gccapacity vmid 显示各个代的容量及使用情况 jstat -gcnew vmid 显示新生代信息 jstat -gcnewcapacity vmid 显示新生代大小与使用情况 jstat -gcold vmid 显示老年代的行为统计 jstat -gcoldcapacity vmid 显示老年代的大小 jstat -gcpermcapaticy vmid 显示永久代大小(jdk1.8以前) jstat -gcutil vmid 显示垃圾收集信息 |
jinfo | 实时地查看和调整虚拟机各项参数。 jinfo可以在不重启虚拟机情况下,动态的修改JVM参数。 | jinfo vmid 输出当前jvm进程的全部参数和系统属性 jinfo -flag "cmd-name" vmid 输出对应名称的参数的具体值 比如: 输出maxHeapSize大小:jinfo -flag MaxHeapSize vmid 查看是否开启打印GC日志:jinfo -flag PrintGC vmid jinfo -flag [+|-]name vmid 动态修改jvm参数 比如: 开启打印GC日志详情:jinfo -flag +PrintGC vmid |
jmap | JVM Memory Map for Java 生成堆转储快照,获取dump文件; 查询finalizer执行队列、Java堆和永久代详细信息 | |
jhat | 用于分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果 | |
jstack | 生成虚拟机当前时刻的线程快照,即当前虚拟机内每一条线程正在执行的方法堆栈的集合。 | 目的主要是定位线程长时间出现停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等,都是线程长时间停顿的原因。 通过jstack 命令查看各个线程的调用堆栈,就可看到线程状态。 |
1、top命令,然后按shift+p按照CPU排序,找到占用CPU过高的进程的pid 2、top -H -p [进程id] 找到进程中消耗资源最高的线程的id 3、printf "%x\n" [线程id] 将需要的线程ID转换为16进制格式 4、jstack [进程id] |grep -A 10 [线程id的16进制]” |
-
JDK 可视化分析工具
- JConsole Java监视与管理控制台
- 是基于JMX的可视化监视、管理工具。可以方便监视本地及远程服务器的Java进程的内存使用情况。在JDK目录下的/bin目录找到jconsole.exe运行。
连接远程进程,需在远程Java程序启动时加上以下参数:
-Djava.rmi.server.hostname= // 远程服务IP地址
Dcom.sun.management.jmxremote.port=60001 // 监控的端口号
Dcom.sun.management.jmxremote.authenticate=false //关闭认证
Dcom.sun.management.jmxremote.ssl=false
- 查看Java程序概况
- 线程监控
- 内存监控
- Visual VM 多合一故障处理工具
- VisualVM 提供在 Java 虚拟机 (Java Virutal Machine, JVM) 上运行的 Java 应用程序的详细信息。
- 显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。
- 监视应用程序的 CPU、GC、堆、方法区以及线程的信息(jstat、jstack)。
- dump 以及分析堆转储快照(jmap、jhat)。
- 方法级的程序运行性能分析,找到被调用最多、运行时间最长的方法。
- 离线程序快照:收集程序的运行时配置、线程 dump、内存 dump 等信息建立一个快照,可以将快照发送开发者处进行 Bug 反馈。
CPU 占用过高,分析定位到哪一行代码存在bug
总的来说,是利用jstack 命令打印堆栈信息,而jstack 需要线程id,因为第一步是先分析系统中占用cpu过高的程序是哪一个进程;
- 第一步定位进程:找到CPU占用最高的进程id,通过top命令找到cpu 占用最高的进程;
找到进程后,再找进程中的哪个线程占用cpu最高;
top
- 第二步定位是哪个Java 程序:
jps -l | grep 进程Id
- 第三步定位线程的tid:
ps -mp 进程pid -o THREAD,tid,tim
- 第四步根据tid 得到16进制的线程id
printf "%x\n" 上一步得到的线程ti
- 第五步通过jstack 定位到具体的代码
jstack 进程id | grep 16进制的tid -A60
打印指定进程的堆栈信息,然后通过 grep 对应的tid进行过滤(找到cpu占用最高的线程),-A60是打印前面60行堆栈信息
- 第六步分析西昌信息,定位代码
这个时候就会发现报错的信息,就好比在写程序中控制台打印出来的异常一样,然后找到对应的类第几行代码就可以了。
CPU 飙升100% 问题排查
- 问题复现:线上系统突然运行缓慢,CPU 飙升,甚至到100%,以及Full GC 次数过多;
- 问题排查步骤(既然是CPU 飙升,肯定要查一下耗CPU 的线程,然后看GC):
- 执行 jmap -dump:format=b,file=filename 进程Id :导出某进程下内存的heap 输出到文件中。
- 执行 jstat -gcutil 进程Id 统计间隔毫秒 统计次数(缺省则一直统计):查看某进程GC持续变化情况,如果发现返回中FullGC很大且一直增大-》确认Full GC! 也可以使用jmap -heap 进程ID查看一下进程的堆内从是不是要溢出了,特别是老年代内从使用情况一般是达到阈值(具体看垃圾回收器和启动时配置的阈值)就会进程Full GC。
- 执行 jstack 进程Id | grep 线程Id 命令:查找某进程下-》线程ID(jstack堆栈信息中的nid)=0xa的线程状态。定位到具体的代码行;
- 执行 printf "%x\n" 线程Id 命令:后续查看线程堆栈信息展示的都是十六进制,为了找到咱们的线程堆栈信息,咱们需要把线程号转成16进制;
- 执行 top -Hp 进程Id 命令:查看Java 进程下的所有线程占CPU 的情况;
- 执行 top 命令:查看所有进程占系统CPU 的排序;
- 原因分析
- 内存消耗过大,导致Full GC 次数过多;
- 代码中有大量消耗CPU 的操作,导致CPU 过高,系统运行缓慢;比如:某些复杂算法,无线循环递归等;
- 由于锁使用不当,导致死锁;
- 随机出现大量线程访问接口缓慢;代码某个位置有阻塞性的操作,导致该功能调用整体比较耗时,但出现比较随机;平时消耗的CPU不多,占用的内存也不高;