18-2 jvm调优工具(二)

211 阅读9分钟

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

四、Jstack使用

Jstack可以用来查看堆栈使用情况,还可以查看进程死锁情况。

4.1 [Jstack 进程号] 进程死锁分析

1.执行命令:

Jstack 进程号

2. 死锁案例分析:

package com.lxl.jvm;
​
public class DeadLockTest {
    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) {
​
                }
                synchronized (lock2) {
                    System.out.println("thread1 end");
                }
            }
        }).start();
​
        new Thread(() -> {
            synchronized (lock2) {
                try {
                    System.out.println("thread2 begin");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
​
                }
                synchronized (lock1) {
                    System.out.println("thread2 end");
                }
            }
        }).start();
    }
}

下面来分析一下这段代码:

  1. 定义了两个成员变量lock1,lock2

  2. main方法中定义了两个线程。

    • 线程1内部使用的是同步执行--上锁,锁是lock1。休眠5秒钟之后,他要获取第二把锁,执行第二段代码。
    • 线程2和线程1类似,锁相反。
  3. 问题:一开始,像个线程并行执行,线程一获取lock1,线程2获取lock2.然后线程1继续执行,当休眠5s后获取开启第二个同步执行,锁是lock2,但这时候很可能线程2还没有执行完,所以还没有释放lock2,于是等待。线程2刚开始获取了lock2锁,休眠五秒后要去获取lock1锁,这时lock1锁还没释放,于是等待。两个线程就处于相互等待中,造成死锁。

运行程序,通过Jstack命令来看看是否能检测到当前有死锁。

从这里面个异常可以看出,

  • prio:当前线程的优先级
  • cpu:cpu耗时
  • os_prio:操作系统级别的优先级
  • tid:线程id
  • nid:系统内核的id
  • state:当前的状态,BLOCKED,表示阻塞。通常正常的状态是Running我们看到Thread-0和Thread-1线程的状态都是BLOCKED.

通过上面的信息,我们判断出两个线程的状态都是BLOCKED,可能有点问题,然后继续往下看。

我们从最后的一段可以看到这句话:Found one Java-level deadlock; 意思是找到一个死锁。死锁的线程号是Thread-0,Thread-1。

Thread-0:正在等待0x000000070e706ef8对象的锁,这个对象现在被Thread-1持有。

Thread-1:正在等待0x000000070e705c98对象的锁,这个对象现在正在被Thread-0持有。

最下面展示的是死锁的堆栈信息。死锁可能发生在DeadLockTest的第17行和第31行。通过这个提示,我们就可以找出死锁在哪里了。

3. 使用jvisualvm查看死锁

在程序代码启动的过程中,打开jvisualvm工具。

找到当前运行的类,查看线程,就会看到最头上的一排红字:检测到死锁。然后点击“线程Dump”按钮,查看相信的线程死锁的信息。

这里可以找到线程私锁的详细信息,具体内容和上面使用Jstack命令查询的结果一样,这里实用工具更加方便。

4.2 Jstack找出占用cpu最高的线程堆栈信息。

我们使用案例来说明如何查询cpu线程飙高的问题。

代码:

package com.lxl.jvm;
​
public class Math {
    public static int initData = 666;
    public static User user = new User();
    public User user1;
​
    public int compute() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }
​
    public static void main(String[] args) {
        Math math = new Math();
        while(true){
            math.compute();
​
        }
    }
}
​

这是一段死循环代码,会占满cpu。下面就运行这段代码,来看看如何排查cpu飙高的问题。

第一步:运行代码,使用top命令查看cpu占用情况

top

我们看到cpu严重飙高,一般cpu达到80%就会报警了

第二步:使用top -p 命令查看飙高进程

使用【top -p 进程号】 查看进程id的cpu占用情况

第三步:按H,获取### 每个线程的内存情况

需要注意的是,这里的H是大写的H。

我们可以看出线程0和线程1线程号飙高。

第四步:找到内存和cpu占用最高的线程tid

通过上图我们看到占用cpu资源最高的线程有两个,线程号分别是4013442,4013457。我们一第一个为例说明,如何查询这个线程是哪个线程,以及这个线程的什么地方出现问题,导致cpu飙高。

第五步:将线程tid转化为十六进制

67187778是线程号为4013442的十六进制数。具体转换可以网上查询工具。

第六步:执行[ jstack 4013440|grep -A 10 67187778] 查询飙高线程的堆栈信息

接下来查询飙高线程的堆栈信息

 jstack 4013440|grep -A 10 67190882
  • 4013440:表示的是进程号
  • 67187778: 表示的是线程号对应的十六进制数

通过这个方式可以查询到这个线程对应的堆栈信息

从这里我们可以看出有问题的线程id是0x4cd0, 哪一句代码有问题呢,Math类的22行。

第七步:查看对应的堆栈信息找出可能存在问题的代码

上述方法定位问题已经很精确了,接下来就是区代码里排查为什么会有问题了。

五、Jinfo

Jinfo命令主要用来查看jvm参数

1. 查看当前运行的jvm参数

jinfo -flags 线程id

执行结果:

从结果可以看出,我们使用的是CMS+Parallel垃圾收集器

2. 查看java系统参数

jinfo -sysprops 进程id

执行结果:

Java System Properties:
#Thu Nov 11 17:28:19 CST 2021
java.runtime.name=OpenJDK Runtime Environment
java.protocol.handler.pkgs=org.springframework.boot.loader
sun.boot.library.path=/data/java/jdk8/jre/lib/amd64
java.vm.version=25.40-b25
java.vm.vendor=Oracle Corporation
java.vendor.url=http://java.oracle.com/
path.separator=:
java.vm.name=OpenJDK 64-Bit Server VM
file.encoding.pkg=sun.io
user.country=CN
sun.java.launcher=SUN_STANDARD
sun.os.patch.level=unknown
java.vm.specification.name=Java Virtual Machine Specification
user.dir=/data/temp
java.runtime.version=1.8.0_41-b04
java.awt.graphicsenv=sun.awt.X11GraphicsEnvironment
java.endorsed.dirs=/data/java/jdk8/jre/lib/endorsed
os.arch=amd64
java.io.tmpdir=/tmp
line.separator=\n
java.vm.specification.vendor=Oracle Corporation
os.name=Linux
sun.jnu.encoding=UTF-8
java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
java.specification.name=Java Platform API Specification
java.class.version=52.0
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
os.version=5.10.23-5.al8.x86_64
user.home=/root
user.timezone=Asia/Shanghai
java.awt.printerjob=sun.print.PSPrinterJob
file.encoding=UTF-8
java.specification.version=1.8
user.name=root
java.class.path=chapter1-jvm-0.0.1-SNAPSHOT.jar
java.vm.specification.version=1.8
sun.java.command=chapter1-jvm-0.0.1-SNAPSHOT.jar
java.home=/data/java/jdk8/jre
sun.arch.data.model=64
user.language=zh
java.specification.vendor=Oracle Corporation
awt.toolkit=sun.awt.X11.XToolkit
java.vm.info=mixed mode
java.version=1.8.0_41
java.ext.dirs=/data/java/jdk8/jre/lib/ext:/usr/java/packages/lib/ext
sun.boot.class.path=/data/java/jdk8/jre/lib/resources.jar:/data/java/jdk8/jre/lib/rt.jar:/data/java/jdk8/jre/lib/sunrsasign.jar:/data/java/jdk8/jre/lib/jsse.jar:/data/java/jdk8/jre/lib/jce.jar:/data/java/jdk8/jre/lib/charsets.jar:/data/java/jdk8/jre/lib/jfr.jar:/data/java/jdk8/jre/classes
java.vendor=Oracle Corporation
file.separator=/
java.vendor.url.bug=http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding=UnicodeLittle
sun.cpu.endian=little
sun.cpu.isalist=

六、Jstat使用

Jstat命令是jvm调优非常重要,且非常有效的命令。我们来看看她的用法:

1. 垃圾回收统计 jstat -gc

jstat -gc 进程id

这个命令非常常用,在线上有问题的时候,可以通过这个命令来分析问题。

下面我们来测试一下,启动一个项目,然后在终端驶入jstat -gc 进程id,得到如下结果:

上面的参数分别是什么意思呢?先识别参数的含义,然后根据参数进行分析

  • S0C: 第一个Survivor区的容量
  • S1C: 第二个Survivor区的容量
  • S0U: 第一个Survivor区已经使用的容量
  • S1U:第二个Survivor区已经使用的容量
  • EC: 新生代Eden区的容量
  • EU: 新生代Eden区已经使用的容量
  • OC: 老年代容量
  • OU:老年代已经使用的容量
  • MC: 方法区大小(元空间)
  • MU: 方法区已经使用的大小
  • CCSC:压缩指针占用空间
  • CCSU:压缩指针已经使用的空间
  • YGC: YoungGC已经发生的次数
  • YGCT: 这一次YoungGC耗时
  • FGC: Full GC发生的次数
  • FGCT: Full GC耗时
  • GCT: 总的GC耗时,等于YGCT+FGCT

连续观察GC变化的命令

jstat -gc 进程ID 间隔时间  打印次数

举个例子:我要打印10次gc信息,每次间隔1秒

jstat -gc 进程ID 1000 10

image

这样就连续打印了10次gc的变化,每次隔一秒。

这个命令是对整体垃圾回收情况的统计,下面将会差分处理。

2.堆内存统计

这个命令是打印堆内存的使用情况。

jstat -gccapacity 进程ID

a

  • NGCMN:新生代最小容量
  • NGCMX:新生代最大容量
  • NGC:当前新生代容量
  • S0C:第一个Survivor区大小
  • S1C:第二个Survivor区大小
  • EC:Eden区的大小
  • OGCMN:老年代最小容量
  • OGCMX:老年代最大容量
  • OGC:当前老年代大小
  • OC: 当前老年代大小
  • MCMN: 最小元数据容量
  • MCMX:最大元数据容量
  • MC:当前元数据空间大小
  • CCSMN:最小压缩类空间大小
  • CCSMX:最大压缩类空间大小
  • CCSC:当前压缩类空间大小
  • YGC:年轻代gc次数
  • FGC:老年代GC次数

3.新生代垃圾回收统计

命令:

jstat -gcnew 进程ID [ 间隔时间  打印次数]

这个指的是当前某一次GC的内存情况

  • S0C:第一个Survivor的大小
  • S1C:第二个Survivor的大小
  • S0U:第一个Survivor已使用大小
  • S1U:第二个Survivor已使用大小
  • TT: 对象在新生代存活的次数
  • MTT: 对象在新生代存活的最大次数
  • DSS: 期望的Survivor大小
  • EC:Eden区的大小
  • EU:Eden区的使用大小
  • YGC:年轻代垃圾回收次数
  • YGCT:年轻代垃圾回收消耗时间

4. 新生代内存统计

jstat -gcnewcapacity 进程ID

image

参数含义:

  • NGCMN:新生代最小- 容量
  • NGCMX:新生代最大容量
  • NGC:当前新生代容量
  • S0CMX:Survivor 1区最大大小
  • S0C:当前Survivor 1区大小
  • S1CMX:Survivor 2区最大大小
  • S1C:当前Survivor 2区大小
  • ECMX:最大Eden区大小
  • EC:当前Eden区大小
  • YGC:年轻代垃圾回收次数
  • FGC:老年代回收次数

5. 老年代垃圾回收统计

命令:

jstat -gcold 进程ID

image

参数含义:

  • MC:方法区大小
  • MU:方法区已使用大小
  • CCSC:压缩指针类空间大小
  • CCSU:压缩类空间已使用大小
  • OC:老年代大小
  • OU:老年代已使用大小
  • YGC:年轻代垃圾回收次数
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间,新生代+老年代

6. 老年代内存统计

命令:

jstat -gcoldcapacity 进程ID

image

参数含义:

  • OGCMN:老年代最小容量
  • OGCMX:老年代最大容量
  • OGC:当前老年代大小
  • OC:老年代大小
  • YGC:年轻代垃圾回收次数
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间

7. 元数据空间统计

命令

jstat -gcmetacapacity 进程ID

image

  • MCMN:最小元数据容量
  • MCMX:最大元数据容量
  • MC:当前元数据空间大小
  • CCSMN:最小指针压缩类空间大小
  • CCSMX:最大指针压缩类空间大小
  • CCSC:当前指针压缩类空间大小
  • YGC:年轻代垃圾回收次数
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间

8.整体运行情况

命令:

jstat -gcutil 进程ID

image

  • S0:Survivor 1区当前使用比例
  • S1:Survivor 2区当前使用比例
  • E:Eden区使用比例
  • O:老年代使用比例
  • M:元数据区使用比例
  • CCS:指针压缩使用比例
  • YGC:年轻代垃圾回收次数
  • YGCT:年轻代垃圾回收消耗时间
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间