内存泄漏和内存溢出的区别及各自的解决办法

最近在复习JVM相关知识的时候,多次提到了内存泄漏(OOM),心里突然想到了内存溢出.
索性在这里整理一下内存泄漏和内存溢出的区别及相应的解决办法~

内存泄漏 memory leak

是指程序在申请内存后,无法释放已申请的内存空间就造成了内存泄漏,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
举个例子就是:我租了一个房子,只有一把钥匙,我有事离开不住了,但是我拿的钥匙被没有给别人,造成的别人也进不去这个房子,这个房子就相当于内存泄漏了. 如果每个人都跟我一样,最后的结果就是没有房子可以出租给别人了. 如果这个时候再有人要租房子,这也就造成了内存溢出.
一次的内存泄漏并不可怕,可怕的事多次的内存泄漏造成最终的内存溢出.而且还会在这之前的过程中导致多次的GC,从而降低性能.

以发生的方式来分类,内存泄漏可以分为4类:

  1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
  2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
  3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
  4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,称这类内存泄漏为隐式内存泄漏。

内存泄漏的解决办法:

内存泄漏的解决方法基本上还是要基于良好的编程习惯,有一个良好的编程习惯就可以避免大多数的内存泄漏.编程习惯,或者说是编程守则当然是首推阿里的Java开发手册.阿里Java开发手册[泰山版]

内存泄漏大部分的原因都是因为我们在执行完需要的资源或者执行完程序之后,并没有及时释放、关闭资源、释放空间,从而造成的内存的浪费.这个时候我们就需要在 执行完程序之后,及时的回收之前交给程序的资源和空间.
也有一部分的内存泄漏是由于代码引起的.
比如:

byte[] arr1 = new byte[1024*1024*20];
byte[] arr2 = new byte[1024*1024*20];
arr2 = arr1;

这个时候,代码执行的时候会在堆空间内开辟两块内存分别给arr2,和arr1,但是当arr2 = arr1 之后, 原本分配给arr2的堆空间的内存地址就会失去引用. 这个时候就是相当于造成了内存泄漏,只能等待 堆空间对应的GC触发之后才能够回收,如果是YGC影响还稍微小一些,但如果是直接进入老年区,等待FGC的话,造成内存泄漏的时间就会加长.
而且,如果频繁出现这种情况,也会导致GC的高频触发,降低了整个程序的性能.
相关知识点可以看一下我之前的博客详解堆空间之新生代和老年代.

还有就是在使用线程池或者连接池的情况下,在使用完对应的线程或连接后,要及时的释放相应的线程或者连接,而且在可能的情况下,新线程尽量要创建成为 守护线程(虽然默认就是守护线程,但是一旦用到threadfFactory创建线程的时候,一定要仔细核对代码,确保明确是否需要建立非守护线程).

内存溢出 out of memory

内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
举个例子就是:我要买房子,我想啃老,问家里要100W,但是家里只能给我10W,然后因为钱不够导致房子没买上(报错体质)这就是内存溢出.

内存溢出原因:

  1. 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
  2. 集合类中有对对象的引用,使用完后未清空,产生了堆积,使得JVM不能回收;
  3. 代码中存在死循环或循环产生过多重复的对象实体;
  4. 使用的第三方软件中的BUG;
  5. 启动参数内存值设定的过小

内存溢出的解决办法

观察Jvm日志,如果确定是因为一开始分配的启动参数太小,可以通过设置 -Xms[N] -Xmx[N] 来进行设置启动时分配给Jvm的运行时内存空间大小.
其次就是根据错误日志,走读代码,找到对应的发生内存溢出的位置,针对错误做出相应的解决.
还有一点就是能够使用栈内引用就尽量使用栈内引用,方便资源的释放和提高程序的性能.
如下代码:

public class StringTest {
    public StringBuilder getString1(){
        StringBuilder sb = new StringBuilder("做个测试~~");
        return sb;
    }
    public String getString2(){
        StringBuilder sb = new StringBuilder("做个测试~~");
        return sb.toString();
    }
}

getString1 返回的是一个StringBuilder 对象, 如果执行完getString1()方法的时候,对应的stringBuilder 对象并不能释放,因为 这个对象可能挥别其他方法调用.
但是getString2 返回的是一个String 对象,getString2()中的StringBuilder对象 在方法执行完毕后就会自动回收了,因为他的作用范围只有这个方法.
这样就会减少GC发生的频率,或者说一定概率减少发生OOM的发生概率.

猜你喜欢

转载自blog.csdn.net/shiliu_baba/article/details/106865605