springboot项目跑了一段时间就挂了,返回如下错误
[error occurred during error reporting (null), id 0xc0000005]
There is insufficient memory for the Java Runtime Environment to continue.
Native memory allocation (malloc) failed to allocate 32744 bytes for ChunkPool::allocate
An error report file with more information is saved as:
C:\Users\Administrator\Desktop\release\release\hs_err_pid10184.log
定位过程:
找到C:\Users\Administrator\Desktop\release\release\hs_err_pid10184.log文件,并打开,里面记录的报错当时所有的状态,下面作以分析
日志结点目录
--------------- T H R E A D ---------------
--------------- P R O C E S S ---------------
Java Threads: ( => current thread )
Other Threads:
Heap:
GC Heap History (10 events):
Deoptimization events (10 events):
Classes redefined (0 events):
Internal exceptions (10 events):
Events (10 events):
Dynamic libraries:
VM Arguments:
Environment Variables:
--------------- S Y S T E M ---------------
解读
从上面可以列出的日志目录看出,所打印的信息还是蛮全面的,如果你了解他们,那么对于你定位问题是非常轻松的。
这里我把对我们有帮助的结点挑出来,重点说明,大家可以对比着看
Java Threads: ( => current thread )
这个是我要介绍的重点,这里列出了boot项目从启动到结束所有创建的线程堆栈,因为是堆栈,我们当然应该从上往下去看,因为最新的线程在最上面。
Java Threads: ( => current thread )
0x00000006002c7000 JavaThread "JS executor for com.gargoylesoftware.htmlunit.WebClient@15a54c9b" daemon [_thread_blocked, id=40636, stack(0x000000063c800000,0x000000063c900000)]
0x000000060048d000 JavaThread "pool-769-thread-30" [_thread_blocked, id=88376, stack(0x0000000639a00000,0x0000000639b00000)]
0x000000060048c000 JavaThread "pool-769-thread-29" [_thread_in_native, id=20476, stack(0x0000000639800000,0x0000000639900000)]
0x0000000600481800 JavaThread "pool-769-thread-28" [_thread_blocked, id=73288, stack(0x0000000639600000,0x0000000639700000)]
0x0000000600484800 JavaThread "pool-769-thread-27" [_thread_in_native, id=96240, stack(0x0000000639400000,0x0000000639500000)]
0x0000000600484000 JavaThread "pool-769-thread-26" [_thread_blocked, id=68776, stack(0x0000000639200000,0x0000000639300000)]
0x0000000600487000 JavaThread "pool-769-thread-25" [_thread_blocked, id=22364, stack(0x0000000639000000,0x0000000639100000)]
0x0000000600481000 JavaThread "pool-769-thread-24" [_thread_blocked, id=10776, stack(0x0000000638e00000,0x0000000638f00000)]
...
所以大家在创建线程的时候一定要养成一个好的习惯,顺便给线程起个线程名,这样在定位的时候一眼就能看出来是哪个线程导致的。
我们都知道(也许不知道),当用普通方式创建大量线程时会消耗大量的资源,包括内存资源和计算资源,因此,如果定位时这里的原因导致的,我建议用线程池去替代,可以解决。使用Executor框架创建线程
Heap:
SUN JVM GC 使用是分代收集算法,即将内存分为几个区域,将不同生命周期的对象放在不同区域里.
新的对象会先生成在Young area,也就是PSYoungGen中
在几次GC以后,如过没有收集到,就会逐渐升级到PSOldGen 及Tenured area(也就是PSPermGen)中。
三者区别:
在GC收集的时候,频繁收集生命周期短的区域(Young area),因为这个区域内的对象生命周期比较短,GC 效率也会比较高。而比较少的收集生命周期比较长的区域(Old area or Tenured area),以及基本不收集的永久区(Perm area)。
PSYoungGen total 43008K, used 5605K [0x00000000f5d80000, 0x00000000f9700000, 0x0000000100000000)
eden space 33792K, 0% used [0x00000000f5d80000,0x00000000f5da5e78,0x00000000f7e80000)
from space 9216K, 59% used [0x00000000f7e80000,0x00000000f83d3630,0x00000000f8780000)
to space 9216K, 0% used [0x00000000f8e00000,0x00000000f8e00000,0x00000000f9700000)
ParOldGen total 166912K, used 157860K [0x00000000e1800000, 0x00000000ebb00000, 0x00000000f5d80000)
object space 166912K, 94% used [0x00000000e1800000,0x00000000eb2292f8,0x00000000ebb00000)
Metaspace used 119653K, capacity 124695K, committed 124976K, reserved 1157120K
class space used 16430K, capacity 17407K, committed 17456K, reserved 1048576K
GC Heap History (10 events):
这个结点是JVM回收空间的历史记录,可以和Heap对比着看,
Event: 136628.565 GC heap before
{Heap before GC invocations=1400 (full 11):
PSYoungGen total 33792K, used 29917K [0x00000000f5d80000, 0x00000000f8c80000, 0x0000000100000000)
eden space 24064K, 100% used [0x00000000f5d80000,0x00000000f7500000,0x00000000f7500000)
from space 9728K, 60% used [0x00000000f7e80000,0x00000000f8437640,0x00000000f8800000)
to space 9728K, 0% used [0x00000000f7500000,0x00000000f7500000,0x00000000f7e80000)
ParOldGen total 166912K, used 157159K [0x00000000e1800000, 0x00000000ebb00000, 0x00000000f5d80000)
object space 166912K, 94% used [0x00000000e1800000,0x00000000eb179d88,0x00000000ebb00000)
Metaspace used 119651K, capacity 124695K, committed 124976K, reserved 1157120K
class space used 16430K, capacity 17407K, committed 17456K, reserved 1048576K
Event: 136628.627 GC heap after
Heap after GC invocations=1400 (full 11):
PSYoungGen total 33792K, used 4093K [0x00000000f5d80000, 0x00000000f8b80000, 0x0000000100000000)
eden space 24064K, 0% used [0x00000000f5d80000,0x00000000f5d80000,0x00000000f7500000)
from space 9728K, 42% used [0x00000000f7500000,0x00000000f78ff690,0x00000000f7e80000)
to space 9216K, 0% used [0x00000000f8280000,0x00000000f8280000,0x00000000f8b80000)
ParOldGen total 166912K, used 157327K [0x00000000e1800000, 0x00000000ebb00000, 0x00000000f5d80000)
object space 166912K, 94% used [0x00000000e1800000,0x00000000eb1a3ec8,0x00000000ebb00000)
Metaspace used 119651K, capacity 124695K, committed 124976K, reserved 1157120K
class space used 16430K, capacity 17407K, committed 17456K, reserved 1048576K
}
问题解决
上面扯了这么多,主要是想说明整个分析过程,最终入手还是得看代码,这里我写一段伪代码来说明,我为啥会出现这个问题。
@Scheduled(fixedRate = 1000 * 60 * 3) //3分钟
public RestResult
ExecutorService executor = Executors.newFixedThreadPool(10);
for (...){
executor.execute(new Runnable() {
@Override
public void run() {
...
}
});
Thread thread = new Thread();
thread.start();
}
return restResult;
}
从这里可以看到,我在这个定时任务里面创建了个线程池,大小为10,但是这个方法执行结束后,这个线程池并没有释放,有可能还在执行任务,这样过三分钟之后,又会去创建新的线程池,我们都知道创建线程池需要消耗计算机资源,当资源耗尽之后,JVM就会报错,申请内存空间失败。
通过查看资料,其实正确的线程池应该是定义成为一个全局静态的(或交由spring管理线程池),这样就能保证线程池的复用,而且减少了线程池创建过程中资源的消耗。