一,前言
说到内存泄漏,往往会跟内存溢出混淆吧!!! 哈哈哈哈~因此抽点时间来整理一下他们的关系;
let’s go!
二,概述
Java开发者都知道,Java的垃圾回收机制可以及时的回收空闲内存,可以有效的防止内存泄露,尽管如此,Java编码中还是存在许多内存泄漏的可能性,如果不能有效的处理内存泄漏, 使内存使用率达到应用承受的极限,往往会导致Crash~
1,什么是内存泄漏?
一些对象有着有限的生命周期。当这些对象所要做的事情完成了,我们希望他们会被回收掉。但是如果有一系列对这个对象的引用,那么在我们期待这个对象生命周期结束的时候被收回的时候,它是不会被回收的。它还会占用内存,这就造成了内存泄露。
2,什么是内存溢出(OOM)?
大量的内存泄漏,使内存很快被耗尽,从而引发内存溢出(OOM)
Out Of Memory Error
,导致程序Crash
3,内存又是什么?
想要更好的了解内存泄漏,那么对内存的理解必不可少
Java是在JVM所虚拟的内存环境中执行的,Java的内存可分为3个区:堆(heap),栈(stack),和方法区(method)
(1)堆
堆(heap):堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身。
(2)栈
栈(stack):是最简单的数据结构,(栈)最显著的特征是 LIFO(Last In, First Out,
后进先出
),栈中只存放基本类型和对象引用(不是对象)。
(3)方法区
方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的
class
和static
变量。
内存的概念大概理解清楚后,要考虑的问题来了:
到底是哪里的内存会让我们造成内存泄露?
3,造成内存泄漏的原因分析
在JAVA中JVM的栈记录了方法的调用,每个线程拥有一个栈。在线程的运行过程当中,执行到一个新的方法调用,就在栈中增加一个内存单元,即帧(frame)。在frame中,保存有该方法调用的参数、局部变量和返回地址。然而JAVA中的局部变量只能是基本类型变量(int),或者对象的引用。所以在栈中只存放基本类型变量和对象的引用。引用的对象保存在堆中。
当某方法运行结束时,该方法对应的frame将会从栈中删除,frame中所有局部变量和参数所占有的空间也随之释放。线程回到原方法继续执行,当所有的栈都清空的时候,程序也就随之运行结束。
而对于堆内存,堆存放着普通变量。在JAVA中堆内存不会随着方法的结束而清空,所以在方法中定义了局部变量,在方法结束后变量依然存活在堆中。
综上所述,栈(stack)可以自行清除不用的内存空间。但是如果我们不停的创建新对象,堆(heap)的内存空间就会被消耗尽。所以JAVA引入了垃圾回收(garbage collection,简称GC)去处理堆内存的回收,但如果对象一直被引用无法被回收,造成内存的浪费,无法再被使用。所以对象无法被GC回收就是造成内存泄露的原因!
4,从垃圾回收机制看内存回收
垃圾回收(GC)可以自动清空堆中不再使用的对象。在JAVA中对象是通过引用使用的。如果没有任何引用指向该对象,那么该对象就无从处理,这样的对象称为不可到达(unreachable)。垃圾回收就会释放不可到达的对象所占据的内存。
实现思想:我们将栈定义为root,遍历栈中所有的对象的引用,再遍历一遍堆中的对象。因为栈中的对象的引用执行完毕就删除,所以我们就可以通过栈中的对象的引用,查找到堆中没有被指向的对象,这些对象即为“不可到达对象”,对其进行垃圾回收。
如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。
5,引用类型的不同对内存回收造成的影响
在jdk1.2以前的版本中,如果一个变量不在被任何一个变量引用,那么程序就无法在使用这个对象。也就是说,只有对象处于“可触及状态”(reachable)时,程序才可以使用它。
从jdk1.2版本开始,把对象的引用分为4种等级,从而使程序能够更加灵活的控制对象的生命周期。这四种级别由高到底分别是:强引用,软引用,弱引用,和虚引用;详见Java引用类型和使用分析
三,总结
经过上面一系列的理性分析,终于可以得到一些结论:
1, 当某些地方持有对象的强引用,且在生命周期结束的时候没有及时释放,进而造成内存单元的占用,使其无法被GC回收,引发内存泄漏
2,大量的内存泄漏,导致内存急剧耗尽,一旦内存耗尽进而引发内存溢出 OOM Crash;