在代码层面避免OOM

来源:互联网 发布:凌渡车钥匙淘宝图片 编辑:程序博客网 时间:2024/06/06 02:41

之前去一家公司面试,面试官问了我这个问题:如何在代码层面上做到不出现OOM,我当时很是紧张,一个劲在再跟他解释OOM发生的两种情况。后来 回到住处总结了一下。

OOM包括内存溢出和内存泄漏,内存溢出是指当需要为对象分配内存的时候,得不到足够的内存来分配。而内存泄漏则是对象在申请到内存后,使用完了之后却无法释放掉这部分内存,最终会导致OutOfMemoryError。

先说一说内存泄漏,内存泄漏,是指程序在申请内存后,使用完了以后无法释放已申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光。内存泄漏会最终会导致out of memory。 

内存泄漏的根本原因是泄漏对象到GC Root存在 引用链,一般可以作为GC Root的有虚拟机栈中的对象,类的静态属性、常量。到此就不得不说一说GC Root了。

GC Root是java垃圾收集里面的东西。最早的时候,判断一个对象是否“死亡”使用的是引用计数的方法——给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用。但是,这个方法有一个明显的缺陷,那就是如果两个本该被GC的对象之间互相引用,造成双方均无法被GC。

改进的算法就是可达性分析算法——通过一系列的称为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所经过的路径称为引用链,当一个对象到GC Root没有任何引用链相连时,则证明此对象是不可用的,是可以被GC掉的。

GC Root的概念大概就是这个样子,我们还需要知道虚拟机栈是java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧用于存储局部变量表(比较重要)、操作数栈、动态链接、方法出口等信息。方法的执行到结束,对应着一个栈帧在虚拟机栈中的出栈和入栈。

所以,针对GC Root,方法有:

不要经常在调用的方法中创建对象。同样很忌讳在循环中创建对象。可以考虑使用容器装载这些对象,然后从容器中取那

些对象,而不用每次new之后又丢弃(对象池的思想)。
尽量少使用静态变量,因为静态变量是全局的,GC不会回收
,除非这个静态变量对应的类被卸载,而一个类被卸载必须满足三个条件:

该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

还有一些其他的办法——

尽早的释放无用对象的引用,对于一个需要长期运行的程序来说,将不用的对象设置为null;

或者是在使用临时变量时,在引用变量退出活动域后,设置为null。
避免集中创建对象,尤其是大对象。
尽量运用对象池技术提高系统性能;生命周期长的对象拥有生命周期短的对象时容易引发内存泄露,

例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略(分治思想)。
程序中不可避免大量使用字符串处理时,避免使用String,而是用StringBuffer。

第一次写博客,如果有什么不对的地方,还望大家批评指正


原创粉丝点击