在介绍 Java 堆溢出的时候提到过这两者的区别,相关内容可见 Java 堆溢出
但是当时并没有介绍的很详细,所以本篇文章专门从不同角度来刨析这两个内容!!
「内存泄漏」有一个很形象的形容:占着茅坑不拉屎;放在内存中就是:占据着内存资源,但是却毫无用处
言归正传,内存泄漏更严谨的定义:本应该被回收的对象,由于错误的引用链导致未进行回收
如上图所示,根据「可达性分析算法」很明显对象 D、E、F 可以被正确的回收;但由于错误的引用链导致对象 A、B、C 没有被回收,这就造成了内存泄漏
还有一种宽泛意义上的内存泄漏:由于开发人员的疏忽,使得对象的生命周期被演延长,最终造成了内存溢出
public void f() {
Object obj = new Object();
if (1 + 1 > 0) {
// 使用对象 obj ...
}
// 其它地方都不会使用对象 obj
}
上面这种就属于宽泛意义上的内存泄漏,可以直接把obj
定义在if
判断里面,当if
代码块执行完,该对象的生命周期就结束了,可以直接被回收
单例模式:单例对象的生命周期自被创建开始一直到应用程序结束,所以在单例程序中,如果持有外部对象的引用,那么该外部对象就不能被回收,导致内存泄漏!
提供 close 的资源未关闭导致内存泄漏:数据库连接dataSourse.getConnection()
,网络连接 (socket) 和 IO 连接必须手动 close,否则就不能被回收,导致内存泄漏!
内存溢出相比于内存泄漏好理解:为对象分配内存时,发现内存不够,导致无法完成内存分配
由于 GC 的不断发展,一般情况下,除非应用程序占用内存的速度非常快,导致垃圾回收的速度赶不上内存分配的速度,否则不太容易出现 OOM
大多情况下,GC 会进行各年龄段的垃圾回收,实在不行就放大招,来一次独占式的 Full GC 操作,这个时候会回收大量内存,以供程序继续使用
JavaDoc 对内存溢出给出的官方描述:Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.
这一句话中有两个重点:没有空闲内存,而且垃圾回收也无法提供更多内存
导致没有空闲内存的原因可能有以下三种:
Java 虚拟机堆内存一开始分配不够
在虚拟机启动时,可以通过参数-Xms -Xmx
指定虚拟机初始内存和最大内存,可能一开始分配的内存就不足以支撑系统正常运行,可以适当增加内存!!
程序中存在内存泄漏
内存泄漏会导致无用对象依旧占据内存,使得后续无法为新对象分配内存,导致内存溢出!!
存在大量大对象,并且长时间不能被回收 (存在引用)
对于老版本的 JDK,因为永久代的大小是有限的,并且 Java 虚拟机对永久代垃圾回收 (如:常量池回收、卸载不再需要的类型) 非常不积极,所以当不断添加新类型的时候,永久代出现 OOM 的概率非常大,尤其是在运行时存在大量动态类型生成的场合
类似 intern 字符串缓存占用太多空间,也会导致 OOM 问题,对应的异常信息,会标记出来和永久代相关:java.lang.OutOfMemoryError: PermGen space
,这也是字符串常量池移入堆中的原因
随着元空间的引入,方法区内存已经不再那么窘迫,所以相应的 OOM 有所改观,出现 OOM 异常信息则变成了:java.lang.OutofMemoryError:Metaspace
直接内存不足,也会导致 OOM
上面说:没有空闲内存,而且垃圾回收也无法提供更多内存才会出现 OOM 异常
这里面隐含了一层意思:并不是没有空闲内存就会抛出 OOM 异常,而是在发现没有空闲内存时,会先执行一次垃圾回收操作,尽最大努力去清理内存空间,如果回收完内存依旧不够,才会抛出 OOM 异常
例外:并非所有内存不够的时候都会先执行一次垃圾回收操作,如果要分配的对象过大,如:大到空堆都无法存下,这种情况就会直接抛出 OOM 异常