Java生产环境下性能监控与调优

1,371 阅读6分钟

基于JDK命令行工具的监控

JVM参数类型

标志参数

用法: java [-options] class [args...]
           (执行类)
   或  java [-options] -jar jarfile [args...]
           (执行 jar 文件)
其中选项包括:
    -d32	  使用 32 位数据模型 (如果可用)
    -d64	  使用 64 位数据模型 (如果可用)
    -server	  选择 "server" VM
                  默认 VM 是 server,
                  因为您是在服务器类计算机上运行。


    -cp <目录和 zip/jar 文件的类搜索路径>
    -classpath <目录和 zip/jar 文件的类搜索路径>
                  用 : 分隔的目录, JAR 档案
                  和 ZIP 档案列表, 用于搜索类文件。
    -D<名称>=<值>
                  设置系统属性
    -verbose:[class|gc|jni]
                  启用详细输出
    -version      输出产品版本并退出
    -version:<值>
                  警告: 此功能已过时, 将在
                  未来发行版中删除。
                  需要指定的版本才能运行
    -showversion  输出产品版本并继续
    -jre-restrict-search | -no-jre-restrict-search
                  警告: 此功能已过时, 将在
                  未来发行版中删除。
                  在版本搜索中包括/排除用户专用 JRE
    -? -help      输出此帮助消息
    -X            输出非标准选项的帮助
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                  按指定的粒度启用断言
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                  禁用具有指定粒度的断言
    -esa | -enablesystemassertions
                  启用系统断言
    -dsa | -disablesystemassertions
                  禁用系统断言
    -agentlib:<libname>[=<选项>]
                  加载本机代理库 <libname>, 例如 -agentlib:hprof
                  另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
    -agentpath:<pathname>[=<选项>]
                  按完整路径名加载本机代理库
    -javaagent:<jarpath>[=<选项>]
                  加载 Java 编程语言代理, 请参阅 java.lang.instrument
    -splash:<imagepath>
                  使用指定的图像显示启动屏幕

X参数

# 解释执行
-Xint
# 第一次使用就编译成本地代码
-Xcomp
# 混合模式,JVM自己来决定是否编译成本地代码
-Xmixed

XX参数

Boolean类型

格式:-XX:[+-]<name>表示启用或禁用name属性

-XX:+UseConcMarkSweepGC
-XX:+UseG1GC

非Boolean类型

格式:-XX:<name>=<value>表示name属性的值为value

-XX:MaxGCPauseMillis=500
-XX:GCTimeRatio=19
-Xms等价于-XX:InitialHeapSize
-Xmx等价于-XX:MaxHeapSize

查看JVM运行时参数

jinfo

查看、动态修改运行中指定JVM参数的信息

格式:
    jinfo <option> <pid>
       (连接运行时JVM的PID)

where <option> is one of:
    -flag <name>         打印指定JVM参数的值
    -flag [+|-]<name>    启用或禁用JVM参数
    -flag <name>=<value> 设置指定JVM参数的值
# 查看JVM最大堆内存
jinfo -flag MaxHeapSize 8319
-XX:MaxHeapSize=4123000832
# 修改JVM最大堆内存
jinfo -flag MaxHeapSize=4123000000 8319

jstat

查看JVM统计信息

# 查看GC垃圾回收的信息
jstat -gc 9683
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
126464.0 24576.0  0.0   24360.8 1083392.0 924154.1  459264.0   163737.9  126464.0 120015.6 14080.0 12975.1     33    0.593   4      0.943    1.536

jmap+MAT实战内存溢出

OutOfMemeryError

堆内存溢出

非堆内存溢出

内存溢出自动导出

-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=./

导出文件java_pidxxx.hprof
当内存映像文件过大,可能会导不出来,推荐如下jmap导出

Jmap收到导出

jmap -dump:format=b,file=./jmapdump.hprof 1025

MAT分析内存泄漏工具堆内存映像文件进行分析

  • MAT打开文件后选择leck suspects reports。如下图

饼图已经帮我们标出内存泄漏的问题出现在(a) Problem Suspect 1 可以看出OOMController这个实例占用了76.82%的内存

  • 打开Histogram可分析对象实体的创建数量和内存占用

从下图可知,User对象的实例达到14W,可能是此次内存泄漏的关键

  • 打开dominator_tree可分析对象实例上下文的情况

从下图可知,最终结果就是OOMController中的变量List存储14W个User实例且不释放,导致GC overheap limit问题。

jstack分析死循环和死锁造成CPU占用高问题

jstack [pid] > [pid].log
sz [pid].log

通过分析java stack info可以知道问题所在

# 查看高CPU占用的进程PID
top
# 获取PID对应的所有线程信息日志
jstack [pid] > [pid].log
# 查看最高的线程PID
top -p [pid] -H
# pid转换16进制
printf "%x" pid
# 使用16进制的pid在堆栈文件中查找

基于Btrace的监控调试

1.下载

github.com/btraceio/bt…

2.安装配置Mac

vim ~/.bash_profile
# 编辑文本
export BTRACE_HOME=xxx
export PATH=${PATH}:${BTRACE_HOME}/bin
# 配置生效
source ~/.bash_profile
# 是否生效
btrace --version

3.编写btrace监控脚本

import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.*;

@BTrace
public class Btrace {

    @OnMethod(
            clazz = "com.gaofenshuo.controller.common.TokenController",
            method = "login",
            location = @Location(Kind.ENTRY)
    )
    public static void login(@ProbeClassName String className, @ProbeMethodName String methodName, AnyType[] args){
        BTraceUtils.println(className);
        BTraceUtils.println(methodName);
        BTraceUtils.printArray(args);
    }
}

4.启动监控脚本

btrace $pid btrace.java

GC调优实战(吞吐量和响应时间性能调优)

吞吐量越高算法越好,暂停时间越短算法越好

  • 吞吐量与响应时间
    • 吞吐量 = CPU在用户应用程序运行的时间 / (CPU在用户应用程序运行的时间 + CPU垃圾回收的时间)
    • FULL GC,串行垃圾回收会使用应用停顿,响应用户时间长
  • 垃圾回收器算法比较
    • 串行回收算法:会停止当前应用进程,回收垃圾,停顿时间久,吞吐量大,响应时间长
    • 并行回收算法: 是多个线程同时执行串行回收算法(多核),也会使应用停顿,吞吐量大,响应时间长,用户体验差
    • 并发回收算法:应用和垃圾回收多个线程并发执行,吞吐量相对小,响应时间短,用户体验好
    • G1 : 并发 + 并行回收 + 标记管理
垃圾回收器算法 吞吐量 响应时间
串行回收算法 吞吐量大 响应时间长
并行回收算法 吞吐量大 响应时间长
并发回收算法 吞吐量相对小 响应时间短

打印、分析GC日志关键项(吞吐量,最大停顿时间),适当调整JVM参数

初始化启动参数

# 生产环境打开,代码调用System.gc()将失效
-XX:+DisableExplicitGC
# 内存泄漏时打印内存映像
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/gaozaoshun/Desktop/logs/
# 打印GC日志
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-Xloggc:/Users/gaozaoshun/Desktop/logs/gc.log

Parallel GC调优指导原则 (默认GC机制)
1.除非确定,否则不要设置最大堆内存
2.优先设置吞吐量目标
3.如果吞吐量目标达不到,调大最大内存,不能让OS使用Swap,如果仍然达不到,降低目标
4.吞吐量能达到,GC时间太长,设置停顿时间的目标

Parallel GC参数调优

# 调整元空间大小,减少因MetaDataGC引起的GC
-XX:MetaspaceSize
# 设置停顿时间目标
-XX:MaxGCPauseMillis=100
# 设置吞吐量目标
-XX:GCTimeRatio=99
# 修改动态扩容增量(默认20%),YGC频繁时可改大写
-XX:YoungGenerationSizeIncrement=30

G1 GC参数调优

年轻代大小:避免使用-Xmn、-XX:NewRatio等显式设置Young区大小,会覆盖暂停时间目标
暂停时间目标:暂停时间不要太苛刻,其吞吐量目标是90%的应用程序时间和10%的垃圾回收时间,太严苛会直接影响到吞吐量

  • MixedGC参数
-XX:InitiatingHeapOccupancyPercent
-XX:G1MixedGCLiveThresholdPercent
-XX:G1HeapWastePercent
-XX:G1MixedGCCountTarget
-XX:G1OldSetRegionThresholdPercent

G1 GC启动参数

# 开启G1GC
-XX:+UseG1GC
# 调整元空间大小,减少因MetaDataGC引起的GC
-XX:MetaspaceSize
# 设置堆的大小,减少因堆容量不足引起扩容,YGC次数增多
-Xms128M -Xmx128M