内存泄露和内存溢出的区别
概念区别
内存溢出(OutOfMemoryError): 指程序在申请内存时,没有足够的内存空间供其使用,出现OutOfMemoryError ,比如申请一个Integer但给它存了Long才能存下的数那就是内存溢出
内存泄露(Memory Leak): memory leak 指程序在申请内存后,无法释放已经申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后的结果很严重,无论多少内存,迟早会被占光,即不再被使用的对象内存不能被回收,就是内存泄露
Java堆溢出
介绍
Java堆的大小为20MB,不可扩展(将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展),通过参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析
Java堆的大小为20MB,不可扩展,设置堆的参数配置如下:
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
Java堆内存溢出异常测试
import java.util.ArrayList;
import java.util.List;
/**
* VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* @author yanyugang
* @description ${todo}
* @date 2019-06-03 14:13
*/
public class HeapOOM {
static class OOMObject{}
public static void main(String[] args){
List<OOMObject> list=new ArrayList<>();
while (true){
list.add(new OOMObject());
System.out.println(list.size());
}
}
}
- 输出如下
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid10936.hprof ...
Heap dump file created [13679366 bytes in 0.041 secs]
虚拟机栈和本地方法栈溢出
介绍
在Java虚拟机规范中描述了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
- 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
代码测试
Java栈的大小为128KB,设置栈的参数配置为:
-Xss128k
测试方法
- 使用-Xss参数减少栈内存容量。结果:抛出StackOverflowError异常,异常出现时输出的堆栈深度相应缩小
- 定义了大量的本地变量,增大此方法帧中本地变量表的长度。结果:抛出StackOverflowError异常时输出的堆栈深度相应缩小
/**
* VM Args: -Xss128k
* @author yanyugang
* @description ${todo}
* @date 2019-06-05 16:16
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args){
JavaVMStackSOF oom = new JavaVMStackSOF();
try{
oom.stackLeak();
}catch (Throwable e){
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
- 输出结果
stack length:980
Exception in thread "main" java.lang.StackOverflowError
at demo.utils.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
方法区和运行时常量池溢出
在JDK1.6及之前的版本中,由于常量池分配在永久代内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量
String.intern()是一个Native方法,它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用
-XX:PermSize=10M -XX:MaxPermSize=10M
import java.util.ArrayList;
import java.util.List;
/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
* @author yanyugang
* @description ${todo}
* @date 2019-06-06 15:48
* JDK6和JDK7运行的结果不一样
* JDK6会报错:java.lang.OUtOfMemoryError:PermGen space
* JDK7则不会报错
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args) throws InterruptedException{
// 使用List保持着常量池引用,避免Full GC回收常量池行为
List<String> list = new ArrayList<>();
// 10MB的PermSize在integer范围内足够产生OOM了
int i =0;
while (true){
list.add(String.valueOf(i++).intern());
}
}
}
- 借助CGLib使方法区出现的内存溢出异常
/**
* VM Args: -XX:PermSize=1M -XX:MaxPermSize=1M
* @author yanyugang
* @description ${todo}
* @date 2019-06-10 9:57
*/
public class JavaMethodAreaOOM {
public static void main(String[] args){
while (true){
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy proxy) throws Throwable{
return proxy.invoke(obj,objects);
}
});
enhancer.create();
}
}
static class OOMObject{
}
}
本机直接内存溢出
DirectMemory容量可以通过 -XX:MaxDirectMemory指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样
/**
* VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
*
* @author yanyugang
* @description ${todo}
* @date 2019-06-10 10:32
*/
public class DirectMemoryOOM {
private static final int _1MB=1024 * 1024;
public static void main(String[] args) throws Exception{
Field unsafeField =Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe=(Unsafe) unsafeField.get(null);
while (true){
unsafe.allocateMemory(_1MB);
}
}
}
- 运行结果
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at demo.utils.DirectMemoryOOM.main(DirectMemoryOOM.java:23)