JVM Monitor

摘要: jvm monitor

JVM 监控

基于 jstack 监控定位

查看CPU负载

  • 系统负载/ CPU负载 - 是Linux系统中CPU过度使用率或利用率不足的度量; CPU正在执行或处于等待状态的进程数。

  • 负载平均值 - 是在给定的1,5和15分钟时间段内计算的平均系统负载。

  • top 命令 (定位到我们cpu高的进程)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ top
    top - 16:40:59 up 388 days, 1:04, 1 user, load average: 0.00, 0.01, 0.05
    Tasks: 75 total, 1 running, 72 sleeping, 0 stopped, 2 zombie
    %Cpu(s): 0.7 us, 0.3 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
    KiB Mem : 1883492 total, 247312 free, 473204 used, 1162976 buff/cache
    KiB Swap: 0 total, 0 free, 0 used. 1188144 avail Mem

    $ uptime
    16:42:36 up 388 days, 1:06, 1 user, load average: 0.00, 0.01, 0.05

    其中 load average 代表的是cpu的平均负载,三个数字分别代表1分钟、5分钟、15分钟内cpu的平均负载。

    负荷的大小跟cpu个数以及当前负荷有关系,例如1h 处理器,负载为5 则大概表面有1成的在running 4成的在等待,也就意味着此时可能服务器已经无法处理新的请求了,系统也就凉咯

    查看cpu个数

    1
    $ cat /proc/cpuinfo | grep "cpu cores"

定位具体线程

  • jstack 命令

    例如我们由top定位到了某个异常的进程,拿到了pid,为9048

    接下来我们使用 jstack 命令导出 9048 进程中线程栈的信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    $ jstack 9048 > 9048.txt
    $ top -p 9048 -H # 拿到所有线程的cpu信息,定位具体线程pid
    9243 root 20 0 2498028 59096 7008 S 91.0 3.1 0:00.00 java
    9244 root 20 0 2498028 59096 7008 S 99.0 3.1 0:00.32 java
    # 定位到线程pid为 9243 9244的cpu占用高
    $ printf "%x" 9243 # 转为16进制--> 2353
    $ printf "%x" 9244 # 转为16进制--> 2354
    # 在9048.txt 查找nid为 0x2353 和 0x2354的线程栈信息,最后发现是nio的WindowsSelectorImpl导致的
    $ cat 9048.txt

    "http-nio-8499-ClientPoller-0" #27 daemon prio=5 os_prio=0 tid=0x4e69c800 nid=0x2353 runnable [0x5108f000]
    java.lang.Thread.State: RUNNABLE
    at sun.nio.ch.WindowsSelectorImpl.resetWakeupSocket0(Native Method)
    at sun.nio.ch.WindowsSelectorImpl.resetWakeupSocket(Unknown Source)
    - locked <0x257756d8> (a java.lang.Object)
    at sun.nio.ch.WindowsSelectorImpl.doSelect(Unknown Source)
    at sun.nio.ch.SelectorImpl.lockAndDoSelect(Unknown Source)
    - locked <0x257756b8> (a sun.nio.ch.Util$2)
    - locked <0x257756a8> (a java.util.Collections$UnmodifiableSet)
    - locked <0x2575d518> (a sun.nio.ch.WindowsSelectorImpl)
    at sun.nio.ch.SelectorImpl.select(Unknown Source)
    at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:744)
    at java.lang.Thread.run(Unknown Source)

    Locked ownable synchronizers:
    - None

基于 JvisualVM 的可视化监控

本地监控

打开 JDK 安装目录 bin 文件夹下的 jvisualvm.exe ,在左侧的本地下可以看到正常运行的java 应用,除了正常的类似命令行界面化的操作外,我们可以安装别的好用的插件

  1. 工具–插件–设置–编辑 将URL修改为 https://visualvm.github.io/pluginscenters.html 中对应的JDK版本下的地址,如JDK 8 Update 131 - 192

    https://visualvm.github.io/uc/8u131/updates.xml.gz

  2. 切换TAB至可用插件,在其列表中选中 Visual GC 插件安装后重启jvisualvm.exe 即可看到多出的 Visual GC Tab

远程监控

监控远程Tomcat

  • 修改 Catalina.sh 文件

    1
    JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=xx -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.net.preferlPv4Stack=true -Djava.rmi.server.hostname=xx.xx.xx.xx"

监控远程普通java进程

  • 添加启动参数

    1
    nohup java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=xx -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.net.preferlPv4Stack=true -Djava.rmi.server.hostname=xx.xx.xx.xx -jar xx.jar &

JvisualVM 参考链接

基于Btrace的监控调试

Btrace可用动态的向目标应用程序的字节码注入追踪代码

下载安装

  • 下载安装包

    https://github.com/btraceio/btrace 下载realse版本我这里是 v1.3.11.3

  • 解压并配置环境变量

    新增环境变量 BTRACE_HOME ,Path 中追加 %BTRACE_HOME%\bin

运行方式

  • JVisualVM 中添加Btrace插件,添加 classpath
  • 使用命令行 btrace <pid> <trace_script>

使用方法

Demo

这里举在JVisualVM 中使用的例子,安装完Btrace插件后,对其中的 java 应用列表右键打开Trace application,例如对某个应用的某个查询方法的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;

@BTrace
public class PrintArgDemo {

@OnMethod(
clazz="com.itliusir.web.UserController",
method="query",
location=@Location(Kind.ENTRY)
)
public static void queryTest(@ProbeClassName String className, @ProbeMethodName String methodName, AnyType[] args) {
BTraceUtils.printArray(args);
BTraceUtils.println(className + " " + methodName);
BTraceUtils.println();
}
}

勾上 Output Class-Path (Class-Path 可以用来增加第三方jar包),点击 Start 即可等待执行到 query 方法后的打印输出

Doc

  • 普通方法 @OnMethod(clazz=””,method=””)
  • 构造方法 @OnMethod(clazz=””,method=”“)
  • 重载方法通过参数区分
  • Kind 拦截时机
    • Kind.ENTRY 入口,默认值
    • Kind.RETURN 返回
    • Kind.THROW 异常
    • Kind.Line 行
  • this: 形参+@Self 可以拦截this对象
  • 复杂的参数可以通过反射获取
  • printXxx()

注意事项

  • 默认只能本地运行
  • 生产环境下也可以使用,但是被修改的字节码不会被还原