转自:
http://outofmemory.cn/c/java-outOfMemoryError
https://blog.csdn.net/jianghuchuanke/article/details/79970749
OOM指的是OutOfMemoryException,在java项目中极为常见,中文翻译过来是内存溢出。
与之相关的是内存泄漏
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。为了解决Java中内存溢出问题,我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用GC函数来释放内存,因为不同的JVM实现者可能使用不同的算法管理GC,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是中断式执行GC。但GC只能回收无用并且不再被其它对象引用的那些对象所占用的空间。Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就作为垃圾回收。
java.lang.OutOfMemoryError这个错误我相信大部分开发人员都有遇到过,产生该错误的原因大都出于以下原因:JVM内存过小、程序不严密,产生了过多的垃圾。
导致OutOfMemoryError异常的常见原因有以下几种:
1、内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2、集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3、代码中存在死循环或循环产生过多重复的对象实体;
4、使用的第三方软件中的BUG;
5、启动参数内存值设定的过小;
此错误常见的错误提示:
1、tomcat:java.lang.OutOfMemoryError: PermGen space
2、tomcat:java.lang.OutOfMemoryError: Java heap space
3、weblogic:Root cause of ServletException java.lang.OutOfMemoryError
4、resin:java.lang.OutOfMemoryError
5、java:java.lang.OutOfMemoryError
第二条解释:为什么集合类中有对对象的引用,使用完后未清空,使得JVM不能回收?
因为GC是根据对象有没有被引用,如果一个对象在哪都没有被引用就可以被GC。但是如果一个集合如List a 是全局静态变量,a 在使用完一次后仍然会被引用,它里面的对象也因为它不会被GC,这个时候如果列表中存储大量数据,就可能造成内存溢出了。
如果集合对象是局部变量,那就没事,因为一旦这段代码执行完,它立马就可以被GC。
解决java.lang.OutOfMemoryError的方法有如下几种:
一、增加jvm的内存大小。
方法有:
1)在执行某个class文件时候,可以使用java -Xmx256M aa.class来设置运行aa.class时jvm所允许占用的最大内存为256M。
2)对tomcat容器,可以在启动时对jvm设置内存限度。对tomcat,可以在catalina.bat中添加:
set CATALINA_OPTS=-Xms128M -Xmx256M
set JAVA_OPTS=-Xms128M -Xmx256M
或者把%CATALINA_OPTS%和%JAVA_OPTS%代替为-Xms128M -Xmx256M
3)对resin容器,同样可以在启动时对jvm设置内存限度。在bin文件夹下创建一个startup.bat文件,内容如下:
@echo off
call “httpd.exe” “-Xms128M” “-Xmx256M”
:end
其中"-Xms128M"为最小内存,"-Xmx256M"为最大内存。
二、 优化程序,释放垃圾。
主要包括避免死循环,应该及时释放种资源:内存, 数据库的各种连接,防止一次载入太多的数据。导致java.lang.OutOfMemoryError的根本原因是程序不健壮。因此,从根本上解决Java内存溢出的唯一方法就是修改程序,及时地释放没用的对象,释放内存空间。 遇到该错误的时候要仔细检查程序,嘿嘿,遇多一次这种问题之后,以后写程序就会小心多了。
需要重点排查以下几点:
不健壮代码的特征及解决办法
1 、尽早释放无用对象的引用。好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为 null ,暗示垃圾收集器来收集该对象,防止发生内存泄露。
对于仍然有指针指向的实例, jvm 就不会回收该资源 , 因为垃圾回收会将值为 null 的对象作为垃圾,提高 GC 回收机制效率;
2 、我们的程序里不可避免大量使用字符串处理,避免使用 String ,应大量使用 StringBuffer ,每一个 String 对象都得独立占用内存一块区域;
String str = “aaa”;
String str2 = “bbb”;
String str3 = str + str2;
假如执行此次之后 str ,str2 以后再不被调用 , 那它就会被放在内存中等待 Java 的 gc 去回收 , 程序内过多的出现这样的情况就会报上面的那个错误 , 建议在使用字符串时能使用 StringBuffer 就不要用 String, 这样可以省不少开销;
3 、尽量少用静态变量,因为静态变量是全局的, GC 不会回收的;
4 、避免集中创建对象尤其是大对象, JVM 会突然需要大量内存,这时必然会触发 GC 优化系统内存环境;显示的声明数组空间,而且申请数量还极大。
这是一个案例想定供大家警戒:
使用jspsmartUpload作文件上传,现在运行过程中经常出现java.outofMemoryError的错误,用top命令看看进程使用情况,发现内存不足2M,花了很长时间,发现是jspsmartupload的问题。把jspsmartupload组件的源码文件(class文件)反编译成Java文件,如梦方醒:
m_totalBytes = m_request.getContentLength();
m_binArray = new byte[m_totalBytes];
变量m_totalBytes表示用户上传的文件的总长度,这是一个很大的数。如果用这样大的数去声明一个byte数组,并给数组的每个元素分配内存空间,而且m_binArray数组不能马上被释放,JVM的垃圾回收确实有问题,导致的结果就是内存溢出。
jspsmartUpload为什末要这样作,有他的原因,根据RFC1867的http上传标准,得到一个文件流,并不知道文件流的长度。设计者如果想文件的长度,只有操作servletinputstream一次才知道,因为任何流都不知道大小。只有知道文件长度了,才可以限制用户上传文件的长度。为了省去这个麻烦,jspsmartUpload设计者直接在内存中打开文件,判断长度是否符合标准,符合就写到服务器的硬盘。这样产生内存溢出,这只是我的一个猜测而已。
所以编程的时候,不要在内存中申请大的空间,因为web服务器的内存有限,并且尽可能的使用流操作,例如
byte[] mFileBody = new byte[512];
Blob vField= rs.getBlob("FileBody");
InputStream instream=vField.getBinaryStream();
FileOutputStream fos=new FileOutputStream(saveFilePath+CFILENAME);
int b;
while( (b =instream.read(mFileBody)) != -1){
fos.write(mFileBody,0,b);
}
fos.close();
instream.close();
5 、尽量运用对象池技术以提高系统性能;生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。
6 、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用 hashtable , vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次 new 之后又丢弃
7 、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成 Out Of Memory Error 的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。