JVM性能调优工具

一、Jps

查看当前正在运行的java程序,比如我们在这里启动自己的项目以后使用该命令

二、Jinfo

查看正在运行的java应用程序的扩展参数。从上一步获取到了我们自己的程序的进程id为9749,然后使用jinfo查看该程序的扩展参数

2.1 查看jvm参数

jinfo -flags 9749

2.2 查看系统参数

jinfo -sysprops 9749

三、Jstat

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

3.1 类加载统计

jstat -class 9749

可以看到加载了13619个类,这些类的总大小为25286.0字节

  • Loaded:加载class的数量
  • Bytes:所占用空间大小
  • Unloaded:未加载数量
  • Bytes:未加载占用空间
  • Time:时间

3.2 垃圾回收统计

jstat -gc 9749

  • S0C:survivor区中的from区配置大小,jvm中为s0区,capacity 容量 S0C=S0 capacity

  • S1C:survivor区中的to区配置大小,jvm中为s1区

  • S0U:survivor区中的from区使用大小

  • S1U:survivor区中的to区使用大小

  • EC:伊甸园eden区的大小

  • EU:伊甸园区的使用大小

  • OC:老年代大小

  • OU:老年代使用大小

  • MC:方法区大小(元空间)

  • MU:方法区使用大小

  • CCSC:压缩类空间大小 Compressed class space capacity https://www.zhihu.com/question/268392125

  • CCSU:压缩类空间使用大小

  • YGC:年轻代垃圾回收次数

  • YGCT:年轻代垃圾回收消耗时间,已收集次数累计时间

  • FGC:老年代垃圾回收次数

  • FGCT:老年代垃圾回收消耗时间

  • GCT:垃圾回收消耗总时间 75.807+0.991=76.798

3.3 堆内存统计

和上面的结果会有一些重复的参数,但是同样有他自己的参数
jstat -gccapacity 4420

  • NGCMN:新生代最小容量 new generation capacity min

  • NGCMX:新生代最大容量 new generation capacity max

  • NGC:当前新生代容量

  • S0C:survivor中from区容量

  • S1C:survivor中to区容量

  • EC:eden区容量

  • OGCMN:老年代最小容量 old generation capacity min

  • OGCMX:老年代最大容量

  • OGC:当前老年代大小

  • OC:当前老年代大小

  • MCMN:最小元数据容量 metaspace capacity min 最小元空间容量

  • MCMX:最大元数据容量

  • MC:当前元数据空间大小

  • CCSMN:最小压缩类空间大小

  • CCSMX:最大压缩类空间大小

  • CCSC:当前压缩类空间大小

  • YGC:年轻代gc次数

  • FGC:老年代GC次数

3.4 新生代垃圾回收统计

jstat -gcnew 9749

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

3.4 新生代内存统计

jstat -gcnewcapacity 9749

  • NGCMN:新生代最小容量

  • NGCMX:新生代最大容量

  • NGC:当前新生代容量

  • S0CMX:最大幸存1区大小

  • S0C:当前幸存1区大小

  • S1CMX:最大幸存2区大小

  • S1C:当前幸存2区大小

  • ECMX:最大伊甸园区大小

  • EC:当前伊甸园区大小

  • YGC:年轻代垃圾回收次数

  • FGC:老年代回收次数

3.5 老年代垃圾回收统计

jstat -gcold 9749

  • MC:方法区大小

  • MU:方法区使用大小

  • CCSC:压缩类空间大小

  • CCSU:压缩类空间使用大小

  • OC:老年代大小

  • OU:老年代使用大小

  • YGC:年轻代垃圾回收次数

  • FGC:老年代垃圾回收次数

  • FGCT:老年代垃圾回收消耗时间

  • GCT:垃圾回收消耗总时间

3.6 老年代内存统计

jstat -gcoldcapacity 9749

  • OGCMN:老年代最小容量

  • OGCMX:老年代最大容量

  • OGC:当前老年代大小

  • OC:老年代大小

  • YGC:年轻代垃圾回收次数

  • FGC:老年代垃圾回收次数

  • FGCT:老年代垃圾回收消耗时间

  • GCT:垃圾回收消耗总时间

3.7 元数据空间统计

jstat -gcmetacapacity 9749

  • MCMN:最小元数据容量 meta capacity min

  • MCMX:最大元数据容量

  • MC:当前元数据空间大小

  • CCSMN:最小压缩类空间大小

  • CCSMX:最大压缩类空间大小

  • CCSC:当前压缩类空间大小

  • YGC:年轻代垃圾回收次数

  • FGC:老年代垃圾回收次数

  • FGCT:老年代垃圾回收消耗时间

  • GCT:垃圾回收消耗总时间

3.8 总结垃圾回收统计

jstat -gcutil 4420

  • S0:幸存1区当前使用比例

  • S1:幸存2区当前使用比例

  • E:伊甸园区使用比例

  • O:老年代使用比例

  • M:元数据区使用比例

  • CCS:压缩类使用比例

  • YGC:年轻代垃圾回收次数

  • FGC:老年代垃圾回收次数

  • FGCT:老年代垃圾回收消耗时间

  • GCT:垃圾回收消耗总时间

四、Jmap

此命令用来查看内存信息

4.1 实例个数以及内存占用大小(会导致停顿,生产环境慎用)

jmap -histo 4420 > ./log.txt

  • num:序号
  • instances:实例数量
  • bytes:占用空间大小
  • class name:类名称

4.2 堆信息

jmap -heap 9749

4.3 堆内存dump文件

4.3.1 手动导出堆内存dump文件

可以导出导出那一刻的程序堆运行内存dump文件,描述了那一刻的程序堆内存详情(和那一刻线程的快照),我们一般使用dump主要是查内存的使用情况

当然也可以用jvisualvm导出堆dump/线程dump

jmap -dump:format=b,file=radient.hprof 2964

该dump文件我们可以直接加载到jvisualvm中进行查看

在类选项中点进去任何一个类,还可以查看每个类实例的详细信息,比如值

4.3.2 自动导出堆内存dump文件

我们可以设置当oom内存溢出时候自动导出dump文件(内存占用比较大的时候,可能会导不出来),下面我们使用一个能导致oom的案例程序演示如何自动导出dump

// 因为新建出来的对象一直在list中,一直在被引用没有被回收,因此堆内存迟早有满了的一天
public class Test {
    public static void main(String[] args) {
        List list = new ArrayList<>();
        int i =0;
        for(;;){
            list.add(new R(i++,UUID.randomUUID().toString(),null));
        }
    }
}
 
 

配置jvm启动参数(为了更快oom,设置最小最大运行堆内存10M,输出GC日志,oom时候自动导出dump文件)

-Xms10M -Xmx10M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\develop\jvm.dump

上述程序的运行结果如下,其中黄色字体下面的就是GCdetail参数打印出来的各个内存空间使用情况

另外,虽然我们自动导出的dump文件后缀名和之前的手动导出的dump文件后缀名不一样,但是其实是一个东西都是我们程序运行的那一刻的内存分配的快照,然后我们将上述dump文件导入到jvisualvm中进行分析

有内存溢出情况,实质上是有一些异常的类

我们可以看到R类和integer,string类 ,都是new R的时候产生的,占据内存最大的是char字符数组,5m,接近一半的空间,这是为什么呢?

从string类点进去,查看value会继续进入到char数组,可以查看到每个R的msg内容即UUID,我们知道new 一个string,实际是用char数组存储的在string类内部

五、Jstack

我们下面将采用jstack来分析死锁程序,下面是死锁示例:

public class Test2 {

    private static Object o1= new Object();
    private static Object o2= new Object();

    public static void main(String[] args) {
        Thread t1= new Thread(() -> {
            synchronized (o1) {
                try {
                    System.out.println("线程1开始");
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("线程1结束");
                }
            }
        });

        Thread t2= new Thread(() -> {
            synchronized (o2) {
                try {
                    System.out.println("线程2开始");
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println("线程2结束");
                }
            }
        });

        t1.start();
        t2.start();
        System.out.println("主线程提前结束,但是进程并未退出(由于t1和t2线程导致进程中出现死锁)");
    }
}


用jstack查看死锁的命令
jstack 16900 > deadlock.txt

可以看到上面是一些线程信息,在最底下输出了发现java层次的死锁的信息

从图上我们可以一目了然的发现两个线程处于等待对方持有的锁的阻塞状态

下面我们使用jvisualvm来查看死锁,可以看到自动为我们分析出了死锁

jstack找出占用cpu最高的堆栈信息
1,使用命令top -p ,显示你的java进程的内存情况,pid是你的java进程号,比如9749

2,按H,获取每个线程的内存情况

3,找到内存和cpu占用最高的线程tid,比如4977

4,转为十六进制得到 0x1371 ,此为线程id的十六进制表示

5,执行 jstack 9749|grep 1371 -A 120 ,得到线程堆栈信息中1371这个线程所在行的后面10行

6,查看对应的堆栈信息找出可能存在问题的代码

5.2 Jstack Dump 日志文件中的线程状态

dump 文件里,值得关注的线程状态有:

  • 死锁,Deadlock(重点关注)
    死锁线程,一般指多个线程调用间,进入相互资源占用,导致一直等待无法释放的情况
  • 执行中,Runnable
    一般指该线程正在执行状态中,该线程占用了资源,正在处理某个请求,有可能正在传递SQL到数据库执行,有可能在对某个文件操作,有可能进行数据类型等转换
  • 等待资源,Waiting on condition(重点关注)
    等待资源,或等待某个条件的发生。具体原因需结合 stacktrace来分析
    1. 如果堆栈信息明确是应用代码,则证明该线程正在等待资源。一般是大量读取某资源,且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取。
    2. 又或者,正在等待其他线程的执行等。
    3. 如果发现有大量的线程都在处在 Wait on condition,从线程 stack看,正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。
      3.1 一种情况是网络非常忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写;
      3.2 另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。
    4. 另外一种出现 Wait on condition的常见情况是该线程在 sleep,等待 sleep的时间到了时候,将被唤醒。
  • 等待获取监视器,Waiting on monitor entry(重点关注)
  • 暂停,Suspended
  • 对象等待中,Object.wait() 或 TIMED_WAITING
  • 阻塞,Blocked(重点关注)
  • 停止,Parked

Waiting for monitor entry 和 in Object.wait():Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。从下图1中可以看出,每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程状态是 “in Object.wait()”

A Java Monitor
"http-nio-7901-exec-11" 200 daemon prio=5 os_prio=0 tid=0x00007fe45c001800 nid=0x473e waiting on condition [0x00007fe48cbed000]
   java.lang.Thread.State: TIMED_WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x000000070e3924c8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
    at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467)
    at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:89)
    at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:33)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

detail

六、远程连接jvisualvm

http://blog.sina.com.cn/s/blog_92e2ec4f0100vz35.html
https://blog.csdn.net/qq_39575279/article/details/105403597
注意:Djava.rmi.server.hostname为本机的ip地址,如果为云服务器,则为云服务器的公网ip

七、-Xms、-Xmn参数的含义:

答:
堆内存分配:
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64
JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4
默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。
因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。

非堆内存分配:
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;
由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
-Xmn2G:设置年轻代大小为2G。
-XX:SurvivorRatio,设置年轻代中Eden区与Survivor区的比值

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