内存溢出
内存溢出的原因:程序在申请内存时,没有足够的内存空间
栈溢出
方法死循环递归调用(StackOverflowError)、不断建立线程,多个线程同时在跑(OutOfMemoryError)
/**
* 1M *5000线程同时在跑 =5G 内存小于5G
*/
public class StackOverFlow {
public void test(){
test();//死递归
}
public static void main(String[] args)throws Throwable {
StackOverFlow javaStack = new StackOverFlow();
javaStack.test();
}
}
堆溢出
不断创建对象,分配对象大于最大堆大小(OutOfMemoryError)
import java.util.LinkedList;
import java.util.List;
/**
* @author yyb
* VM Args:-Xms30m -Xmx30m -XX:+PrintGCDetails 堆的大小30M
* 堆内存溢出
*/
public class HeapOom {
public static void main(String[] args)
{
//String[] strings = new String[100000000]; //100m的数组(堆)
List<Object> list = new LinkedList<>(); //在方法执行的过程中,它是GCRoots
int i =0;
while(true){
i++;
if(i%10000==0) System.out.println("i="+i);
list.add(new Object());
}
}
}
本机内存直接溢出
JVM分配的本地直接内存大小大于JVM的限制(可以通过-XX:MaxDirectMemorySize来设置[不设置的话默认与堆内存最大值一样,也会出现OOM异常])
import java.nio.ByteBuffer;
/**
* @author yyb
* VM Args:-XX:MaxDirectMemorySize=100m
* 限制最大直接内存大小100m
* 直接内存溢出
*/
public class DirectOom {
public static void main(String[] args) {
//直接分配128M的直接内存(100M)
ByteBuffer bb = ByteBuffer.allocateDirect(128*1024*1204);
}
}
方法区溢出
在经常动态产生大量Class的应用中,CGLib字节码增强,动态语言,大量JSP(JSP第一次运行需要编译成Java类),基于OSGI的应用(同一个类,被不同的加载器加载也会设为不同的类)
内存泄漏
程序在申请内存后,无法释放已经申请的内存空间
长生命周期的对象持有短生命周期对象的引用
如将ArrayList设置为静态变量,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏
连接未关闭
如数据库连接、网络连接和IO连接等,只有连接被关闭后,垃圾回收器才会回收对应的对象。
变量作用域不合理
例如:1.一个变量定义的作用范围大于其使用范围
2.没有及时地把对象设置为null
内部类持有外部类
Java 的非静态内部类的这种创建方式,会隐式地持有外部类的引用,而且默认情况下这个引用是强引用,
因此,如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏 如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏(你认为垃圾回收器会回收掉外部类的实例,但由于内部类持有外部类的引 用,导致垃圾回收器不能正常工作)
解决方法:你可以在内部类的内部显示持有一个外部类的软引用(或弱引用),并通过构造方法的方式传递进来,在内部类的使用过程中,先判断一下外部 类是否被回收。
Hash值改变
在集合中,如果修改了对象中的那些参与计算哈希值的字段,会导致无法从集合中单独删除当前对象,造成内存泄漏
import java.util.HashSet;
import java.util.Iterator;
/**
* @author yyb
* Hash值改变导致的内存泄漏
*/
public class Node {
private int x;
private int y;
public Node(int x, int y) {
super();
this.x = x;
this.y = y;
}
//重写HashCode的方法
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
//改变y的值:同时改变hashcode
public void setY(int y) {
this.y = y;
}
public static void main(String[] args) {
HashSet<Node> hashSet = new HashSet<Node>();
Node nod1 = new Node(1, 3);
Node nod2 = new Node(3, 5);
hashSet.add(nod1);
hashSet.add(nod2);
nod2.setY(7);//nod2的Hash值改变
Iterator<Node> iterator = hashSet.iterator();
while (iterator.hasNext()) {
Node next = iterator.next();
System.out.println(next.x + "==" + next.y);
}
boolean remove = hashSet.remove(nod2);
System.out.println(remove);
//删掉nod2节点
System.out.println(hashSet.size());
}
}
查看输出
1==3
3==7
false
2
查看结果可以发现,集合中的Node对象字段值确实被改变了,但是remove方法没有成功移除该元素
内存溢出与内存泄漏辨析
内存溢出:实实在在的内存空间不足导致
内存泄漏:该释放的对象没有释放,常见于使用容器保存元素的情况下
如何避免:
内存溢出:检查代码以及设置足够的空间
内存泄漏:一定是代码有问题
大多数情况下,内存溢出往往是内存泄漏造成的。