实战 内存溢出定位与分析
环境搭建
/** * 模拟测试插入一百万条字符串[image.png](https://b3logfile.com/file/2019/08/image-dd10de62.png) str += UUID.randomUUID().toString(); } list.add(str); } System.out.println("ok"); } }
结果:
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid10272.hprof ... Heap dump file created [8323049 bytes in 0.028 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332) at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448) at java.lang.StringBuilder.append(StringBuilder.java:136) at cn.jeff.test.Test01.main(Test01.java:18) Process finished with exit code 1
参数中设置了输出 dump 文件,默认输出在项目根目录下。
问题分析(基于 MAT):
可以看出 87.93% 的内存被 Object[]数组占用了,通常情况下不会出现这么高的占用
再去看详细的数据情况:
可以看出在 Object[]数组中装满了刚才生产的 UUID,导致了最后的系统内存溢出问题。
分析线程执行情况:jstack
线程知识回顾
线程的六种状态
- 初始态(NEW)
创建一个 Thread 对象,但还未调用 start()启动线程时,线程处于初始态。 - 运行态(RUNNABLE),在 Java 中,运行态包括 就绪态 和 运行态。
2.1 就绪态 :
该状态下的线程已经获得执行所需的所有资源,只要 CPU 分配执行权就能运
行。
所有就绪态的线程存放在就绪队列中。
2.2 运行态 :
获得 CPU 执行权,正在执行的线程。
由于一个 CPU 同一时刻只能执行一条线程,因此每个 CPU 每个时刻只有一条
运行态的线程。 - 阻塞态(BLOCKED)
当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。
而在 Java 中,阻塞态专指请求锁失败时进入的状态。
由一个阻塞队列存放所有阻塞态的线程。
处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执
行。 - 等待态(WAITING)
当前线程中调用 wait、join、park 函数时,当前线程就会进入等待态。
也有一个等待队列存放所有等待态的线程。
线程处于等待态表示它需要等待其他线程的指示才能继续运行。
进入等待态的线程会释放 CPU 执行权,并释放资源(如:锁) - 超时等待态(TIMED_WAITING)
当运行中的线程调用 sleep(time)、wait、join、parkNanos、parkUntil 时,就
会进入该状态;
它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其
他线程唤醒;
进入该状态后释放 CPU 执行权 和 占有的资源。
与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。 - 终止态(TERMINATED)
线程执行结束后的状态。
实战:死锁问题
构建死锁环境
public class TestDeadLock { // 定义两个锁 private static Object obj1 = new Object(); private static Object obj2 = new Object(); public static void main(String[] args) { // 创建初始态线程 Thread thread1 = new Thread(new Thread1()); Thread thread2 = new Thread(new Thread2()); // 就绪态 thread1.start(); thread2.start(); } // 第一个线程 private static class Thread1 implements Runnable { @Override public void run() { synchronized (obj1) { // 此时得到了obj1这个锁 System.out.println("Thread1得到了obj1这把锁!"); try { // 停下来休息两秒 为了让Thread2得到obj2这把锁 造成死锁 sleep方法是不会释放锁的 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() { // 得到obj2这把锁 synchronized (obj2) { System.out.println("Thread2得到了obj2这把锁!"); try { // 为了让obj1被先得到 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // 得到obj1 synchronized (obj1) { System.out.println("Thread2得到了obj1这把锁!"); } } } } }
结果
[root@hadoop101 jvm]# javac TestDeadLock.java [root@hadoop101 jvm]# java TestDeadLock Thread1得到了obj1这把锁! Thread2得到了obj2这把锁! # 程序卡在这里
保持程序的运行状态,另外创建一个 xshell 命令行窗口
# 通过jps找到运行的TestDeadLock程序的端口 [root@hadoop101 ~]# jps 2956 TestDeadLock 3038 Jps 1967 Bootstrap # 指令格式: jstack 端口号 [root@hadoop101 ~]# jstack 2956 2019-08-16 02:43:12 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.111-b14 mixed mode): "Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007ffa54001000 nid=0xbf4 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007ffa7c008800 nid=0xb8d waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Thread-1" #9 prio=5 os_prio=0 tid=0x00007ffa7c0ca800 nid=0xb97 waiting for monitor entry [0x00007ffa6c51d000] java.lang.Thread.State: BLOCKED (on object monitor) at TestDeadLock$Thread2.run(TestDeadLock.java:52) - waiting to lock <0x00000000e345be78> (a java.lang.Object) - locked <0x00000000e345be88> (a java.lang.Object) at java.lang.Thread.run(Thread.java:745) "Thread-0" #8 prio=5 os_prio=0 tid=0x00007ffa7c0c9000 nid=0xb96 waiting for monitor entry [0x00007ffa6c61e000] java.lang.Thread.State: BLOCKED (on object monitor) at TestDeadLock$Thread1.run(TestDeadLock.java:31) - waiting to lock <0x00000000e345be88> (a java.lang.Object) - locked <0x00000000e345be78> (a java.lang.Object) at java.lang.Thread.run(Thread.java:745) "Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007ffa7c0b3000 nid=0xb94 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007ffa7c0b0000 nid=0xb93 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007ffa7c0ad800 nid=0xb92 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007ffa7c0ac000 nid=0xb91 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007ffa7c079000 nid=0xb90 in Object.wait() [0x00007ffa6cc24000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000e3408e98> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143) - locked <0x00000000e3408e98> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209) "Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007ffa7c074800 nid=0xb8f in Object.wait() [0x00007ffa6cd25000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000e3406b40> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked <0x00000000e3406b40> (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153) "VM Thread" os_prio=0 tid=0x00007ffa7c06d000 nid=0xb8e runnable "VM Periodic Task Thread" os_prio=0 tid=0x00007ffa7c0b6000 nid=0xb95 waiting on condition JNI global references: 6 Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x00007ffa600062c8 (object 0x00000000e345be78, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x00007ffa60004e28 (object 0x00000000e345be88, a java.lang.Object), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at TestDeadLock$Thread2.run(TestDeadLock.java:52) - waiting to lock <0x00000000e345be78> (a java.lang.Object) - locked <0x00000000e345be88> (a java.lang.Object) at java.lang.Thread.run(Thread.java:745) "Thread-0": at TestDeadLock$Thread1.run(TestDeadLock.java:31) - waiting to lock <0x00000000e345be88> (a java.lang.Object) - locked <0x00000000e345be78> (a java.lang.Object) at java.lang.Thread.run(Thread.java:745) # 系统提示发现一个死锁 Found 1 deadlock.
死锁分析:
"Thread-1" #9 prio=5 os_prio=0 tid=0x00007ffa7c0ca800 nid=0xb97 waiting for monitor entry [0x00007ffa6c51d000] java.lang.Thread.State: BLOCKED (on object monitor) at TestDeadLock$Thread2.run(TestDeadLock.java:52) - waiting to lock <0x00000000e345be78> (a java.lang.Object) - locked <0x00000000e345be88> (a java.lang.Object) at java.lang.Thread.run(Thread.java:745) "Thread-0" #8 prio=5 os_prio=0 tid=0x00007ffa7c0c9000 nid=0xb96 waiting for monitor entry [0x00007ffa6c61e000] java.lang.Thread.State: BLOCKED (on object monitor) at TestDeadLock$Thread1.run(TestDeadLock.java:31) - waiting to lock <0x00000000e345be88> (a java.lang.Object) - locked <0x00000000e345be78> (a java.lang.Object) at java.lang.Thread.run(Thread.java:745)
可以看出:
Thread-1:正在手握着 0x00000000e345be88 这个锁,等待着 0x00000000e345be78 的获取;
Thread-0:正在手握着 0x00000000e345be78 这个锁,等待着 0x00000000e345be88 的获取;
VisualVM 工具的使用
简介
VisualVM,能够监控线程,内存情况,查看方法的 CPU 时间和内存中的对 象,已被 GC 的对象,反向查看分配的堆栈(如 100 个 String 对象分别由哪几个对象分配出来的)。
VisualVM 使用简单,几乎 0 配置,功能还是比较丰富的,几乎囊括了其它 JDK 自带命令的所有功能。
1、内存信息
2、线程信息
3、Dump 堆(本地进程)
4、Dump 线程(本地进程)
5、打开堆 Dump。堆 Dump 可以用 jmap 来生成。
6、打开线程 Dump
7、生成应用快照(包含内存信息、线程信息等等)
8、性能分析。
9、CPU 分析(各个方法调用时间,检查哪些方法耗时多)
10、内存分析(各类对象占用的内存,检查哪些类占用内存多
使用方法
启动
VisualVM 在 jdk 的安装目录的 bin 下面有 jvisualvm.exe,打开便可使用。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于