6款工具助力分析JVM问题

写在前面

源码 。

在生产环境不可避免的会出现因为JVM问题导致的故障,这种情况如果处理不当,不及时,很有可能影响毛爷爷,甚至丢掉饭碗,因此掌握处理该类问题的硬技能就显得非常重要了,那都需要哪些硬技能呢,我认为如下的2个:

1:定位分析问题的能力。
2:不要慌!不要慌!!不要慌!!!

对于1,除了平时积累的技术能力外,外部辅助工具的使用也异常重要,这也正是本文要重点分析的内容,对于2,大家都知道,不管什么事,一慌张就完蛋,所以千万不要慌(至少要装作不慌张(* ̄︶ ̄))。下面我们就开始吧!

1:准备工作

使用如下程序:

public class CommonMistakesApplication {

    public static void main(String[] args) throws Exception {
        //启动10个线程
        IntStream.rangeClosed(1, 10).mapToObj(i -> new Thread(() -> {
            while (true) {
                //每一个线程都是一个死循环,休眠10秒,打印10M数据
                // IntStream.rangeClosed(1, 10000000)生成1~10000000的数字序列
                // mapToObj(__ -> "a")生成10000000个a,即10M数据
                // collect(Collectors.joining("")) 拼接为字符串
                String payload = IntStream.rangeClosed(1, 10000000)
                        .mapToObj(__ -> "a")
                        .collect(Collectors.joining("")) + UUID.randomUUID().toString();
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(payload.length());
            }
        })).forEach(Thread::start);
        TimeUnit.HOURS.sleep(1);
    }
}

如下pom:

<plugin>
    <groupId>org.apache.maven.pluginsgroupId>
    <artifactId>maven-shade-pluginartifactId>
    <version>1.2.1version>
    <executions>
        <execution>
            <phase>packagephase>
            <goals>
                <goal>shadegoal>
            goals>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>dongshi.daddy.zhengxi.CommonMistakesApplicationmainClass>
                    transformer>
                transformers>
            configuration>
        execution>
    executions>
plugin>

如果觉得麻烦,也可以直接从这里 下载打好的jar包。

插件用于生成可执行的jar包。生成jar包后可以使用命令java -jar .\java8instrument-1.0-SNAPSHOT.jar -Xms1g -Xmx1g,将堆的最大内存和最小内存都设置为1g。
如下我本地启动的结果:

$ java -jar java8instrument-1.0-SNAPSHOT.jar -Xms1g -Xmx1g
10000036
10000036
10000036
10000036
...

2:jps获取进程信息

C:\WINDOWS\system32>jps -l | findstr "java8"
23028 java8instrument-1.0-SNAPSHOT.jar

因为jps值专门针对java进程的命令,所以使用起来要比ps方便的多。

3:jinfo pid获取配置信息

C:\WINDOWS\system32>jinfo 23028
Attaching to process ID 23028, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.202-b08
Java System Properties:

java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.202-b08
sun.boot.library.path = D:\program_files\jdk1.8.0_202\jre\bin
java.vendor.url = http://java.oracle.com/
...
sun.cpu.isalist = amd64

VM Flags:
Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=266338304 -XX:MaxHeapSize=4242538496 -XX:MaxNewSize=1414004736 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=88604672 -XX:OldSize=177733632 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line:

注意到-XX:MaxHeapSize=4242538496注意到堆内存是4g,而不是我们设置的1g,这是因为我们将参数配置写在了后面,被当做主函数的参数了,可以增加如下代码验证:

System.out.println("启动参数:");
System.out.println(Arrays.stream(args).collect(Collectors.joining(System.lineSeparator())));

测试输出如下:

$ java -jar -Dfile.encoding=utf-8 java8instrument-1.0-SNAPSHOT.jar -Xms1g -Xmx1g
启动参数:
-Xms1g
-Xmx1g

也可以添加如下代码确认设置的VM参数:

$ java -jar -Dfile.encoding=utf-8 -Xms1g -Xmx1g java8instrument-1.0-SNAPSHOT.jar test
启动参数:
test
VM options
-Dfile.encoding=utf-8
-Xms1g
-Xmx1g
10000036
10000036
...

因此我们的启动命令修改为java -jar -Dfile.encoding=utf-8 -Xms1g -Xmx1g java8instrument-1.0-SNAPSHOT.jar

4:jvisualvm

这是一个GUI工具,可以通过界面方便的查看相关信息。

4.1:查看VM配置信息

  • JVM参数
    6款工具助力分析JVM问题_第1张图片
  • 系统属性
    6款工具助力分析JVM问题_第2张图片

如图红框的展示信息。

  • 堆内存信息
    6款工具助力分析JVM问题_第3张图片
    在180M到900M之间。
  • 线程信息
    6款工具助力分析JVM问题_第4张图片
    23个线程。

  • 6款工具助力分析JVM问题_第5张图片
    已装载类个数在一千六左右。
  • CPU
    6款工具助力分析JVM问题_第6张图片

4.2:转储

  • 线程转储
    6款工具助力分析JVM问题_第7张图片
  • 堆转储
    6款工具助力分析JVM问题_第8张图片

5:jconsole

这是一个GUI工具,类似于jvirtualvm,可以通过界面方便的查看相关信息。

  • 查看内存
    6款工具助力分析JVM问题_第9张图片
    右下角可以切换各个区域的内存使用情况。
  • 查看线程
    6款工具助力分析JVM问题_第10张图片
    最下角检测死锁按钮还可以检测死锁。
  • 类个数
    6款工具助力分析JVM问题_第11张图片

6:jstat

使用该命令可以查看堆各个区域的使用情况,GC的情况,GC的耗时等信息,一般使用命令jstat -gcutil PID 刷新毫秒间隔 打印次数,如下是2秒打印一次,一共打印1000次:
6款工具助力分析JVM问题_第12张图片
各列含义如下:

S0:survivor 0区域使用百分比。
S1:survivor 1区域使用百分比。
E:Eden区域使用百分比。
O:Old区域使用百分比。
M:Metadata元数据区使用百分比。
YGC:年轻代发生GC的次数。
YGCT:年轻代回收总耗时(秒)。
FGC:老年代发生GC的次数。
FGCT:老年老垃圾回收总耗时(秒)。
GCT:所有区本次垃圾回收总耗时(秒)。

从图中gc次数的增长可以看出大概计算出多长时间发生一次GC,如年老代大概是10秒钟发生一次FGC,这也是生产环境出现问题时我们重点需要关注的点。

7:jstack

用于生成线程栈信息,如下:
6款工具助力分析JVM问题_第13张图片

我们可以将生成的信息保存到文件中,然后通过网站分析工具fastThread 来生成图标的分析结果,方便我们分析,如下操作:

7.1:保存结果到文件

6款工具助力分析JVM问题_第14张图片

7.2:使用fastThread 分析

  • 选择7.1:保存结果到文件生成的文件:

6款工具助力分析JVM问题_第15张图片

然后点击Analyze开始分析,分析主要结果如下:

  • 线程总量和状态统计

6款工具助力分析JVM问题_第16张图片

  • 守护线程VS非守护线程
    6款工具助力分析JVM问题_第17张图片

  • 线程调用栈帧
    6款工具助力分析JVM问题_第18张图片

6款工具助力分析JVM问题_第19张图片

  • 方法调用次数统计
    6款工具助力分析JVM问题_第20张图片

  • 调用栈深度统计

可用于排查StackOverflowError,即方法调用栈过深,导致栈内存溢出如下测试代码获取的结果:

public class DeepStackTrace {

    public static void main(String[] args) throws Exception {
        sayHi();
    }

    private static void sayHi() throws Exception {
        Thread.sleep(200);
        sayHi();

    }
}

6款工具助力分析JVM问题_第21张图片

点击红框内容:

6款工具助力分析JVM问题_第22张图片

  • 死锁检测

6款工具助力分析JVM问题_第23张图片

8:jcmd

8.1: 获取启动的虚拟机列表

$ jcmd -l
2816 org.jetbrains.idea.maven.server.RemoteMavenServer36
15044 java8instrument-1.0-SNAPSHOT.jar
20516 org.jetbrains.idea.maven.server.RemoteMavenServer36
22084 org.jetbrains.idea.maven.server.RemoteMavenServer36
...

8.2: 查看虚拟机启动时长

$ jcmd 20724 VM.uptime
20724:
765227.730 s

8.3: 打印线程栈帧信息

$ jcmd 20724 Thread.print
20724:
2022-04-01 14:26:58
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.202-b08 mixed mode):

"DestroyJavaVM" #51 prio=5 os_prio=0 tid=0x000000002837a800 nid=0x4214 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"http-nio-8765-AsyncTimeout" #49 daemon prio=5 os_prio=0 tid=0x0000000028378000 nid=0x1288 waiting on condition [0x000000002c08e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at org.apache.coyote.AbstractProtocol$AsyncTimeout.run(AbstractProtocol.java:1133)
        at java.lang.Thread.run(Thread.java:748)
...

8.4: 查看对象实例数

获取对象的实例数,占用空间大小,按照占用空间大小排序。如下测试代码和测试的结果:

public class ManyObj {
    private static List<People> peopleList = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        System.out.println("test begin");
        IntStream.rangeClosed(1, 99999).forEach(__ -> peopleList.add(new People()));
        System.out.println("test end");
        System.in.read();
    }
}

6款工具助力分析JVM问题_第24张图片

红框中就是我们在测试代码中创建的对象实例,同样的功能也可以使用jmap 命令,jmap -histo PID来实现,如下:

$ jmap -histo 26396

 num     #instances         #bytes  class name
----------------------------------------------
   1:         99999        1599984  dongshi.daddy.zhengxi.People
   2:           950         479976  [Ljava.lang.Object;
   3:          5138         479224  [C
   4:           455         136400  [B
   5:          4989         119736  java.lang.String
   6:           899         102840  java.lang.Class
   7:           791          31640  java.util.TreeMap$Entry
   ...

8.5: 导出堆栈信息

$ jcmd 26396 GC.heap_dump d:\\test\\manyobj_jcmd.bin
26396:
Heap dump file created

在这里插入图片描述

类似的命令还有jmap -dump:live,format=b,file=/path/to/filename.hprof PID

8.6: 获取启动参数

$ jcmd 26396 VM.flags
26396:
-XX:CICompilerCount=4 -XX:InitialHeapSize=266338304 -XX:MaxHeapSize=4242538496 -XX:MaxNewSize=1414004736 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=88604672 -XX:OldSize=177733632 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC

写在后面

参考文章列表:

6 款 Java 8 自带工具,轻松分析定位 JVM 问题! 。
十二、jdk工具之jcmd介绍(堆转储、堆分析、获取系统信息、查看堆外内存) 。

你可能感兴趣的:(杂,jvm工具,jps,jmap)