JVM调优工具

本次介绍一下在排查问题以及JVM调优的时候,常用的一些工具。

1. 示例项目

首先创建一个示例项目,用于演示工具的使用。

1.1 项目整体结构图

项目整体结构图

1.2 项目pom文件



    4.0.0

    org.example
    WebJVM
    1.0-SNAPSHOT

    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.2.RELEASE
         
    

    
        UTF-8
        UTF-8
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

1.3 配置文件application.yml

server:
  port: 8080

1.4 启动类Application

package com.jvm;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

2. JPS

本次在Mac环境上演示这些工具。首先要查看Java程序进程号,可以用jps命令

运行示例项目,并在命令行输入jps命令。

jps

可知29321为本次运行的进程。

3. Jmap

jmap命令是一个可以输出所有内存中对象的工具,甚至可以将JVM中的heap,以二进制输出成文本

3.1 命令格式

jmap [option] 
    (to connect to running process) 连接到正在运行的进程

jmap [option] 
    (to connect to a core file)     连接到核心文件

jmap [option] [server_id@]
    (to connect to remote debug server) 连接到远程调试服务

3.2 参数说明

3.2.1 options

>    pid:    目标进程的PID,进程编号,可以采用ps -ef | grep java 查看java进程的PID;
>    executable:     产生core dump的java可执行程序;
>    core:     将被打印信息的core dump文件;
>    remote-hostname-or-IP:     远程debug服务的主机名或ip;
>    server-id:     唯一id,假如一台主机上多个远程debug服务;

3.2.2 histo

此命令可以查看内存信息、实例个数以及占用内存大小。

jmap -histo 29321 > ./log.txt

该命令可以将结果输出到log.txt文件中。打开该文件,如下图:


histo命令

可以看到有四列,分别代表:

  • 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[][]

3.2.3 heap

该命令可以查看堆信息,这里特别说明一下,由于实验机是Mac,而Mac系统在JDK8的版本上有BUG,导致对当前进程进行监控时,当前进程会退出,所以Java在JDK9之后加入了jhsdb前缀命令进行了修复。所以下边说明会分普通版本和Mac版本。

普通版本:

jmap -heap 29251

Mac版本:

jhsdb jmap --heap --pid 29591

得到下图:


heap指令

可以看到JVM采用的垃圾收集器是G1,有G1的一些信息。

3.2.4 dump命令

记录堆内存快照

jmap ‐dump:format=b,file=dump.hprof 30381

也可以设置内存溢出自动导出dump文件(文件很大的时候,可能会导不出来)

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

示例代码:

package com.zyz.oom;

import com.zyz.User;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * JVM设置
 *  ‐Xms10M ‐Xmx10M ‐XX:+PrintGCDetails ‐XX:+HeapDumpOnOutOfMemoryError ‐XX:HeapDumpPath=/Users/zhangyunzheng/jvm.dump
 */
public class OOMTest {
    public static void main(String[] args) {
        List list = new ArrayList();
        int i = 0;
        int j = 0;
        while (true){
            list.add(new User(i++, UUID.randomUUID().toString()));
            new User(j--, UUID.randomUUID().toString());
        }
    }
}


可以用jvisualvm工具导入该dump文件分析


OOM

4. Jstack

4.1 监控死锁

用该工具可以查找死锁,见如下示例代码

package com.zyz.deadLock;

import java.util.Date;

public class DeadLockTest {

    public static String obj1 = "obj1";
    public static String obj2 = "obj2";

    public static void main(String[] args) {
        LockA la = new LockA();
        new Thread(la).start();
        LockB lb = new LockB();
        new Thread(lb).start();
    }

    static class LockA implements Runnable {
        public void run() {
            try {
                System.out.println(new Date().toString() + " LockA 开始执行");
                while (true) {
                    synchronized (DeadLockTest.obj1) {
                        System.out.println(new Date().toString() + " LockA 锁住 obj1");
                        Thread.sleep(3000); // 此处等待是给B能锁住机会
                        synchronized (DeadLockTest.obj2) {
                            System.out.println(new Date().toString() + " LockA 锁住 obj2");
                            Thread.sleep(60 * 1000); // 为测试,占用了就不放
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static class LockB implements Runnable {
        public void run() {
            try {
                System.out.println(new Date().toString() + " LockB 开始执行");
                while (true) {
                    synchronized (DeadLockTest.obj2) {
                        System.out.println(new Date().toString() + " LockB 锁住 obj2");
                        Thread.sleep(3000); // 此处等待是给A能锁住机会
                        synchronized (DeadLockTest.obj1) {
                            System.out.println(new Date().toString() + " LockB 锁住 obj1");
                            Thread.sleep(60 * 1000); // 为测试,占用了就不放
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}

运行该程序,执行jstack命令

jstack 30578
image
"Thread-1" 线程名 
prio=5 优先级=5 
tid=0x00007fef9082d000 线程id 
nid=0x5f03 线程对应的本地线程标识nid 
java.lang.Thread.State: BLOCKED 线程状态
image

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

步骤如下:

  • 使用命令top -p ,显示你的java进程的内存情况,pid是你的java进程号,比如19663
  • 按H,获取每个线程的内存情况
  • 找到内存和cpu占用最高的线程tid,比如19664
  • 转为十六进制得到 0x4cd0,此为线程id的十六进制表示
  • 执行 jstack 19663|grep -A 10 4cd0,得到线程堆栈信息中 4cd0 这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙高的调 用方法
  • 查看对应的堆栈信息找出可能存在问题的代码

5. Jinfo

查看正在运行的Java应用程序的扩展参数

5.1 查看jvm的参数

jinfo -flags 30383

5.2 查看java系统参数

jinfo -sysprops 30383

6. Jstat

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。

6.1 命令格式

jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数]

6.2 垃圾回收统计

jstat -gc pid 最常用,可以评估程序内存使用及GC压力整体情况

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

6.3 堆内存统计

jstat -gccapacity 30634
堆内存统计
  • NGCMN:新生代最小容量
  • NGCMX:新生代最大容量
  • NGC:当前新生代容量
  • S0C:第一个幸存区大小
  • S1C:第二个幸存区的大小
  • EC:伊甸园区的大小
  • OGCMN:老年代最小容量
  • OGCMX:老年代最大容量
  • OGC:当前老年代大小
  • OC:当前老年代大小
  • MCMN:最小元数据容量
  • MCMX:最大元数据容量
  • MC:当前元数据空间大小
  • CCSMN:最小压缩类空间大小
  • CCSMX:最大压缩类空间大小
  • CCSC:当前压缩类空间大小
  • YGC:年轻代gc次数
  • FGC:老年代GC次数

6.4 新生代垃圾回收统计

jstat -gcnew 30634
新生代垃圾回收统计
  • S0C:第一个幸存区的大小
  • S1C:第二个幸存区的大小
  • S0U:第一个幸存区的使用大小
  • S1U:第二个幸存区的使用大小
  • TT:对象在新生代存活的次数
  • MTT:对象在新生代存活的最大次数
  • DSS:期望的幸存区大小
  • EC:伊甸园区的大小
  • EU:伊甸园区的使用大小
  • YGC:年轻代垃圾回收次数
  • YGCT:年轻代垃圾回收消耗时间

6.5 新生代内存统计

jstat -gcnewcapacity 30634
新生代内存统计
  • NGCMN:新生代最小容量
  • NGCMX:新生代最大容量
  • NGC:当前新生代容量
  • S0CMX:最大幸存1区大小
  • S0C:当前幸存1区大小
  • S1CMX:最大幸存2区大小
  • S1C:当前幸存2区大小
  • ECMX:最大伊甸园区大小
  • EC:当前伊甸园区大小
  • YGC:年轻代垃圾回收次数
  • FGC:老年代回收次数

6.6 老年代垃圾回收统计

jstat -gcold 30634
老年代垃圾回收统计
  • MC:方法区大小 MU:方法区使用大小
  • CCSC:压缩类空间大小
  • CCSU:压缩类空间使用大小
  • OC:老年代大小
  • OU:老年代使用大小
  • YGC:年轻代垃圾回收次数
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间

6.7 老年代内存统计

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

6.8 元空间数据统计

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

6.9 查看GC情况

jstat -gcutil 30634
查看GC情况
  • S0:幸存1区当前使用比例
  • S1:幸存2区当前使用比例
  • E:伊甸园区使用比例
  • O:老年代使用比例
  • M:元数据区使用比例
  • CCS:压缩使用比例
  • YGC:年轻代垃圾回收次数
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间

7. Arthas

Arthas 是 Alibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6+, 采用命令行交互模式,可以方便的定位和诊断 线上程序运行问题。Arthas 官方文档十分详细,详见:https://alibaba.github.io/arthas

PS:没想到阿里的大佬也是魔兽迷,十分推荐用这个工具排查问题

你可能感兴趣的:(JVM调优工具)