OutOfMemoryException JVM

本文详细解析了Java中常见的内存溢出错误,包括PermGen、HeapSpace和GCoverheadLimitExceeded等异常的原因及解决策略。同时介绍了如何通过调整JVM参数来优化内存使用,避免FullGC,以及在Java8中MetaSpace的管理。

java.lang.OutOfMemoryError: PermGen space

这个异常比较常见,是说JVM里的Perm内 存区的异常溢出,由于JVM在默认的情况下,Perm默认为64M,而很多程序需要大量的Perm区内 存,尤其使用到像Spring等框架的时候,由于需要使用到动态生成类,而这些类不能被GC自动释放,所以导致OutOfMemoryError: PermGen space异常。解决方法很简单,增大JVM的 -XX:MaxPermSize 启动参数,就可以解决这个问题,如过使用的是默认变量通常是64M[5.0 and newer: 64 bit VMs are scaled 30% larger; 1.4 amd64: 96m; 1.3.1 -client: 32m.],改成128M就可以了,-XX:MaxPermSize=128m。如果已经是128m(Eclipse已经是128m了),就改成 256m。我一般在服务器上为安全起见,改成256m。

java.lang.OutOfMemoryError:heap space或其它OutOfMemoryError

这个异常实际上跟上面的异常是一个异常,但解决方法不同,所以分开来写。上面那个异常是因为JVM的perm区内 存区分少了引起的(JVM的内 存区分为 young,old,perm三种)。而这个异常是因为JVM堆内 存或者说总体分少了。解决方法是更改 -Xms -Xmx 启动参数,通常是扩大1倍。xms是管理启动时最小内 存量的,xmx是管里JVM最大的内 存量的。 注:OutOfMemoryError可能有很多种原因,根据JVM Specification, 可能有一下几种情况,我先简单列出。stack:stack分区不能动态扩展,或不足以生成新的线程。Heap:需要更多的内 存,而不能获得。Method Area :如果不能满足分配需求。runtime constant pool(从Method Area分配内 存)不足以创建class or interface。native method stacks不能够动态扩展,或生成新的本地线程。

java.lang.OutOfMemoryError: GC overhead limit exceeded

这个是JDK6新添的错误类型。是发生在GC占用大量时间为释放很小空间的时候发生的,是一种保护机制。我在JSP导大Excel的时候碰到过。最终解决方案是,关闭该功能,使用—— -XX:-UseGCOverheadLimit

java.lang.StackOverflowError

老实说这个异常我也没碰见过,但JVM Specification就提一下,规范上说有一下几种境况可能抛出这个异常,一个是Stacks里的线程超过允许的时候,另一个是当native method要求更大的内 存,而超过native method允许的内 存的时候。根据SUN的文档,提高-XX:ThreadStackSize=512的值。

总的来说调优JVM的内存,主要目的就是在使用内 存尽可能小的,使程序运行正常,不抛出内 纯溢出的bug。而且要调好最小内 存,最大内 存的比,避免GC时浪费太多时间,尤其是要尽量避免FULL GC。

补充:由于JDK1.4新增了nio,而nio的buffer分配内存比较特殊(读写流可以共享内存)。如果有大量数据交互,也可能导致java.lang.OutOfMemoryError。相应的JDK新增了一个特殊的参数:-XX:MaxDirectMemorySize 默认是64M,可以改大些如128M。

写一个导致PermGen的OOM的Java程序

此程序主要参考了2。为了使程序能更快的出现OOM,运行时加入了虚拟机参数-XX:PermSize=2M -XX:MaxPermSize=2M,如下。

public class Problem1 {

public static void main(String args[]){

List<String> list = new ArrayList<String>();

int i = 0;

while(true){

list.add(String.valueOf(i++).intern());

}

}

}


程序运行结果如下图,jdk版本1.7.0_67。

如果是在Java 8下运行这个程序,就要使用其他的参数,因为Java 8的JVM没有Perm区了,而改为了MetaSpace,参考3。所以要使用参数-XX:MetaspaceSize=2M -XX:MaxMetaspaceSize=2M,如果还同时使用之前的参数会看到运行时会提示Java 8中已经移除这些参数了。其运行结果如下图,jdk版本1.8.0_11。

另外,由于Perm区存放了类的信息、类的静态变量、类中final类型的变量、类的方法信息,如果类的信息或静态变量过多,也会出现OOM的情况。不过Java对动态生成类的信息支持的不是很好,做起来比较麻烦。普通的反射机制,只能获取已有的类,并对这个类做一些修改,然后实例化。如果要动态创建一个Java类,就需要动态的compile,可以参考4。如果我们使用其中的方法,动态的生成类的信息,并实例化后放在List中,应该也能引发Perm区的OOM。以下这段代码参考《深入理解Java虚拟机 JVM高级特性与最佳实践》这本书,通过CGlib来动态生成类信息,最终引发Perm区的OOM。代码如下。


public class Problem1_2 {
	public static void main(String args[]){
		while(true){
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(OOMObject.class);
			enhancer.setUseCache(false);
			enhancer.setCallback(new MethodInterceptor(){
				public Object intercept(Object obj, Method method,
						Object[] args, MethodProxy proxy) throws Throwable{
					return proxy.invokeSuper(obj, args);
				}
			});
		}
	}
	static class OOMObject{
	}
}


程序运行结果如下图,jdk版本1.7.0_67。

如果是使用Java 8的话,也能看到和之前类似的结果,如下图。

防止递归的stackoverflow

如果递归的深度很深,也会引发stackoverflow。例如在Fibonacci数列的计算中,如果不做任何优化,很可能算不对1000位就stackoverflow了,即使能算完的,性能也不是很好,优化的办法有以下几种。本文的代码参考了这2篇文章5 6,代码如下。

public class TestFibo {
	public static void main(String[] args) {
		int N=50;
		long begin=System.currentTimeMillis();
		System.out.println(fibo1(N)); //fibo1
		System.out.println(System.currentTimeMillis()-begin);
		begin=System.currentTimeMillis();
		System.out.println(fibo2(N)); //fibo2
		System.out.println(System.currentTimeMillis()-begin);
		begin=System.currentTimeMillis();
		System.out.println(fibo3(N)); //fibo3
		System.out.println(System.currentTimeMillis()-begin);
		begin=System.currentTimeMillis();
		System.out.println(fibo4(N)); //fibo4
		System.out.println(System.currentTimeMillis()-begin);
	}
    //1.1 非尾递归实现(书本上经常出现)
    public static long fibo1(long n){
        if(n<2) return n;
        return fibo1(n-1)+fibo1(n-2); //小心栈溢出
    }
    //1.2 缓存实现(JS good part里有过介绍)
    public static int LENGTH=30; //过大了就会占用过多空间
    public static long[] cache=new long[LENGTH];
    public static long fibo2(int n){
        if(n<2) return n;
        if(n>=LENGTH){
            return fibo2(n-1)+fibo2(n-2);
        }else if(cache[n]==0){
            cache[n]=fibo2(n-1)+fibo2(n-2); //减少重复计算
        }
        return cache[n];
    }
    //1.3 迭代实现
    public static long fibo3(long n){
        if(n<2) return n;
        long pre=1,prepre=1,ret=0;
        for(int i=2;i<n;i++){
            ret=pre+prepre;
            prepre=pre;
            pre=ret;
        }
        return ret;
    }
    //1.4 尾递归实现
    public static long fibo4(int n){
        if(n<2) return n;
        return fibo4Helper(n, 1, 1, 3); //保持与非尾递归接口不变,是借助帮助方法实现尾递归的
    }
    private static long fibo4Helper(int n,long prepre,long pre,int begin){
        if(n==begin) return pre+prepre;
        return fibo4Helper(n, pre, prepre+pre, ++begin);    //这里相当于迭代实现for-loop的浓缩
    }
}


尾递归实现

尾递归是函数式编程(FP)中的概念,其主要思想是在函数递归调用时,函数调用栈的高度不增高,即,递归调用完成后,不需要再做其他的指令即可退出函数的情况,如上述代码的1.4部分。多数的函数式编程语言,可以将尾递归编译成循环,这样就不会发生栈溢出,可惜Java编译器没有尾递归的优化,因此,必须手动转换成循环实现。

循环实现

循环实现如上述代码1.3的部分,其实循环的思想还是来自于尾递归,如果一步不能想到递归转换为循环的方法,可以先转成为尾递归,再转循环,就简单多了。

手工维护栈

如果不能想到转换为尾递归的方法,那就只好手动去维护递归调用的栈,将所需要的数据存在栈中,然后对于手工栈非空的情况下,循环进行计算。以上的1.2缓存实现是一种优化了的手工栈,在这种实现中避免了重复计算。但是手工栈的问题是,需要有大量的空间消耗,以及手工栈维护的复杂性,这些都是无法避免的。其实,手工栈方法就是将函数调用的stack转移到堆上,堆的空间比较大,因此发生overflow的可能性就被降低了。

调整-XX:ThreadStackSize参数

如果不改变程序,可以调整-XX:ThreadStackSize参数,加大线程堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K,修改为8M或者更大,就可以支持更深的递归调用。也可以减小递归程序的局部变量表,比如,少用比较大的double,long类型,减少参数个数,局部变量注意作用域,如果可以在作用域外开,就不要开在作用域内,以此减少局部变量表的大小。

### 回答1: NPOI是一个强大的.NET库,用于读取和写入Microsoft Office文件,如Excel、Word和PowerPoint。在使用NPOI的过程中,有时会遇到System.OutOfMemoryException(内存不足)的异常。 System.OutOfMemoryException.NET框架中的一种异常,它表示当前系统的可用内存不足,无法分配和处理所需的内存,导致内存溢出。这个异常通常与大数据量的文件或处理大型文件相关。 当使用NPOI处理大型Excel文件时,可能会遇到System.OutOfMemoryException异常。这主要是因为Excel文件通常包含大量的数据,当尝试一次性加载整个文件到内存时,可能会超出计算机可用的内存限制。 为了解决这个问题,可以尝试以下几种方法: 1.使用逐行读取的方式处理Excel文件,而不是一次性加载全部数据到内存中。通过逐行读取,可以降低内存使用量,并逐步处理数据。 2.优化代码逻辑,减少不必要的内存使用。例如,在处理每行数据时,可以立即释放不再需要的资源,如关闭流或释放对象。 3.增加系统的可用内存。可以尝试增加计算机的物理内存或调整相关的系统配置,以提供更多的可用内存供NPOI使用。 4.分割大型Excel文件为多个较小的文件进行处理。将一个大的Excel文件拆分成多个小文件,分别处理,可以降低系统内存的压力。 总之,遇到NPOI System.OutOfMemoryException异常时,需要通过合理的代码逻辑和合适的资源管理来解决内存不足的问题,以确保能够成功处理大型Excel文件。 ### 回答2: System.OutOfMemoryException 是一种在使用 NPOI 库时可能会遇到的异常。这个异常通常发生在尝试加载或处理大型 Excel 文件时,程序在内存分配方面遇到了限制。 当使用 NPOI 进行 Excel 文件的读取或写入操作时,它会将文件内容加载到内存中进行处理。如果文件过大,可能会超出系统的内存限制,导致程序抛出 OutOfMemoryException 异常。 为了解决这个问题,可以考虑以下几种方法: 1. 内存优化:可以检查代码中是否有内存泄漏的问题。确保在完成操作后及时释放对象和资源,避免持有不必要的引用。合理使用 NPOI 提供的 API,避免一次性加载整个文件。 2. 分批处理:如果 Excel 文件太大无法一次性加载到内存中,可以考虑使用分批处理的方式。可以将大文件拆分为多个小文件进行处理,这样可以减少每次加载的数据量,降低内存占用。 3. 增加 JVM 内存限制:如果应用程序运行在 Java 虚拟机上,可以考虑增加 JVM 的内存限制。可以在启动应用程序时,通过设置 -Xmx 参数来增加最大堆内存限制。 4. 使用 SAX 解析器:NPOI 提供了 HSSFSheet 类和 XSSFWorkbook 类用于加载和处理 Excel 文件,这两个类会将文件内容一次性加载到内存中。如果遇到大型文件导致内存溢出的问题,可以考虑使用基于事件的 SAX 解析器,逐行解析 Excel 文件,避免一次性加载整个文件。 通过以上方法,我们可以优化内存使用,避免发生 NPOI System.OutOfMemoryException 异常。 ### 回答3: NPOI是一个用于操作Excel、Word和PowerPoint等文件格式的开源库。当使用NPOI进行一些大量数据操作时,有时会遇到System.OutOfMemoryException的异常。 System.OutOfMemoryException表示系统已经没有足够的内存来分配所需的对象,这可能是由于内存不足或者内存碎片化导致的。当读取或写入大量数据时,NPOI可能需要分配大量的内存来加载或处理这些数据,从而导致内存不足的异常。 出现这个异常时,我们可以尝试以下几种解决方法: 1.增加系统的物理内存。通过增加计算机的内存容量,可以为NPOI提供更多的可用内存,从而减少出现内存不足的可能性。 2.优化内存使用。检查代码中是否有大量的无用对象或者不必要的资源占用,及时释放无用的对象和资源,以减少内存的占用。 3.分批处理数据。如果需要处理的数据量非常大,可以考虑将数据分成多个批次进行处理,每次处理一部分数据,并在处理完毕后及时释放相关资源,从而减少内存的压力。 4.使用SXSSFWorkbook。对于大量数据的导出操作,可以使用SXSSFWorkbook来替代XSSFWorkbook,SXSSFWorkbook可以将数据写入临时文件并进行缓存,从而减少对内存的需求。 总之,遇到NPOI System.OutOfMemoryException异常时,可以通过增加内存、优化内存使用、分批处理数据和使用SXSSFWorkbook等方法来解决该问题。根据具体情况选择合适的解决方法,从而避免出现内存不足的异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值