JAVA内存溢出的常见原因及代码示例
一、堆溢出
堆是java程序中最为重要的内存空间,绝大部分的内存溢出都属于这种情况。其原因是因为大量对象占据了堆空间,而这些对象都持有强引用,导致无法回收。当对象大小之和大于有Xmx参数指定的堆空间大小时,就会发生堆溢出。
代码示例:
public class OOM {
public static void main(String[] args) {
//堆溢出
List<byte[]> list = new ArrayList<byte[]>();
for(int i =0;i<1024000;i++) {
list.add(new byte[1024*1024]);
}
}
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at springboot.jvm.OOM.main(OOM.java:16)
二、直接内存溢出
在Java的NIO中,支持直接内存的使用,可以直接获得一块堆以外的内存空间,这块空间是直接向操作系统申请的。直接内存的申请速度一般比堆内存要慢。
代码示例:
public class OOM {
public static void main(String[] args) {
//直接内存溢出,32位的机器可以试试,64位不会报错,-XX:+PrintGCDetails 可以查看内存回收情况
for(int i =0;i<1024;i++) {
ByteBuffer.allocate(1024*1024*1024);
System.out.println(i);
}
}
}
三、线程过多导致内存溢出
每一个线程的开启都需要占用系统内存。线程所需的栈空间也是在堆外分配的,如果想让系统支持更多的线程,可以减少堆空间。
代码示例:
public static void main(String[] args) {
//线程过多引起oom
for(int i =0;i<1024000;i++) {
new Thread(new SleepThread(),"Thread"+i).start();
System.out.println("Thread"+i+" created");
}
}
public static class SleepThread implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
Thread138280 created
Thread138281 created
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Unknown Source)
at springboot.jvm.OOM.main(OOM.java:27)
四、永久区溢出
永久区是存放类元数据的区域。如果一个系统定义了太多的类,那么永久区是有可能溢出的。在jdk1.8以后取消了永久区,取而代之的是元数据区域。
代码示例
public class OOM {
public static void main(String[] args) {
//永久区溢出,设置-XX:-UseGCOverheadLimit
URL url = null;
List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>();
try {
url = new File("/tmp").toURI().toURL();
URL[] urls = {url};
while (true){
ClassLoader loader = new URLClassLoader(urls);
classLoaderList.add(loader);
loader.loadClass("springboot.nio.NewioClient");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
因为JDK 1.8 中已经不存在永久代,所以显示的是Java heap space。换到JDK1.6中可以正常显示。
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.lang.ClassLoader.findLoadedClass0(Native Method)
at java.lang.ClassLoader.findLoadedClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
五、GC效率低下引起的OOM
虚拟机会评估GC的效率,一旦虚拟机认为GC的效率地下,就有可能抛出OOM异常。可以通过设置-XX:-UseGCOverheadLimit 来禁止这种OOM的产生
代码示例
public class OOM {
public static void main(String[] args) {
//GC效率低下
URL url = null;
List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>();
try {
url = new File("/tmp").toURI().toURL();
URL[] urls = {url};
while (true){
ClassLoader loader = new URLClassLoader(urls);
classLoaderList.add(loader);
loader.loadClass("springboot.nio.NewioClient");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
[Full GC (Ergonomics) [PSYoungGen: 38399K->0K(40960K)] [ParOldGen: 87410K->1918K(60928K)] 125810K->1918K(101888K), [Metaspace: 7724K->7724K(1056768K)], 0.0190998 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.net.URLClassLoader.<init>(Unknown Source)
at springboot.jvm.OOM.main(OOM.java:25)