【jvm系列-11】jvm性能调优篇---命令行工具的基本使用

JVM系列整体栏目


内容 链接地址
【一】初识虚拟机与java虚拟机 https://blog.csdn.net/zhenghuishengq/article/details/129544460
【二】jvm的类加载子系统以及jclasslib的基本使用 https://blog.csdn.net/zhenghuishengq/article/details/129610963
【三】运行时私有区域之虚拟机栈、程序计数器、本地方法栈 https://blog.csdn.net/zhenghuishengq/article/details/129684076
【四】运行时数据区共享区域之堆、逃逸分析 https://blog.csdn.net/zhenghuishengq/article/details/129796509
【五】运行时数据区共享区域之方法区、常量池 https://blog.csdn.net/zhenghuishengq/article/details/129958466
【六】对象实例化、内存布局和访问定位 https://blog.csdn.net/zhenghuishengq/article/details/130057210
【七】执行引擎,解释器、JIT即时编译器 https://blog.csdn.net/zhenghuishengq/article/details/130088553
【八】精通String字符串底层机制 https://blog.csdn.net/zhenghuishengq/article/details/130154453
【九】垃圾回收底层原理和算法以及JProfiler的基本使用 https://blog.csdn.net/zhenghuishengq/article/details/130261481
【十】垃圾回收器的种类以及内部的执行原理 https://blog.csdn.net/zhenghuishengq/article/details/130261481
【十一】jvm性能调优篇之命令行工具的基本使用 https://blog.csdn.net/zhenghuishengq/article/details/130641456

一,JVM监控调优工具

从本文开始,正式进入jvm性能调优篇阶段

1,jvm调优篇概述

1.1,生产环境中可能出现的问题

  • 生产环境中发生了内存泄漏该如何处理?
  • 生产环境中该给服务器分配多少内存合适?
  • 如何对垃圾回收器的性能进行调优?
  • 生产环境中的CPU负载飙高该如何解决?
  • 生产环境应该给引用分配多少内存?
  • 不加日志,如何确定是否执行了某一行代码?
  • 不加日志,如何实时的查看某个方法的入参和返回值?

因此需要通过调优,来解决这些问题:防止出现OOM、解决OOM、减少Full GC出现的频率

1.2,性能优化的步骤

1,性能监控(发现问题): 通过一些监控工具来发现问题,然后对产生的问题的内容进行调优,一般可能出现的问题有:GC频繁、CPUload过高、OOM、内存泄漏、死锁、程序响应的时间较长等

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

2,性能分析(排查问题): 可以通过GCviewer堆日志进行分析;也可以运用一些命令行工具,如jstack、jmap、jinfo等分析;也可以通过dump出堆文件,然后使用内存分析工具分析文件;或者直接AkiraArthas,或者jconsoleJViaual来查看实时的JVM状态;或者直接使用jstack查看堆栈信息

3,性能调优(解决问题): 适当的增加内存;优化代码;增加机器;合理的设置线程池线程数量;增加消息中间件,缓存、消息队列等。

1.3,性能测试指标

主要从以下面的几个参数就行分析

1,停顿时间: 提交请求和返回请求响应之间的时间,一般比较关注的是平均时间

2,吞吐量: 单位时间内完成的工作量

3,并发数: 同一时刻,对服务器有实际交互的请求数

4,内存占用: Java堆区所占的内存大小

在调优中,停顿时间和吞吐量是主要关注点。

2,JVM监控工具

2.1,JPS

Java Process Status,表示java进程的状态。通过这个命令,可以显示指定系统内所有的HotSpot虚拟机进程,可用于查询正在运行的虚拟机线程。

[root@VM-12-3-centos study]# jps
27586 Jps
29658 jar

除了使用这个jps查看,也可以在jps后面加上以下参数,分别是-l-q-m-v ,日常开发中,基本上很少用这些参数,当然,这些参数除了可以单独使用之外,还可以结合使用。

//输出应用程序主类的全名称
[root@VM-12-3-centos study]# jps -l
29658 study-0.0.1-SNAPSHOT.jar
30190 sun.tools.jps.Jps
//只能够看到进程id
[root@VM-12-3-centos study]# jps -q
29658
30190
//查看进程id以及mian方法中参数信息
[root@VM-12-3-centos study]# jps -m
29658 jar
3437 Jps -m
//列出虚拟机启动时的JVM参数
[root@VM-12-3-centos study]# jps -v
29658 study-0.0.1-SNAPSHOT.jar
30190 sun.tools.jps.Jps

随后也可以通过以下的命令查看对应的进程号的详情

ps -ef | grep 29658
ps -aux | grep 29658

如果在进程中,使用了 -XX:-UsePerfData 这个参数 ,那么jps将无法查看此进程的信息,同时也说明这个参数是默认开启的。

2.2,jstat(重点)

Jvm Statistics Monitoring Tool ,表示监视虚拟机运行状态的信息。他可以显示本地或者远程虚拟机进程中类加载、内存、垃圾收集、JIT即时编译等运行数据,常用于检测垃圾回收问题和内存泄漏问题。

输入这个jstat这个命令之后,可以查看到如何使用以及可加的参数。

[root@VM-12-3-centos study]# jstat
invalid argument count
Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
       //<option> : 只要监视的对象信息,如class
       //-t:程序执行的总时间,显示在head头部的Timestamp中
       //-h:多少周期打印一次表头信息
       //<interval> : 周期,比如多长时间打印一次
       //<count> : 打印的总次数,默认只查询一次

这个option可以是很多选择的对象,这里以 class 为例,主要用于显示类加载的相关信息。这里表示的就是监视的是class对象,加了-t就会在前面通过这个Timestamp显示一个运行了多长时间,-h3表示每隔3个就会打印一次头部信息,接着的就是进程号,接着的就是1000 5,表示每间隔1秒打印一次,总共打印5次。

[root@VM-12-3-centos study]# jstat -class -t -h3 29658 1000 5
Timestamp     Loaded    Bytes    Unloaded    Bytes      Time   
6913.2         10912   19626.3       0        0.0       6.44
6914.3         10912   19626.3       0        0.0       6.44
6915.3         10912   19626.3       0        0.0       6.44
Timestamp     Loaded    Bytes    Unloaded    Bytes      Time   
6916.3  	   10912   19626.3       0        0.0       6.44
6917.3  	   10912   19626.3       0        0.0       6.44

接下来对上面的各个参数做一个解释

//Timestamp:程序运行的总时间
//Loaded:加载的类的个数  //Bytes:加载类的字节数
//Unloaded:卸载的类的个数 //Bytes:卸载的类的字节数
//Time:加载的总体时间

上面这个option只用过了class,除了class之外,还可以使用垃圾回收相关的参数

  • -gc:显示GC相关的堆信息。如新生代老年代的容量、已用空间等
  • -gccapacity:显示的内容和-gc的基本一致,关注的内容是Java堆的各个区域使用到的最大、最小空间
  • -gcutil:显示内容与 -gc 基本一致,关注的内容是已使用空间占总空间的百分比
  • -gccause:与-gcutil功能一样,会额外输出导致最后一次或者当前发生gc的原因
  • -gcnew:显示新生代GC状况
  • -gcnewcapacity:与-gcnew基本相同,输出主要是关注使用到的最大空间和最小空间
  • -geold:显示老年代GC状况
  • -gcoldcapacity:显示的内容与-gcold基本相同,输出关注点是使用到的醉倒和最小空间
  • -gcpermcapacity:显示永久代使用到的最大空间

接下来可以通过这个命令分析GC相关的具体参数

jstat -gc 进程号id
[root@VM-12-3-centos ~]# jstat -gc 29658
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1024.0 1024.0  0.0   480.3  15872.0   5225.1   80384.0    71941.6   59992.0 56814.9 7808.0 7262.8   3727   11.990   3      0.321   12.312

接下来对上面的参数做一个详细的解释

//上面的S表示survivor区,C表示已经使用的容量,U表示已经使用的容量
//E表示Eden区,O表示old区,M表示方法区,CCS表示压缩
//YGC表示Young Gc次数,YGCT表示YoungGC时间
//FGC表示Full Gc次数,FGCT表示Full GC时间
//GCT表示总GC的时间,这里指的是Young GC和Full Gc的和

这些依旧可以和参数搭配使用

jstat -gc -t -h3 29658 1000 5

其结果如下,和上面的class的现象是一样的

[root@VM-12-3-centos ~]# jstat -gc -t -h3 29658 1000 5
Timestamp        S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
        86023.0 1024.0 1024.0  0.0   368.3  15872.0   6335.5   80384.0    71965.6   59992.0 56814.9 7808.0 7262.8   3753   12.074   3      0.321   12.395
        86024.0 1024.0 1024.0  0.0   368.3  15872.0   6335.5   80384.0    71965.6   59992.0 56814.9 7808.0 7262.8   3753   12.074   3      0.321   12.395
        86025.0 1024.0 1024.0  0.0   368.3  15872.0   6337.5   80384.0    71965.6   59992.0 56814.9 7808.0 7262.8   3753   12.074   3      0.321   12.395
Timestamp        S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
        86026.0 1024.0 1024.0  0.0   368.3  15872.0   6337.5   80384.0    71965.6   59992.0 56814.9 7808.0 7262.8   3753   12.074   3      0.321   12.395
        86027.0 1024.0 1024.0  0.0   368.3  15872.0   6339.6   80384.0    71965.6   59992.0 56814.9 7808.0 7262.8   3753   12.074   3      0.321   12.395

因此在实际开发中,可以通过这个jstat来对内存进行监控。并且可以通过这个Java进程启动的时间(TimeStamp)比上GC时间(GCT),得出GC时间占总运行时间的比例,如果该比例超过20%,则说明堆的压力较大,如果比例超过90%,则说明堆里面几乎没有可用空间,随时都可能出现OOM。

2.3,Jinfo

Configuration Info for Java,查看虚拟机配置参数的信息,也可以用于调整虚拟机的配置参数。在很多情况下,Java应用程序不会指定所有的Java虚拟机参数,开发人员可能不知道某一个具体参数的默认值,因此可以直接通过这个jinfo工具,开发人员可以很方便的找到java虚拟机参数的当前值。

jinfo的基本使用如下:

[root@VM-12-3-centos study]# jinfo --help
Usage:
   jinfo [option] <pid>
       (to connect to running process)
   jinfo [option] <executable <core>
       (to connect to a core file)
   jinfo [option] [server_id@]<remote server IP or hostname>
       (to connect to remote debug server)

where <option> is one of:
   -flag <name>         to print the value of the named VM flag
   -flag [+|-]<name>    to enable or disable the named VM flag
   -flag <name>=<value> to set the named VM flag to the given value
   -flags               to print VM flags
   -sysprops            to print Java system properties
   <no option>          to print both of the above
   -h | -help           to print this help message

在jinfo中,主要可以用来查看和修改配置参数,查看的具体命令可以如下

jinfo -flags PID  //查看一些曾经赋值过的一些参数
jinfo -flags 具体参数 PID  //查看某个java进程的一些参数的值
jinfo -flags UseParallel 29658  //查看当前进程是否用的是Parallel垃圾回收器

也可以修改参数,修改的具体命令如下

jinfo -flag 具体参数 PID
jinfo -flag PrintGcDetails 29658  //修改这个打印GC日志的状态,默认不打印,修改后为打印

2.4,jmap

JVM Memory Map,导出内存映像文件,显示内存的使用情况。 获取dump文件,同时也可以获取到Java进程的内存相关信息,如一些堆各个区域的使用情况,堆对象的统计信息,类加载信息等。可以通过jmap -help查看相关指令

[root@VM-12-3-centos ~]# jmap -help
Usage:
    jmap [option] <pid>(to connect to running process)
    jmap [option] <executable <core>(to connect to a core file)
    jmap [option] [server_id@]<remote server IP or hostname>(to connect to remote debug server)

jmap可以通过 -dump 生成Java堆转储快照dump文件,-dump:live只保存堆中存活的对象。可以通过该文件查看具体是什么原因导致的堆溢出,内存泄漏等问题。可以通过手动的方式生成,如下

jmap -dump:format=b,file=zhs.hprof  29658
jmap -dump:live:format=b,file=../zhs.hprof  29658    //只保存那一刻的存活对象

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

也可以通过自动的方式生成,会在系统即将要OOM的时候自动生成一个dump文件,其指令如下

-Xmx100m -XX:HeapDumpOnOutOfMemoryError -XX:heapDumpPath=zhs.hprof

可以通过 -heap 输出整个堆空间的详细信息,包括GC的使用,堆配置信息,以及内存的使用信息等,挺好用的

jmap -heap 29658

可以通过 -histo 输出堆对象中的统计信息,包括一些类、实例数量和总容量等

jmap -histo 29658
jmap -histo:live 29658   //只统计堆中存活的对象

可以通过-finalizerinfo 显示需要被回收的对象信息,但是该命令只在Linux下有效

jmap -finalizerinfo 29658  

总结: 由于jmap将访问堆中的对象,为了保证在此过程中不被打扰,jmap需要在安全点机制访问,即stw的时候访问数据,因此可能导致对快照的分析结果存在偏差。并且如果一直无法stw,那么jmap将一直等下去。相对于jstat而言,jstat是实时监控的,这方面的准确度比jmap高。

2.5,jhat

JVM Heap Analysts Tool ,显而易见,从这几个单词来看就是用来分析堆内存的工具。在jmap这个工具中,可以生成一个dump文件,而这个jhat就是为了配合这个jmap使用的。在jhat中,其内部内嵌了一个小型的服务器,生成dump文件的分析结果之后,用户可以在浏览器中查看分析结果。

使用了这个jhat命令,就相当于启动了一个http服务,访问地址为:localhost:7000。但是在JDK9,10之后已被删除,官方建议使用VisualVM图形化界面代替这个命令行工具。

2.6,jstack(重点)

jmap是用于打印堆空间的快照,这个jstack就是用于打印这个栈帧中的快照了。JVM StackTrace ,用于生成指定进程当前时刻的线程快照。线程快照指的就是虚拟机内每一条线程正在执行方法堆栈的集合。

生成线程快照的主要作用如下:可以用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间等待,阻塞等问题。

其基本语法如下:

[root@VM-12-3-centos study]# jstack 
Usage:
    jstack [-l] <pid>(to connect to running process)
    jstack -F [-m] [-l] <pid>(to connect to a hung process)
    jstack [-m] [-l] <executable> <core>(to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>(to connect to a remote debug server)

Options:
    -F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

接下来继续体验一下,直接通过这个jstack -pid

[root@VM-12-3-centos study]# jstack 29658
"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f0790141000 nid=0x73e3 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f079013e000 nid=0x73e2 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
...

如下面模拟一段死锁问题,其代码如下

package com.zhs.study.test;

/**
 * @author zhenghuisheng
 * @date : 2023/5/12
 */
public class DeadBlockTest {
    
    
    public static void main(String[] args) {
    
    
        StringBuilder s1 = new StringBuilder();
        StringBuilder s2= new StringBuilder();
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                synchronized (s1){
    
    
                    s1.append("a");
                    s1.append("b");
                    try {
    
    
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    synchronized (s2){
    
    
                        s2.append("c");
                        s2.append("d");
                    }
                }
            }
        }.start();

        new Thread(){
    
    
            @Override
            public void run() {
    
    
                synchronized (s2){
    
    
                    s1.append("a");
                    s1.append("b");
                    try {
    
    
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    synchronized (s1){
    
    
                        s2.append("c");
                        s2.append("d");
                    }
                }
            }
        }.start();
    }
}

运行上面的这一段代码之后,再通过jps获取其jvm对应的进程号,再通过jstack + 进程号即可,如我这边通过输入jps查到的进程号为12308,然后直接 jstack 12308 即可获取死锁等问题。

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x000000001ce7f608 (object 0x000000076b5b6a28, a java.lang.StringBuilder),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x000000001ce7cd78 (object 0x000000076b5b6a70, a java.lang.StringBuilder),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.zhs.study.test.DeadBlockTest$2.run(DeadBlockTest.java:42)
        - waiting to lock <0x000000076b5b6a28> (a java.lang.StringBuilder)
        - locked <0x000000076b5b6a70> (a java.lang.StringBuilder)
"Thread-0":
        at com.zhs.study.test.DeadBlockTest$1.run(DeadBlockTest.java:23)
        - waiting to lock <0x000000076b5b6a70> (a java.lang.StringBuilder)
        - locked <0x000000076b5b6a28> (a java.lang.StringBuilder)

Found 1 deadlock.

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

除了死锁,其他的sleep长时间等待等等,也能查看得到。

2.7,jcmd

在jdk7之后,新增了一个命令行工具jcmd,可以实现除了jstat之外的所有命令,如导出堆、内存使用、查看java进程、导出线程信息、执行GC、jvm运行时间等。并且更加推荐jcmd代替jmap的使用。

可以通过 jcmd -l 获取所有的jvm的进程,jcmd pid help 查看指定线程所支持的所有命令,可以通过这个jcmd pid 具体命令 来显示指定进程的指令命令的数据。

可以先通过这个help命令查看有哪些命令可以执行

jcmd 1744 help

其执行结果如下

C:\Users\p'v>jcmd 1744 help
1744:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.run_finalization
GC.run
VM.uptime
VM.flags
VM.system_properties
VM.command_line
VM.version
help

再通过查出来的命令来查看对应命令的数据

// 打印线程信息
jcmd 1744 Thread.print
// 查看gc的内存信息
jcmd 1744 GC.class_histogram
// 生成GC的dump文件
jcmd 1744 GC.heap_dump d:\\a.hprof
...

猜你喜欢

转载自blog.csdn.net/zhenghuishengq/article/details/130641456