内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用GC函数来释放内存,因为不同的JVM实现者可能使用不同的算法管理GC,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是中断式执行GC。但GC只能回收无用并且不再被其它对象引用的那些对象所占用的空间。 Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就作为垃圾回收。
常见几种内存溢出原因:
- 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
- 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
- 代码中存在死循环或循环产生过多重复的对象实体;
- 使用的第三方软件中的BUG;
- 启动参数内存值设定的过小;
常见的三种Java内存溢出:
- java.lang.OutOfMemoryError: Java heap space(Java堆溢出)
- java.lang.OutOfMemoryError: PermGen space(PermGen space溢出)
- java.lang.StackOverflowError(栈溢出)
java.lang.OutOfMemoryError: Java heap space(Java堆溢出)
JVM在启动的时候会自动设置JVM Heap的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)不可超过物理内存。可以利用JVM提供的-Xmn -Xms -Xmx等选项进行设置。Heap的大小是Young Generation 和Tenured Generaion 之和。在JVM中如果98%的时间是用于GC,且可用的Heap size 不足2%的时候将抛出此异常信息。
以下示例不断创建对象以模拟堆溢出:
public class Test {
public static void main(String[] args) {
List<Test> list = new ArrayList<Test>();
while (true)
list.add(new Test());
}
}
可以手动设置JVM Heap(堆)的大小以解决。修改 TOMCAT_HOME/bin/catalina.sh ,在顶部加入:
JAVA_OPTS="-server -Xms800m -Xmx800m -XX:MaxNewSize=256m"
-XX:MaxNewSize=size 设定新生代占整个堆内存的最大值。
java.lang.OutOfMemoryError: PermGen space(PermGen space溢出)
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。为什么会内存溢出?这是由于这块内存主要是被JVM存放Class和Meta信息的,Class在被Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同,sun的 GC不会在主程序运行期对PermGen space进行清理,所以如果你的APP会载入很多CLASS的话,就很可能出现PermGen space溢出。
以下代码示例不断创建新的class以模拟(需要cglib和asm相关jar包):
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class Test {
static class MethodAreaOomObject {
}
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MethodAreaOomObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy)
throws Throwable {
return proxy.invoke(obj, args);
}
});
enhancer.create();
}
}
}
可以手动设置MaxPermSize大小以解决。修改TOMCAT_HOME/bin/catalina.sh,在顶部加入:
JAVA_OPTS="-server -XX:PermSize=64M -XX:MaxPermSize=128M"
-XX:MaxNewSize=size 设定 Perm(俗称方法区)占整个堆内存的最大值。
java.lang.StackOverflowError(栈溢出)
JVM依然是采用栈式的虚拟机,这个和C和Pascal都是一样的。函数的调用过程都体现在堆栈和退栈上了。调用构造函数的 “层”太多了,以致于把栈区溢出了。通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K的空间(这个大约相当于在一个C函数内声明了256个int类型的变量),那么栈区也不过是需要1MB的空间。通常栈的大小是1-2MB的。通常递归也不要递归的层次过多,很容易溢出。
以下代码示例不断递归调用某一函数以模拟栈溢出:
public class Test {
public static void main(String[] args) {
new Test().test();
}
public void test() {
test();
}
}