前言

1、如何分析jvm cpu占用率过高的方法。
2、如何排查线上jvm内存泄漏问题。
3、如何定位jvm 线程死锁问题定位。
4、如何解决jvm 频繁full gc 问题等。
这些问题会通过这篇文章一步一步的去解决它。


一、前置准备

1、先准备一个web 服务: eureka-server.jar 并启动

java -jar eureka-server.jar

下载链接: https://pan.baidu.com/s/1Y5DG2H_GGGXlSyDUVFut1A 提取码: lsuw

二、jvm 常用命令

1.jps

查看刚启动erureka服务进程id为:4123

[root@centos-linux ~]# jps
4280 Jps
4123 jar

2.jmap

2.1 此命令可以用来查看内存信息,实例个数以及占用内存大小,将其写入obj_info.txt 文件中:
[root@centos-linux opt]# jmap -histo 4123 > obj_info.txt

在这里插入图片描述

  • num:序号
  • instances:实例数量
  • bytes:占用空间大小
  • class name:类名称,[C is a char[],[S is a short[],[I is a int[],[B is a byte[],[[I is a int[][]
2.2 查看堆的信息。
[root@centos-linux opt]# jmap -heap 4123

Attaching to process ID 4123, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13

using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 260046848 (248.0MB)
   NewSize                  = 5570560 (5.3125MB)
   MaxNewSize               = 86638592 (82.625MB)
   OldSize                  = 11206656 (10.6875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 20643840 (19.6875MB)
   used     = 4067112 (3.8787002563476562MB)
   free     = 16576728 (15.808799743652344MB)
   19.701334635416668% used
Eden Space:
   capacity = 18415616 (17.5625MB)
   used     = 3581160 (3.4152603149414062MB)
   free     = 14834456 (14.147239685058594MB)
   19.446322077958186% used
From Space:
   capacity = 2228224 (2.125MB)
   used     = 485952 (0.46343994140625MB)
   free     = 1742272 (1.66156005859375MB)
   21.80893841911765% used
To Space:
   capacity = 2228224 (2.125MB)
   used     = 0 (0.0MB)
   free     = 2228224 (2.125MB)
   0.0% used
tenured generation:
   capacity = 45654016 (43.5390625MB)
   used     = 36456800 (34.767913818359375MB)
   free     = 9197216 (8.771148681640625MB)
   79.85453021263234% used

23817 interned Strings occupying 3034368 bytes.
2.3 堆内存dump(重点,常用来分析OOM问题)。
[root@centos-linux opt]# jmap -dump:format=b,file=eureka 4123
[root@centos-linux opt]# jmap -dump:format=b,file=eureka.hprof 4123
Dumping heap to /opt/eureka.hprof ...
Heap dump file created

线上配置一般会设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)

1. -XX:+HeapDumpOnOutOfMemoryError
2. -XX:HeapDumpPath=./ (路径)

好下面我们模拟一下内存溢出的一个场景来分析一下,实例代码如下。

public class OOMTest {

    public static List<Object> list = new ArrayList<>();

    // JVM设置    
    // -Xms5M -Xmx5M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./jvm.dump
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        int i = 0;
        int j = 0;
        while (true) {
        //往list 中不停放入student 对象直到OOM
            list.add(new Student(i++, UUID.randomUUID().toString()));
            new Student(j--, UUID.randomUUID().toString());
        }
    }
}

在这里插入图片描述

将jvm.dump文件,导入jvisualvm命令工具来分析(jdk bin 目录下有该工具,执行运行即可)

在这里插入图片描述

可以明显看出堆栈中有大量的student 对象,有小伙伴会有疑问,那排第一的char[]、String 是什么呢?

在这里插入图片描述

3、Jstack

3.1、该命令能得到运行java程序的java stack和native stack的信息。可以轻松得知当前线程的运行情况。下面我们用一个示例来学习一下
public class LockTest {

    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            
            synchronized (lock1) {
                try {
                    System.out.println("thread1 begin");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                }
                //已获取到lock1 尝试获取lock2
                synchronized (lock2) {
                    System.out.println("thread1 end");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lock2) {
                try {
                    System.out.println("thread2 begin");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                }
                //已获取到lock2 尝试获取lock1
                synchronized (lock1) {
                    System.out.println("thread2 end");
                }
            }
        }).start();

        System.out.println("main thread end");
    }
}
相互争夺资源导致死锁。
##用jstack加进程id查找死锁
jstack 64992

在这里插入图片描述

当然我们也可以用JVisualVM 工具来分析。
在这里插入图片描述

3.2 jstack命令也常用来找出占用cpu最高的线程堆栈信息
/**
 * 运行此代码,cpu会飙高
 */
public class CPUTest {
    
    public int calculate() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }

    public static void main(String[] args) {
        CPUTest cpuTest = new CPUTest();
        while (true){
            cpuTest.calculate();
        }
    }
}
1、执行后代码后,top 查看当前cpu 使用状况,cpu 占用在98%左右
使用命令top -p <pid> ,显示你的java进程的内存情况,pid是你的java进程号,比如72414
top -p 72414

在这里插入图片描述

2,按H(大写),获取每个线程的内存情况

在这里插入图片描述

3,找到内存和cpu占用最高的线程tid,比如19664
4,转为十六进制得到 0x2703,此为线程id的十六进制表示
5,执行 jstack 72414|grep -A 10 0x2703,得到线程堆栈信息中 0x2703 这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙高的调用方法

在这里插入图片描述


总结:

  • 学习了jvm 的常用命令:jps、jmap、jstack 等常用命令以及些经典案例的排查方法。后面我会继续更新相关jvm 的性能调优的方法,以及一些强大好用的工具使用。大大方便了我们解决线上问题,提高效率。有兴趣小伙伴点个关注,同时给个赞。你们的肯定是我继续分享的动力~~
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐