首页 > 代码库 > 内存溢出的预防及解决汇总

内存溢出的预防及解决汇总

内存溢出是一个非常隐式的问题,经过相关资料的查询,先总结一下内存溢出的关键字:what(溢出表现)、origin(内存分配及回收)、where(溢出类型)、why(泄露原因)、how(预防及解决方案)。
 
一、what(溢出表现)
1. 服务器内存长期不合理占用,内存经常处于高位占用,很难回收到低位;
2. 服务器极为不稳定,几乎每两天重新启动一次,有时甚至每天重新启动一次;
3. 服务器经常做 Full GC(Garbage Collection),而且时间很长,大约需要 30-40秒,应用服务器在做 Full GC的时候是不响应客户的交易请求的,非常影响系统性能。
4. 服务器的内存占用不是锯齿形波动,而是趋势向上,最后宕机。
 
二、origin(起源)
1. 内存溢出是指无法回收的内存积攒过多或者正在使用的内存过多,最终导致程序运行需要的内存大于虚拟机所能提供的最大内存,从而导致内存溢出。
因此,为了解决这个问题,从抽丝剥茧的角度来看,首先第一步需要明白起源,即明白内存分配以及回收的方式:
“在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用GC函数来释放内存,因为不同的JVM实现者可能使用不同的算法管理GC,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是中断式执行GC。但GC只能回收无用并且不再被其它对象引用的那些对象所占用的空间。Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就作为垃圾回收。” --引用
我觉得这一段话说的比较准确,总结起来就是:1.内存的分配及回收基本都可以交由程序静默进行,释放不了的核心在于引用还在。
2. 那么什么时候进行垃圾回收(分为Scavenge GC 跟 Full GC)呢?
2.1 Scavenge GC 主要集中在 Eden 园区,一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。
2.2 Full GC。速度很慢,对整个堆进行整理,包括Young、Tenured和Perm。引起 Full GC 的方式有:老年代被写满,持久代被写满以及System.gc()被显示调用。
 
三、where(主要溢出类型)
下一步我们继续关注主要在什么位置有可能内存溢出:
1. java.lang.OutOfMemoryError: Java heap space (堆溢出)
堆是垃圾回收关注的重点,如果在垃圾回收之间 Java 虚拟机创建了过多的对象,虚拟机分配到堆内存的空间已经满了,那么就会报这个错误。
2. Java.lang.OutOfMemoryError: PermGen space (永久区溢出)
如果说堆是给我们程序员使用的话,那么非堆就是给JVM自己使用的,非堆主要存在类的基本信息,他与堆的不同之处在于非堆中在运行期间GC不会释放空间。按照该异常的原义,指的是程序中使用了大量的jar或class,使java虚拟机装载类的空间不够(注意这时候是MaxPermSize较小)。
 
四、why(泄漏原因)
根据时间顺序,我们大致可以总结如下:
1. 启动参数内存值设定的过小,无法加载过大的包;
2. 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
3. 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
4. 代码中存在死循环或循环产生过多重复的对象实体;
5. 使用的第三方软件中的BUG;
6. 注意其他不健壮的代码原因;
6.1 注意无引用对象的及时释放;
6.2 无法避免的大量字符串处理,尽量使用 StringBuffer 而非 String,原因是每一个 String 都需要占用一块内存空间;
6.3 注意静态变量的使用,因为静态变量是全局变量,不会被回收;
6.4 注意大对象的创建及使用,注意显示声明极大的数组对象;
6.5 注意循环体体内的对象创建;
6.6 注意是否一次性查询出来大量的数据;
6.7 检查List及Map是否有对象的引用一直存在;
 
五、how(解决方案及预防方案)
一般来说,系统稳定之后,内存的使用是保持在一定水平的,如果发现内存的使用随时间的增长而呈现线性的增长,那么就有可能说明正在内存泄露。
一般处理的思路,跟以上原因其实一一对应:
1. 增大JVM的启动内存,-Xms256m -Xmx256m -XX:MaxNewSize=256m -XX:MaxPermSize=256m;
2. 检查错误日志;
3. 代码层面的检查(参见第四大点的原因);
4. 使用检查工具,Optimizeit Profiler、JProbe Profiler等等。这些工具的主要点都会聚焦在追踪对象的申请、释放等动作,将内存管理的所有信息进行统计然后可视化,根据这些信息,可以追踪到上述三个步骤仍然无法检查到的泄露对象。
 
文章参考:
1.《jvm垃圾回收是什么时候触发的? 垃圾回收算法? 都有哪些垃圾回收器》http://blog.csdn.net/sunny243788557/article/details/52797088
2.《Java内存溢出的详细解决方案》http://blog.csdn.net/tototuzuoquan/article/details/25591923
3.《java中三种常见内存溢出错误的处理方法》http://outofmemory.cn/java/OutOfMemoryError/PermGen-space-Java-heap-space-unable-create-new-native-thread
4.《Java中垃圾回收有什么目的?什么时候进行垃圾回收?》https://zhidao.baidu.com/question/2204377803050672708.html
5.《 Java内存溢出详解及解决方案》http://blog.csdn.net/xianmiao2009/article/details/49254391
 
 

内存溢出的预防及解决汇总