带你入门jvm性能优化
缘起
我司项目现在的java项目都是基于springboot开发,通过Jenkins与ansible直接把项目编译的jar在阿里云主机上部署运行,几台云主机的配置大都是通用型2核8G,java项目难免遇到诸如内存溢出这类错误,为了让程序在服务器上有最佳运行效果,需要对每个项目进行jvm调优,JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsole外,还有jps、jstack、jmap、jhat、jstat等小巧的工具,本文希望能起抛砖引玉之用,让大家能开始对JVM性能调优的常用工具有所了解
java
-
jps:查看所有java项目进程
一般我都用: jps -m -l PID 这样会列出
21996 /usr/local/platform/patient/bin/patient.jar –spring.profiles.active=test
21996是进程id
/usr/local/platform/patient/bin/patient.jar是详细的进程名称
–spring.profiles.active=test是传入main类或者jar包的参数
-
jstack :查看某个java进程内的堆栈信息,根据堆栈信息,我们可以定位到具体代码,在jvm性能调优中使用的特别多!
我一般这么用: jstack -m -l PID
其中 -m 参数不仅会列出java对战信息,还会列出系统级别native方法的c/c++堆栈信息
-l 会打印出额外的锁信息
栗子:我们找我patient这个java进程最消耗CPU的线程
-
首先用jps获取patient这个java进程的ID
21996 /usr/local/platform/patient/bin/patient.jar –spring.profiles.active=test
-
得到进程ID为21996,第二步找出该进程内最耗费CPU的线程,可以用top -Hp 21996 得到如下:
Threads: 32 total, 0 running, 32 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1867248 total, 94444 free, 1592828 used, 179976 buff/cache
KiB Swap: 2097148 total, 2064196 free, 32952 used. 73720 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
21996 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
21999 root 20 0 2564180 241912 7580 S 0.0 13.0 0:05.11 java
22000 root 20 0 2564180 241912 7580 S 0.0 13.0 0:16.09 java
22001 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22002 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.01 java
22003 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22004 root 20 0 2564180 241912 7580 S 0.0 13.0 0:15.24 java
22005 root 20 0 2564180 241912 7580 S 0.0 13.0 0:06.93 java
22006 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22007 root 20 0 2564180 241912 7580 S 0.0 13.0 5:06.79 java
22081 root 20 0 2564180 241912 7580 S 0.0 13.0 0:01.29 java
22082 root 20 0 2564180 241912 7580 S 0.0 13.0 0:01.67 java
22101 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.43 java
22121 root 20 0 2564180 241912 7580 S 0.0 13.0 0:11.48 java
22123 root 20 0 2564180 241912 7580 S 0.0 13.0 0:01.42 java
22149 root 20 0 2564180 241912 7580 S 0.0 13.0 0:13.73 java
22150 root 20 0 2564180 241912 7580 S 0.0 13.0 0:17.81 java
22232 root 20 0 2564180 241912 7580 S 0.0 13.0 0:08.78 java
22233 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.10 java
22234 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.03 java
22235 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22236 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22237 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22238 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22239 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22240 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22241 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22242 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
22243 root 20 0 2564180 241912 7580 S 0.0 13.0 0:07.24 java
22244 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.02 java
22245 root 20 0 2564180 241912 7580 S 0.0 13.0 0:14.57 java
36964 root 20 0 2564180 241912 7580 S 0.0 13.0 0:00.00 java
其中TIME+这一列是指耗时时间,这一列里面22007 最耗时,我们在命令行里用printf “%x ” 22007 得到16进制55f7
-
这时候用jstack 21996 55f7 来输出堆栈信息如下:
“VM Periodic Task Thread” os_prio=0 tid=0x00007ff3980f0800 nid=0x55f7 waiting on condition
经过发现这个堆栈信息显示的线程状态是waiting on condition,意思是正在等待网络读写,这可能是一个网络瓶颈征兆。
如果这里列出来的是一个java类名,则就可以找到项目里的类,结合堆栈信息,进行性能改进
-
jmap: 一般结合jhat用来查看堆栈内存使用情况,
jmap -heap 21996 显示堆内存如下:
Attaching to process ID 21996, please wait
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b16
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 478150656 (456.0MB)
NewSize = 10485760 (10.0MB)
MaxNewSize = 159383552 (152.0MB)
OldSize = 20971520 (20.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
这里我们可以看到我们堆内存各个区的内存大小。注意:这个是jdk1.8的,其中MetaspaceSize代替了PermGen
java
优化
上面介绍了用jvm的工具查看java进程,并查找相应堆栈信息,根据堆栈信息优化程序,也列出了堆栈内存的大小信息,要避免内存溢出,在程序在承受较大访问量的时候,程序仍然可以健康运行,就要根据自己机器情况,多做几次抗压试验,找出性能最好的参数设置
首先jdk8 运行时通常包含 计数器,栈,堆,本地方法栈,方法区,堆。每个部分的优化参数名称如下:
Xss:每个线程的栈内存大小
Xmx:JAVA HEAP的最大值、默认为物理内存的1/4
Xms:JAVA HEAP的初始值,server端最好Xms与Xmx一样
Xmn:JAVA HEAP young区的大小
XX:PermSize:设定内存的永久保存区域
XX:MaxPermSize:设定最大内存的永久保存区域
我基于jdk1.8 做了一个优化参数,我司单台服务器的配置(2核8G),单台服务器我部署2台服务,配置如下(主要是堆栈)
exec java
-Dfile.encoding=UTF-8
-Xss1024K//栈内存1m
-Xms1024m//堆内存最小1g
-Xmx2048m//堆内存最大2g
-Xmn256m //yong
-XX:MaxMetaspaceSize=512m //方法区512m
java
###常规项目优化
-XX:+DisableExplicitGC //忽略手动调用GC, System.gc()的调用就会变成一个空调用,完全不触发GC
-XX:+UseConcMarkSweepGC //并发标记清除(CMS)收集器
-XX:+CMSParallelRemarkEnabled //降低标记停顿
-XX:LargePageSizeInBytes=128m //内存页的大小
-XX:+UseFastAccessorMethods //原始类型的快速优化
-XX:+UseCMSInitiatingOccupancyOnly //使用手动定义初始化定义开始CMS收集
-XX:CMSInitiatingOccupancyFraction=70 //使用cms作为垃圾回收使用70%后开始CMS收集-Duser.timezone=GMT+8 //避免CentOS坑爹的时区设置
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:InitiatingHeapOccupancyPercent=35
######gc信息打印
-verbose:gc
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime
-Xloggc:${BASE_DIR}/logs/jvm_gc.log
-XX:ErrorFile=${BASE_DIR}/logs/jvm_err.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=${BASE_DIR}/logs/jvm_dump_pid%p.hprof
-jar /usr/local/platform/patient/bin/patient.jar --spring.profiles.active=prod
各位项目还是要根据自己公司服务器配置情况来进行相应优化测试