缘起

我司项目现在的java项目都是基于springboot开发,通过Jenkins与ansible直接把项目编译的jar在阿里云主机上部署运行,几台云主机的配置大都是通用型2核8G,java项目难免遇到诸如内存溢出这类错误,为了让程序在服务器上有最佳运行效果,需要对每个项目进行jvm调优,JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsole外,还有jps、jstack、jmap、jhat、jstat等小巧的工具,本文希望能起抛砖引玉之用,让大家能开始对JVM性能调优的常用工具有所了解

  1. 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包的参数

  2. 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 stKiB Mem :  1867248 total,    94444 free,  1592828 used,   179976 buff/cacheKiB Swap:  2097148 total,  2064196 free,    32952 used.    73720 avail MemPID 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\n” 22007 得到16进制55f7

  • 这时候用jstack 21996 55f7 来输出堆栈信息如下: 
    “VM Periodic Task Thread” os_prio=0 tid=0x00007ff3980f0800 nid=0x55f7 waiting on condition 
    经过发现这个堆栈信息显示的线程状态是waiting on condition,意思是正在等待网络读写,这可能是一个网络瓶颈征兆。 
    如果这里列出来的是一个java类名,则就可以找到项目里的类,结合堆栈信息,进行性能改进

  1. jmap: 一般结合jhat用来查看堆栈内存使用情况, 
    jmap -heap 21996 显示堆内存如下:

Attaching to process ID 21996, please waitDebugger attached successfully.Server compiler detected.JVM version is 25.141-b16using thread-local object allocation.Mark Sweep Compact GCHeap Configuration:MinHeapFreeRatio         = 40MaxHeapFreeRatio         = 70MaxHeapSize              = 478150656 (456.0MB)NewSize                  = 10485760 (10.0MB)MaxNewSize               = 159383552 (152.0MB)OldSize                  = 20971520 (20.0MB)NewRatio                 = 2SurvivorRatio            = 8MetaspaceSize            = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize         = 17592186044415 MBG1HeapRegionSize         = 0 (0.0MB)

这里我们可以看到我们堆内存各个区的内存大小。注意:这个是jdk1.8的,其中MetaspaceSize代替了PermGen

优化

上面介绍了用jvm的工具查看java进程,并查找相应堆栈信息,根据堆栈信息优化程序,也列出了堆栈内存的大小信息,要避免内存溢出,在程序在承受较大访问量的时候,程序仍然可以健康运行,就要根据自己机器情况,多做几次抗压试验,找出性能最好的参数设置 
首先jdk8 运行时通常包含 计数器,栈,堆,本地方法栈,方法区,堆。每个部分的优化参数名称如下:

Xss:每个线程的栈内存大小Xmx:JAVA HEAP的最大值、默认为物理内存的1/4Xms: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###常规项目优化-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

各位项目还是要根据自己公司服务器配置情况来进行相应优化测试