写在前面
内存泄漏实际上很多时候,对于开发者来说不容易引起重视。因为相对于crash来说,android中一两个地方发生内存泄漏的时候,对于整体没有特别严重的影响。但是我想说的是,当内存泄漏多的时候,很容易造成他OOM的,因为android给每个app的分配的内存是有限的,而且当发生内存泄漏的时候,对于app的体验也会不好,容易造成卡顿等不好的体验。
Java内存结构
上面展示的是Java虚拟机运行时数据区的模型图
这里简单介绍下
- 方法区:存储已加载的类信息,常量池,静态变量(静态变量与app的生命周期同步)
- 虚拟机栈:存储基本类型和对象引用
- 堆:存储新建的对象或数组对象
- 程序计数器:线程执行字节码文件位置的指示器,用于线程切换后,再次切换回来能够准确执行上次执行到的字节码文件位置。
- 本地方法栈:用于记录Native方法。
我们主要关注这几个内存区域,new出来的对象,放在Heap堆这个区域,这块内存区由GC(Garbage Collector)负责内存的回收。对于Java程序员来说,很多时候我们不需要关心内存的分配与回收问题,但是这并不表示Java没有内存泄漏,Java的内存泄漏显得更为隐蔽,于是这不仅需要我们在开发时避免写出容易造成内存泄漏的问题代码,还需要我们在出现内存泄漏时掌握相应的技巧去定位和发现问题。
Java 对象在内存中的三种状态
- 可达状态
当一个对象被创建后,有一个以上的引用变量引用它。在有向图中可以从起始顶点导航到该对象,那它就处于可达状态,程序可通过引用变量来调用该对象的属性和方法。 - 可恢复状态
如果程序中某个对象不再有任何引用变量引用它,它将先进入可恢复状态,此时从有向图的起始顶点不能导航到该对象,在这个状态下,系统的垃圾回收机制准备回收该对象所占用的内存。在回收该对象之前,系统会调用可恢复状态的对象的finalize方法进行资源清理,如果系统在调用finalize方法重新让一个以上的引用变量引用该对象,则这个对象会再次变为可达状态;否则,该对象进入不可达状态。 - 不可达状态
当对象的所有关联被切断,且系统调用所有对象的finalize方法依然没有使得该对象变为可达状态,则这个对象将永久性地失去引用,最后变为不可达状态。只有当一个对象处于不可达状态时,系统才会真正回收该对象所占有的资源。
左边的object1,object2,object3和object4是仍然存活的对象,或者说可达状态的对象,而右边的object5,object6和object7则是判定可回收的状态,或者说不可达状态的对象
这里说到的GC Roots,一般作为GC Roots的对象有下面几个(书本上的)可达性算法的根节点(简单来说,这些对象一般不会被GC 回收):
a 虚拟机栈(栈桢中的本地变量表)中的引用的对象
b.方法区中的类静态属性引用的对象
c.方法区中的常量引用的对象
d.本地方法栈中JNI的引用的对象
为什么会有内存泄漏
上面所说的不可达状态的判断,在Java中是由 可达性分析算法 来实现的。其基本思路就是通过一系列的称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可达的。
在android中尤为明显,因为android中很多对象都是有生命周期的。当它们的任务完成之后,它们将被垃圾回收。如果在对象的生命周期本该结束的时候,这个对象还被一系列的引用,这就会导致内存泄漏。
简单来说,没有被GC ROOTS间接或直接引用的对象的内存会被回收。
那么为什么会有内存泄漏呢
来看一段Java代码:
List list = new ArrayList();
for (int i = 1; i < 100; i++) {
Object object = new Object();
l