JVM

JVM性能调优工具

2020-08-23  本文已影响0人  今年五年级

\color{red}{以下的统计空间单位,未标明的,单位都是KB}

一、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字节

3.2 垃圾回收统计

jstat -gc 9749

3.3 堆内存统计

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

3.4 新生代垃圾回收统计

jstat -gcnew 9749

3.4 新生代内存统计

jstat -gcnewcapacity 9749

3.5 老年代垃圾回收统计

jstat -gcold 9749

3.6 老年代内存统计

jstat -gcoldcapacity 9749

3.7 元数据空间统计

jstat -gcmetacapacity 9749

3.8 总结垃圾回收统计

jstat -gcutil 4420

四、Jmap

此命令用来查看内存信息

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

jmap -histo 4420 > ./log.txt

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<Object> 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 <pid> ,显示你的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 文件里,值得关注的线程状态有:

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区的比值

上一篇 下一篇

猜你喜欢

热点阅读