JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析

1 实战:内存溢出的定位与分析

1.1 内存溢出与内存泄露

  • 内存溢出,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;
  • 内存泄露,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

1.2 模拟内存溢出

编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程序能够正常执行,最后打印ok。

public class TestJvmOutOfMemory {

    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            String str = "";
            for (int j = 0; j < 1000; j++) {
                str += UUID.randomUUID().toString();
            }
            list.add(str);
        }
        System.out.println("ok");
    }
}

设置参数:
#参数如下:
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
#指定路径
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=F:\t\dump.hprof
#手动dump内存
#用法:
jmap -dump:format=b,file=dumpFileName
#示例
jmap -dump:format=b,file=/tmp/dump.dat 6219

1.3 运行测试

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid14768.hprof …
Heap dump file created [8127578 bytes in 0.026 secs]
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

可以看到,当发生内存溢出时,会dump内存到java_pid14768.hprof文件中,该文件在项目的根目录下。

1.4 导入到MAT工具中进行分析

1.4.1 MAT工具介绍

MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。
官网地址:https://www.eclipse.org/mat/

1.4.2 下载安装

下载地址:https://www.eclipse.org/mat/downloads.php

1.4.3 导入分析

JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第1张图片
JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第2张图片
查看对象以及它的依赖:
JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第3张图片
查看可能存在内存泄露的分析:
JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第4张图片
JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第5张图片
可以看到,有91.02%的内存由Object[]数组占有,所以比较可疑。
查看详情:
JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第6张图片
可以看到集合中存储了大量的uuid字符串。

2 实战:死锁问题

有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了、出现了死锁、死循环等,我们该如何分析呢?
由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要看下jvm的内部线程的执行情况,然后再进行分析查找出原因。
这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程 情况进行快照,并且打印出来:
#用法:jstack

2.1 构造死锁

编写代码,启动2个线程,Thread1拿到了obj1锁,准备去拿obj2锁时,obj2已经被Thread2锁定,所以发生了死锁。

public class TestDeadLock {

    private static Object obj1 = new Object();

    private static Object obj2 = new Object();


    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }

    private static class Thread1 implements Runnable{
        @Override
        public void run() {
            synchronized (obj1){
                System.out.println("Thread1 拿到了 obj1 的锁!");

                try {
                    // 停顿2秒的意义在于,让Thread2线程拿到obj2的锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj2){
                    System.out.println("Thread1 拿到了 obj2 的锁!");
                }
            }
        }
    }

    private static class Thread2 implements Runnable{
        @Override
        public void run() {
            synchronized (obj2){
                System.out.println("Thread2 拿到了 obj2 的锁!");

                try {
                    // 停顿2秒的意义在于,让Thread1线程拿到obj1的锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj1){
                    System.out.println("Thread2 拿到了 obj1 的锁!");
                }
            }
        }
    }
}

补充知识点:产生死锁的条件

  • 互斥条件:同一时间一个资源只能被一个任务使用
  • 请求与保持条件:T1持有S1的同时,请求S2资源,但是不能立即获得并持续等待(T表示任务,S表示资源)
  • 不可剥夺条件:T1持有的资源无法被T2剥夺
  • 循环等待条件:若干进程之间形成一种头尾相连的循环等待条件(T1拥有S1请求S2,同时T2拥有S2请求S1)

2.2 运行并查询进程编号

JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第7张图片
可以看到,程序已经卡在这里了,不在继续往下执行。
查看进程ID,jps -l

2.3 查看线程状态

jstack 12652 #查看进程中的线程状态

在输出的信息中,已经看到,发现了1个死锁,关键看这个:

Found one Java-level deadlock:
=============================
“Thread-1”:
waiting to lock monitor 0x00000000176636f8 (object 0x00000000d749d958, a java.lang.Object),
which is held by “Thread-0”
“Thread-0”:
waiting to lock monitor 0x0000000017662258 (object 0x00000000d749d968, a java.lang.Object),
which is held by “Thread-1”
Java stack information for the threads listed above:
===================================================
“Thread-1”:
at cn.itcast.jvm.TestDeadLock T h r e a d 2. r u n ( T e s t D e a d L o c k . j a v a : 49 ) − w a i t i n g t o l o c k < 0 x 00000000 d 749 d 958 > ( a j a v a . l a n g . O b j e c t ) − l o c k e d < 0 x 00000000 d 749 d 968 > ( a j a v a . l a n g . O b j e c t ) a t j a v a . l a n g . T h r e a d . r u n ( T h r e a d . j a v a : 748 ) " T h r e a d − 0 " : a t c n . i t c a s t . j v m . T e s t D e a d L o c k Thread2.run(TestDeadLock.java:49) - waiting to lock <0x00000000d749d958> (a java.lang.Object) - locked <0x00000000d749d968> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748) "Thread-0": at cn.itcast.jvm.TestDeadLock Thread2.run(TestDeadLock.java:49)waitingtolock<0x00000000d749d958>(ajava.lang.Object)locked<0x00000000d749d968>(ajava.lang.Object)atjava.lang.Thread.run(Thread.java:748)"Thread0":atcn.itcast.jvm.TestDeadLockThread1.run(TestDeadLock.java:29)
- waiting to lock <0x00000000d749d968> (a java.lang.Object)
- locked <0x00000000d749d958> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.

可以清晰的看到:

  • Thread2获取了 <0x00000000d749d968> 的锁,等待获取<0x00000000d749d958> 这个锁
  • Thread1获取了 <0x00000000d749d958> 的锁,等待获取 <0x00000000d749d968> 这个锁
  • 由此可见,发生了死锁。

3 VisualVM

3.1 基本使用

3.1.1 启动

在jdk的安装目录的bin目录下,找到jvisualvm.exe,双击打开即可。
JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第8张图片

3.1.2 查看本地进程

JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第9张图片

3.1.3 查看CPU、内存、类、线程运行信息

JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第10张图片

3.1.4 查看线程详情

JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第11张图片

3.1.5 抽样器

抽样器可以对CPU、内存在一段时间内进行抽样,以供分析。
JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第12张图片

3.2 监控远程的JVM

VisualJVM不仅是可以监控本地jvm进程,还可以监控远程的jvm进程,需要借助于JMX技术实现。

3.2.1 什么是JMX?

JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。

3.2.2 监控远程的tomcat

想要监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,方法如下:

#在tomcat的bin目录下,修改catalina.sh,添加如下的参数
JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false""
#这几个参数的意思是:
#-Dcom.sun.management.jmxremote :允许使用JMX远程管理
#-Dcom.sun.management.jmxremote.port=9999 :JMX远程连接端口
#-Dcom.sun.management.jmxremote.authenticate=false :不进行身份认证,任何用户都可以连接
#-Dcom.sun.management.jmxremote.ssl=false :不使用ssl

3.2.3 使用VisualJVM连接远程tomcat

3.3 检测死锁

JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第13张图片

3.4 检测堆内存

检测堆内存的具体使用情况,需要安装插件Visual GC进行检测:
JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第14张图片

3.4.1 编写代码

    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        while (true){
            User user = new User();
            user.setId(1L);
            user.setUsername("user");
            user.setPassword("pass");
            if(System.currentTimeMillis() % 2 ==0 ){
                userList.add(user);
                System.out.println("add to list, size = " + userList.size());
            }
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3.4.2 运行测试

运行参数:-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
JVM虚拟机调优指导(二)——内存溢出及死锁问题的定位与分析_第15张图片
可以看到,年轻代、老年代中的内存使用情况。

你可能感兴趣的:(学习笔记系列)