四.从零开始JVM实战高手-JVM监控及线上问题定位

前言

Java程序员面试JVM几乎比问,对于JVM监控,线上OOM,CPU负载100%等问题也是经常被问到,尽管在企业中不一定轮得到我们去处理线上问题,但是不管是为了面试还是为了应对开发那么对于JVM线上问题处理都是必须要去了解的。

相对而言,解决故障问题也好,处理性能瓶颈也罢,通常思路大致都是相同的,即:分析数据(日志) , 分析排查,问题定位,解决问题 ,如果我们连程序执行的数据或日志都拿不到,那么我们是没办法去定位问题的。

庆幸的是Java提送了JVM监视工具以及相关指令来帮助我们获取JVM相关数据来帮助我们进行问题排查。

工具安装

我们只知道有JVM的存在,但它的运行对于我们来说感觉像是摸不着看不见的,所以我们需要借助工具来监控它的一个实时状态,就像Windows的性能监视器一样,JDK也有自己的可视化工具.Java提供了2个监视工具:

  • D:\opensource\jdk1.8\bin\jconsole.exe

  • D:\opensource\jdk1.8\bin\jvisualvm.exe

jconsole

通过cmd命令行输入 jconsole 弹出如下界面
在这里插入图片描述
选择java进行后进入,进入后可以看到内存的情况,类加载情况,线程情况等等。
在这里插入图片描述

Jvisualvm

我们以运行cmd ,输入jvisualvm,将Java VisualVM启动
在这里插入图片描述
左边本地菜单下是java进程,选择一个进程后右边可以看到堆,类加载情况,现成情况等

jvisualvm安装GC插件

自带的jvisualvm没有监视GC垃圾回收功能,我们需要额外安装插件:

打开工具 -> 插件 -> 选择“可用插件”页 : 我们在这里安装一个Visual GC,方便我们看到内存回收以及各个分代的情况 . 打上勾之后点击安装,就是常规的next以及同意协议等 ,网络不是很稳定,有时候可能需要多尝试几次。可以在设置中修改插件中心地址:

在这里插入图片描述

根据如下步骤修改地址:找到插件中心

http://visualvm.github.io/pluginscenters.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EY48yUBo-1683959030847)(课件图片/wps3517.tmp.jpg)]

找到对应的JDK版本:

http://visualvm.github.io/archive/uc/8u40/updates.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GzJYYDpg-1683959030848)(课件图片/1638788437854.png)]
复制插件地址:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0IxXMl7y-1683959030849)(课件图片/1638788450829.png)]

安装插件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uApLHY5r-1683959030849)(课件图片/1638788458616.png)]

然后再 可用插件中 找到 Visual GC

安装完成后我们将当前监控页关掉,再次打开,就可以看到Profiler后面多了一个Visual GC页。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q7SZdcnx-1683959030850)(课件图片/1638788466690.png)]

在这里我们可以看到JIT活动时间,类加载活动时间,GC活动时间以及各个分代的情况。

需要注意的是,当前课件使用的JDK版本为1.8,仍然自带了VisualVM,从1.9开始的版本是没有自带的,需要额外下载,下载的github地址:

https://visualvm.github.io/download.html

另外,如果开发工具使用的是Intellij IDEA的话,可以下载一个插件,VisualVM Launcher,通过插件启动可以直接到上述页面,不用在左边的条目中寻找自己的项目.

当然也有其他的工具,但这个在可预见的未来都会是主力发展的多合一故障处理工具.所以我们后面将会使用这个工具来分析我们的JVM运行情况,进而优化.而需要优化我们还需要对JVM的组成有进一步的了解

JVM监控命令

在生产环境中,经常会遇到各种各样奇葩的性能问题,我们可以通过Java提供的JVM监控命令来达到监控和查看效果,相关命令如下

名称主要作用
jps查看正在运行的Java进程
jstack打印线程快照
jmap导出堆内存映像文件
jstat查看jvm统计信息
jinfo实时查看和修改jvm配置参数
jhat用于分析heapdump文件
jps 查看进程

jps可以列出正在运行的Java进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及进程id ,通过 jps -help 可以查看参数

选项作用
-q只输出进程id
-m输出传递给主类main函数的参数
-l输出主类全类名,如果进程执行的是Jar包,输出jar包名字
-v程序启动时指定的jvm参数

案例演示:
在这里插入图片描述

jstack:打印线程快照

一般在生产环境中发生了长时间停顿,卡死,死锁,请求时间长等问题就可以通过打印线程快照来分析定位问题. 下面是一段死锁的代码:

public class DeadlockExample {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();
     public static void main(String[] args) {
        Thread t1 = new Thread() {
            public void run() {
                synchronized (lock1) {
                    System.out.println("Thread 1 acquired lock 1");
                    try {
                        Thread.sleep(1000);
                        synchronized (lock2) {
                            System.out.println("Thread 1 acquired lock 2");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
         Thread t2 = new Thread() {
            public void run() {
                synchronized (lock2) {
                    System.out.println("Thread 2 acquired lock 2");
                    try {
                        Thread.sleep(1000);
                        synchronized (lock1) {
                            System.out.println("Thread 2 acquired lock 1");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
         t1.start();
         t2.start();
         try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

然后我们先试用 jps -l 查看进程
在这里插入图片描述
拿到进程id,使用jstack查看每个线程快照 : jstack 27928
在这里插入图片描述
从线程快照中可以看到当前进程中的所有线程。其中包括我们的代码的线程,状态是 locked 锁住状态。同时它提示 found 1 deadlock 发现死锁,并给出了死锁出现的位置。

jmap:导出堆快照

执行jmap -histo pid可以打印出当前堆中所有每个类的实例数量和内存占用,如下,class name是每个类的类名([B是byte类型,[C是char类型,[I是int类型),bytes是这个类的所有示例占用内存大小,instances是这个类的实例数量:
在这里插入图片描述
执行jmap -dump 可以转储堆内存快照到指定文件,比如执行

jmap -dump:format=b,file=/data/jvm/dumpfile_jmap.hprof PID ,可以把当前堆内存的快照转储到dumpfile_jmap.hprof文件中,然后可以对内存快照进行分析。

生产环境我们一般会配置,让虚拟机在OOM异常出现之后自动生成dump文件

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

比如现在有一个死循环的代码然后运行到一定时间会导致内存溢出

public class Main {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        int i = 0;
        while(true){
            arrayList.add(new Main());
            System.out.println(i++);
        }
    }
}

为了效果明显,我们把堆设置小一些,拉后把HeapDumpOnOutOfMemoryError设置上

-Xms2m
-Xmx2m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=d:\

给当前应用设置上以上VM参数,执行代码演示效果如下
在这里插入图片描述
然后我们找到hprof 堆快照文件,可以通过 jvisualvm工具去装载分析
在这里插入图片描述
现在快照文件,文件类型需要选择堆快照
在这里插入图片描述
装载后可以直接看到内存溢出的错误信息
在这里插入图片描述
点击线程名字 main 进去后可以直接定位到内存溢出的位置
在这里插入图片描述

jstat:监视虚拟机信息

jstat -gc pid 500 10 :pid是线程ID,每500毫秒打印一次Java堆状况(各个区的容量、使用容量、gc时间等信息),打印10次
在这里插入图片描述
jstat还可以以其他角度监视各区内存大小、监视类装载信息等,具体可以google jstat的详细用法。下面是结果对照

S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
单位:KB

jinfo : 查看进程参数

jinfo(Configuration Info for Java) 查看虚拟机配置参数信思,也可用于调整虚拟机的配置参数。

在很多情况下,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了 jinfo工具,开发人员可以很方便地找到Java虚拟机参数的当前值。

jinfo不仅可以查看运行时某一个Java虚拟机参数的实际取值, 甚至可以在运行时修改部分参 数,并使之立即生效。 但是,并非所有参数都支持动态修改。参数只有被标记 manageable的flag可以被实时修改。其实,这个修改能力是 极其有限的。
在这里插入图片描述

VM Flags:
Non-default VM flags: -XX:CICompilerCount=12 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=2097152 -XX:MaxHeapSize=209715200 -XX:MaxNewSize=69730304 -XX:MinHeapDeltaBytes=524288 -XX:OldSize=524288 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line: -Xms2m -Xmx200m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\ -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54817:D:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin -Dfile.encoding=UTF-8

通过 jinfo -flags pid: 查看曾经赋过值的参数值

在这里插入图片描述
jinfo不仅可以查看运行时某一个Java虚拟机参数的实际取值, 甚至可以在运行时修改部分参 数,并使之立即生效。 但是,并非所有参数都支持动态修改。参数只有被标记 manageable的flag可以被实时修改。可以通过使用java -XX:+PrintFlagsInitial | grep manageable命令来查看manageable
在这里插入图片描述
修改格式如下:

  • 对于布尔类型修改 : jinfo -flag ±参数 pid
  • 对于非布尔类型: jinfo -flag 参数名=参数值 pid

比如修改打印GC日志演示如下 : jinfo -flag +PrintGCDetails PID

//查看进程
C:\Users\Administrator>jps -l
20924 org.example.Main
...

//查看是否设置PrintGCDetails参数配置
C:\Users\Administrator>jinfo -flag PrintGCDetails 20924

//增加jvm参数:打印GC详情
C:\Users\Administrator>jinfo -flag +PrintGCDetails 20924

//查看是否设置PrintGCDetails参数配置
C:\Users\Administrator>jinfo -flag PrintGCDetails 20924
-XX:+PrintGCDetails
...

解决线上CPU100%问题

一般 CPU 100%,基本都是代码死循环造成的。排查的核心思路是 找到对应服务器,定位是哪个进程的哪个线程中的哪些代码引发的问题,可以简单介绍当时的异常代码示例。

第一步找到最耗CPU的进程 : 使用top -c 查看进程,然后输入大 P,按照 CPU 的使用率进行排序。
在这里插入图片描述
第二步找到进程中最耗CPU的线程 :找到CPU最高的进程,找到进程ID (PID) , 通过命令 top -Hp PID 找到这个进程对应的线程,然后输入大 P ,按照 CPU 的使用率进行排序。
在这里插入图片描述
拿到排第一的PID就是最耗时的线程ID,然后使用 printf “%x\n” PID 把PID由十进制转换为十六进制(之所以要转化为16进制,是因为堆栈里,线程id是用16进制表示的。)

[root@VM-4-2-centos ~]# printf "%x\n" 13759
35bf

接着,我们需要使用 jstack 打印进程的堆栈信息,再通过 grep 查看对应线程相关的东西。jstack 进程ID | grep “线程ID” -C5 --color

jstack 30979  | grep "35bf" -C5 --color

这个时候就可以打印出代码,然后从打印的线程快照中匹配nid,就可以定位到哪个线程耗时了,同时可以快速定位到代码,可以看到是哪个类中的哪个方法导致此次 CPU 100% 的原因了。
在这里插入图片描述

远程监控

如果你不是很熟悉命令,使用命令监控JVM是一件痛苦的事情,JVisualvm提供了jmx远程功能。默认是通过localhost的ip地址提供RMI服务,它需要我们在远程配置JVM参数来开启远程连接

-Xms256m 
-Xmx512m 
-Xss256m 
-XX:PermSize=512m 
-XX:MaxPermSize=1024m 

-Dcom.sun.management.jmxremote 
-Djava.rmi.server.hostname=服务器IP 
#远程服务的端口:
-Dcom.sun.management.jmxremote.port=9015 
#客户端 rmi通信端口
-Dcom.sun.management.jmxremote.rmi.port=9015 
#关闭ssl功能
-Dcom.sun.management.jmxremote.ssl=false 
-Dcom.sun.management.jmxremote.authenticate=false 

然后我们在本地Jvisualvm添加远程主机
在这里插入图片描述
然后添加jmx远程连接
在这里插入图片描述
制定远程连接参数,取消ssl连接ss
文章结束,如果对你有帮助请给个好评,你的鼓励是我最大的动力

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨家巨子@俏如来

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值