前言
内存泄漏可以说是安卓开发中常遇到的问题,追溯和排查其问题根源是进阶的程序猿必须具备的一项技能。小盆友今天便与大家分享一下这方面的一些见解,如有理解错误或是不同见解,可以于评论区留言我们进行讨论,如果喜欢给个赞鼓励下吧。
目录
1、JAVA内存解析
2、JAVA回收机制
3、四种引用
4、小结
5、安卓内存泄漏排查工具
6、内存泄漏检查与解决流程
7、常见的内存泄漏原因
1、JAVA内存解析
要想知道内存泄漏,需要先了解java中运行时内存是怎么构成的,才能知道是哪个地方导致。话不多说,先上图
运行时的java内存分为两大块:线程私有(蓝色区域)、共享数据区(黄色区域)
线程私有:主要用于存储各个线程私有的一些信息,包括:程序计数器、虚拟机栈、本地方法栈
共享数据区:主要用于存储公用的一些信息,包括:方法区(内含常量池)、堆
-
程序计数器:让程序中各个线程知道自己接下来需要执行哪一行。在java中多线程为抢占式(因为cpu在某一时刻只会执行一条线程),当线程切换时,需要继续哪一行便由程序计数器告知。
举个例子:A、B两条线程,此时CPU执行从A切换至B,过了段时间从B切换回A,此时A需要从上次暂停的地方继续执行,此时从哪一行执行就是由程序计数器来提供。
值得一提:
(1)若执行java函数时,程序计数器记录的是虚拟机字节码的地址;
(2)若执行native方法时,程序计数器便置为了null。
(3)在java虚拟机规范中,程序计数器是唯一没有定义OutOfMemoryError。 -
虚拟机栈:描述的是java方法的内存模型,平时说的“栈”其实就是虚拟机栈,其生命周期与线程相同。每个方法(不包含native方法)执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
值得一提:在java虚拟机规范中,此处定义了两个异常
(1)StackOverFlowError (在递归中常看到,递归层级过深)
(2)OutOfMemoryError -
本地方法栈:是为虚拟机使用到的Native方法提供内存空间。 有些虚拟机的实现直接把本地方法栈和虚拟机栈合二为一,比如主流的HotSpot虚拟机。
值得一提:在java虚拟机规范中,此处定义了两个异常
(1)StackOverFlowError (在递归中常看到,递归层级过深)
(2)OutOfMemoryError -
方法区:主要存储已加载是类信息(由ClassLoader加载)、常量、静态变量、编译后的代码的一些信息。 GC在这里比较少出现在这块区域。
-
堆:存放的是几乎所有的对象实例和数组数据。 是虚拟机管理的最大的一块内存,是GC的主战场,所以也叫“GC堆”、“垃圾堆” 。
值得一提:在java虚拟机规范中,此处定义了一个异常
(1)OutOfMemoryError -
运行时常量池:属于“方法区”的一部分,用于存放编译器生成的各种字面量和符号引用。
字面量:与Java语言层面的常量概念相近,包含文本字符串、声明为final的常量值等。
符号引用:编译语言层面的概念,包括以下3类:
(1) 类和接口的全限定名
(2)字段的名称和描述符
(3)方法的名称和描述符
2、JAVA回收机制
java中是通过GC(Garbage Collection)来进行回收内存,那jvm是如何确定一个对象能否被回收的呢?这里就需讲到其回收使用的算法
(1) 引用计数算法
引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
优点:
引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点:
无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。例如下面代码片段中,最后的Object实例已经不在我们的代码可控