这是最常见的OOM错误,当通过new创建对象或数组时,如Java Heap空间不足(新生代不足,触发Young GC,还是不够,触发Full GC,还是不够),则抛出此错误。
既然最常见,更要注意避免。让我们看几个出现这种OOM的示例:
1. 先看code
new Thread ( new Runnable ( ) {
public void run ( ) {
List <byte [ ] > listbytes = new ArrayList <byte [ ] > ( ) ;
for ( int i = 0 ; i < 4028 ; i ++ ) {
System. out. println ( "thread a: " +i ) ;
listbytes. add ( new byte [ 1024 ] ) ;
}
}
} ). start ( ) ;
}
以 -Xms20m -Xmx20m -Xmn10m -XX:+UseParallelGC -XX:+ HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -Xloggc:gc.log 参数运行
运行后在输出的日志中可看到大量的Full GC信息,以及:java.lang.OutOfMemoryError: Java heap space
先看看20M的堆内存,怎么分配的:
Heap
PSYoungGen total 8960K
eden space 7680K
from space 1280K
to space 1280K
PSOldGen total 10240K
object space 10240K
PSPermGen total 12288K
object space 12288K
可以看到:
PSYoungGen(新生代)中,eden+from+to=10240K
total 8960K是因为新生代内存from和to两个space不可能同时使用,所以7680k+1280K=8960K
PSOldGen(老生代),10240K
至于PSPermGen,无视他吧,丫不属于GC管理的堆内存。
再观察一下gc.log中gc的情况,在0.1s左右做了一次Young GC之后,后边疯狂的Full GC(好吧,还不是非常疯狂,否则最后报错就不是Java heap space了,这个以后再说)。注意在Full GC的过程中,PSOldGen一直在上涨,因为“可达”,GC对PSOldGen的清理微不足道。一直上涨,也必然造成内存不足、泄露。
前边我们已经知道这种OOM是在new对象或数组时Java Heap Space不足造成的,现在需要知道的是程序中哪些部分占用了Java Heap。
通过mat来分析程序运行生成的hprof文件。通过dominator_tree,可看到sun.misc.Launcher$AppClassLoader占据了大部分的内存。继续点开,发现根源在一个ArrayList,其中存放的对象占据了大部分的内存:
怎样避免:
这种情况造成的OOM,在实际的场景中当使用集合时很容易产生,程序中对于所有集合(List、Map等等)、缓存(本地Cache)大小都应给定限制的最大大小,以避免出现集合或缓存太大,导致消耗了过多的内存,从而导致OOM。
2. 再来一段代码先
for ( int i = 0 ; i < 10 ; i ++ ) {
new Thread ( new Runnable ( ) {
public void run ( ) {
byte [ ] bytes = new byte [ 1024 * 1024 * 2 ] ;
System. out. println ( Thread. currentThread ( ). getName ( ) ) ;
try {
Thread. sleep ( 1000 ) ;
} catch ( InterruptedException e ) {
e. printStackTrace ( ) ;
}
}
} ). start ( ) ;
}
}
同样,以-Xms20m -Xmx20m -Xmn10m -XX:+UseParallelGC -XX:+ HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -Xloggc:gc.log参数运行
把生成的hprof文件放入mat中进行分析,通过dominator_tree,可以看到,是一堆线程瓜分了内存:
在这个例子中,每个线程分配 2M(1024*1024*2)的内存,并且,占用1s钟不释放。与此同时,其他线程又在马不停蹄的申请内存…所以,内存溢出了。
怎样避免:
这种情况主要出现在多线程系统中,并且每个线程占用内存比较大或者处理速度较慢。
有四种方法来解决这个问题:
- 减少处理的线程数
- 处理线程需要消耗的内存
- 提升线程的处理速度
- 增加机器,减少单台机器所需承担的请求量