可以捕获OOME,但通常将是无用的,具体取决于到达捕获时JVM是否能够垃圾收集某些对象,以及到那时还剩下多少堆内存。
示例:在我的JVM中,该程序运行完成:
import java.util.LinkedList;
import java.util.List;
public class OOMErrorTest {
public static void main(String[] args) {
List<Long> ll = new LinkedList<Long>();
try {
long l = 0;
while(true){
ll.add(new Long(l++));
}
} catch(OutOfMemoryError oome){
System.out.println("Error catched!!");
}
System.out.println("Test finished");
}
}
但是,仅在捕获中添加一行就可以向您展示我在说什么:
import java.util.LinkedList;
import java.util.List;
public class OOMErrorTest {
public static void main(String[] args) {
List<Long> ll = new LinkedList<Long>();
try {
long l = 0;
while(true){
ll.add(new Long(l++));
}
} catch(OutOfMemoryError oome){
System.out.println("Error catched!!");
System.out.println("size:" +ll.size());
}
System.out.println("Test finished");
}
}
第一个程序运行良好,因为到达捕获点时,JVM会检测到不再使用列表(此检测也可以是在编译时进行的优化)。因此,当我们到达print语句时,堆内存几乎全部被释放了,因此我们现在有很大的回旋余地可以继续。这是最好的情况。
但是,如果安排了代码,例如ll
在捕获OOME之后使用了列表,则JVM无法收集它。这发生在第二个片段中。捕获了由新的Long创建触发的OOME,但是很快我们就创建了一个新的Object(该System.out,println
行中的String ),并且堆几乎已满,因此引发了新的OOME。这是最坏的情况:我们试图创建一个新对象,但是失败了,我们抓住了OOME,是的,但是现在第一条需要新堆内存的指令(例如:创建一个新对象)将抛出一个新的OOME。想一想,如果剩下这么少的内存,我们现在还能做什么?可能刚刚退出。因此无用。
JVM不收集资源的原因中,有一个确实令人恐惧:与其他线程共享资源也正在使用它。任何有头脑的人都可以看到,如果将OOME插入任何类型的非实验应用中,可能会多么危险。
我正在使用Windows x86 32位JVM(JRE6)。每个Java应用程序的默认内存为64MB。