Android源码系列之十二从源码的角度深入理解LeakCanary的内存泄露检测机制(上)讲义.doc
文本预览下载声明
Android 源码系列之十二从源码的角度深入理解LeakCanary的内存泄露检测机制(上)
在分析LeakCanary源码之前,我们有必要先来了解一下Java的内存分配策略以及Android中常见的内存泄漏。
Java内存分配策略
一、Java程序运行时的内存分配策略有三种,它们分别是静态分配,栈分配和堆分配;对应的三种存储策略所使用的内存空间分别是静态存储区、栈存储区和堆存储区。
静态存储区
静态存储区主要存放静态数据,全局static数据和常量。静态存储区在程序编译时就已经分配好,在程序整个运行期间都存在。
栈存储区
栈存储区主要存放方法体内的局部变量。当一个方法被执行时,该方法体内的局部变量在栈存储区内创建,方法执行结束后局部变量所持有的内存将会自动被释放。
堆存储区
堆存储区通常就是指在程序运行时通过关键字new出来的内存区,这部分内存在不使用时将会由Java垃圾回收器来负责回收。
二、堆与栈的区别
在方法体内定义的一些基本类型的变量和对象的引用变量都称为局部变量,这些局部变量在栈内存中分配空间。当在一个方法块中定义一个变量时,Java就会在栈中为该变量分配内存空间,当超过该变量的作用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间可以被重新使用。
堆内存用来存放所有由new关键字创建的对象(包括该对象中的所有成员变量)和数组。在堆中分配的内存,将由Java垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存的首地址,这个特殊的变量就是我们常说的引用变量。我们可以通过这个引用变量来访问堆中的对象或者数组。
Java如何管理内存
Java的内存管理简单理解就是对象的分配和释放。在Java中对象的内存分配是通过关键字new来进行分配的(注意:基本类型除外),这些对象在堆(Heap)存储区中分配空间,而对象的释放是由GC来决定和进行回收的。在Java的世界里,内存的分配是由程序完成的,而内存的释放是由GC完成的,这种分配和释放两条线的方法简化了程序的工作,但是也加重了JVM的工作,这也是Java程序运行速度较慢得原因之一,因为GC为了能够正确的释放对象,它必须要监控每一个对象的运行状态(包括对象的申请、引用、被引用、赋值等),当GC发现一个对象不再被引用了,那么GC就会认为该对象可以被回收。
为了更好的理解GC的工作原理,我们可以把对象假设为有向图的顶点,把引用关系看做是有向图的边,有向图的边从引用者指向被应用对象。另外,每个线程对象都可以看做一个有向图的起始点,例如大多数程序都是从main()进程开始执行,那么该图就是以main()进程顶点开始的一颗根树,在这个有向图中,根顶点可以到达的对象都是有效对象,GC将不会回收这些对象;如果某个对象与根节点不可到达,那么我们就认为这个对象将不再被引用,可以被GC回收。如下图所示:
如上图所示,GC会选择一些它了解还存活的对象作为内存遍历的根节点(GC Roots),比如说thread stack 中的变量,JNI中的全局变量,zygote中的对象(class loader)等,然后开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,就会被GC回收掉,如下图蓝色部分:
Java使用有向图的方式进行内存管理,这样可以消除循环引用的问题,例如有三个对象A、B和C,在A中引用了B,在B中引用了C,在C中应用了A,只要它们和根进程不可到达,那么GC也是可以回收它们的,这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构建,它与有向图相比,精度很低(很难处理循环引用的问题),但是执行效率很高。
Android的内存溢出
Android的内存溢出是如何发生的了?我们知道Android的虚拟机是基于寄存器的Dalvik,在不设置largeHeap的情况他它的最大堆内存空间为16M,有的设备为24M,根据不同的手机厂商该值是不同的,在设置了largeHeap的情况下该值会更大些但是也是有限制的,因此我们APP所能利用的内存空间是有限的。如果我们APP的内存超过了警戒线Android系统就会抛出OutOfMemory的异常错误,为什么会出现OOM的异常错误了?根据我们的开发经验主要原因有两个:一个是我们程序的bug导致资源得不到释放造成内存泄露(比如长期保持对某些资源的引用);另一个是保存了多个耗用内存过大的对象(比如Bitmap等)。
Android常见内
显示全部