小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
泄漏与溢出
- 内存泄露Memory Leak: 申请的内存空间没有及时释放,导致后续程序里这块内容永远被占用。
- 内存溢出Out Of Memory: 要求的内存超过了系统所能提供的,内存需求不能被满足。
一般情况下大家可能都感受不到内存泄漏的存在,真正有危害的是内存泄漏的堆积,这会最终消耗尽系统的所有内存,导致内存溢出。
运行时数据区域的常见异常
在JVM中,除了程序计数器外,虚拟机内存的其他几个运行时数据区域都有发生OOM异常的可能。
堆内存溢出
不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象。
public class HeapOOM {
static class ObjectInHeap{
}
public static void main(String[] args) {
List<ObjectInHeap> list = new ArrayList();
while (true) {
list.add(new ObjectInHeap());
}
}
}
虚拟机栈和本地方法栈溢出
单个线程下不断扩大栈的深度引起栈溢出。
public class StackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
StackSOF sof = new StackSOF();
try {
sof.stackLeak();
} catch (Throwable e) {
System.out.println("Stack Length: " + sof.stackLength);
throw e;
}
}
}
循环的创建线程,达到最大栈容量。
创建线程导致内存溢出 (注意:该代码创建线程是映射到操作系统内核线程,请不要尝试运行!!!)
public class StackOOM {
private void dontStop() {
while (true) {
}
}
public void stackLeadByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
StackOOM stackOOM = new StackOOM();
stackOOM.stackLeadByThread();
}
}
运行时常量池溢出
不断的在常量池中新建String,并且保持引用不释放。
运行时常量池溢出 java.lang.OutOfMemoryError: Java heap space(JDK6及以前将是 java.lang.OutOfMemoryError: PermGen space)
public class MethodAreaOOM {
static class ObjectInMethod {
}
public static void main(final String[] args) {
// 借助CGLib实现
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ObjectInMethod.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
});
enhancer.create();
}
}
}
// *注:默认情况报GC overhead limit exceeded,通过-XX:-UseGCOverheadLimit参数禁用保护机制看到实际错误信息
方法区溢出(JDK7及以前)
代码1 借助CGLib直接操作字节码运行时产生大量的动态类,最终撑爆内存导致方法区溢出。
方法区溢出 java.lang.OutOfMemoryError: PermGen space
public class MethodAreaOOM {
static class ObjectInMethod {
}
public static void main(final String[] args) {
// 借助CGLib实现
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ObjectInMethod.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
});
enhancer.create();
}
}
}
// *注:默认情况报GC overhead limit exceeded,通过-XX:-UseGCOverheadLimit参数禁用保护机制看到实际错误信息
元空间溢出(JDK 8)
代码同代码1,借助CG Lib运行时产生大量动态类,唯一的区别在于运行环境修改为Java 1.8,设置-XX:MaxMetaspaceSize参数,便可以收获java.lang.OutOfMemoryError: Metaspace这一报错,2.2.4和2.2.5的对比也验证了JDK 8中元空间取代永久代这一事实。
本机直接内存溢出
直接申请分配内存(实际上并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,于是抛出异常。)
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws IllegalAccessException {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}