毫无疑问,内存是我们的应用程序非常宝贵的资源。如何充分的合理的利用内存是程序员的必修课。本文采用提出疑问,解决疑问的方式对Android应用内存优化工作进行阐述。在文末尽量会列出参考文章和链接。

 

综述:Android采用Java语言,Java运行于JVM之中,JVM又实现了自己的内存回收机制。因此,本文会回答如下问题(每个问题想深入学习请自行搜索对应关键词):

1、JVM的内存模型是什么样的?

2、JVM的GC机制是什么?

3、GC roots有哪些?

4、为何会出现内存泄漏,如何避免内存泄漏?

5、Android中如何做内存优化?

 

 

一、JVM的内存模型(关键词:JVM内存模型)




Android 内存在线转换_内存泄漏


图 1.0 Jvm内存模型图

()

图1.0为jvm的内存模型,从图中可以看到jvm内存被划分五个逻辑块:程序计数器,本地方法栈,虚拟机栈,方法区,堆。其中,程序计数器不会抛出内存溢出,本地方法栈和虚拟机栈会出现StackOverflowError、OutOfMemerryError,方法区和堆会出现OutOfMemeryError,如下图1.2 JVM内存模型中各个模块关系及会出现的异常.

Android 内存在线转换_android_02

 

图1.2 JVM内存模型中各个模块关系及会出现的异常

JVM会自动对Java堆进行内存管理,所以本文也会重点讨论堆内存回收问题。

 

二、JVM的GC机制是什么?(关键词:JVM GC算法)

 

Android 内存在线转换_内存泄漏_03

图2.0  GCroot可达性示意图

既然,JVM要负责垃圾回收,那它首先要做一件事情:就是判断堆中哪一对象是无用对象,是可以回收的对象。图2.0 GCroot可达性示意图,简要的说明了这一点。在发起内存回收的时候,应用停掉所有工作(stop the world),然后遍历每一个GCroot,并将其能到达的每一个对象视为有用对象,不可到达的对象视为垃圾,并将其回收。至于更详细的,内存中对象移动,在这里不深讨论。从图2.0中可以看到,GCroot1持有o1的引用,o1持有o2,o4的引用,o2持有o3的引用,说明GCroot1可以以引用链的形式直接或间接的到达堆内存中的o1,o2,o3,o4;同样可判断,o6,o7也是可以通过引用链到达的。而o5,o8,o9则GCroot无法通过引用链到达。所以,在这一次发起的内存回收中,o5,o8,o9将被视为垃圾回收掉,其占用的内存将得到释放。

 

三、GCroots有哪些呢?(关键词GC Root)

我们知道如何判断哪些对象有用,哪些无用之后,很自然产生这样一个疑问,我们该从哪里开始判断呢,即哪些是GCroots呢,为什么以他们作为GCroot呢?

GCRoots有如下:()

1.Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots

2.Thread - 所有该进程中活着的线程

3.Stack Local - Java方法的local变量或参数

4.JNI Local - JNI方法的local变量或参数

5.JNI Global - 全局JNI引用

6.Monitor Used - 用于同步的监控对象

7.Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。

以上七点看完之后可能又懵了,但其实理解起来也不是那么复杂,概括点说就是除了JVM内存模型中的堆之外的模块内的东西都可以被考虑成GCroot。比如class存在于方法区,其它也都分别存在于除了堆之外的内存块中。再反过来说:堆里面没有GCRoot。毕竟我们就是判断堆里的对象是不是垃圾。


四、为何会出现内存泄漏,如何避免内存泄漏?(关键词:Android内存泄漏)

什么是内存泄漏呢?个人理解:当业务逻辑上,某个对象不再被需要,而一次或者多次内存回收操作之后仍没能将其回收。该对象仍然占用内存资源,此时我们就认为出现了内存泄漏。传统的内存泄漏是由忘记释放分配的内存导致的,而逻辑上的内存泄漏则是由于忘记在对象不再被使用的时候释放对其的引用导致的。

内存泄漏有什么危害呢?如果大量的内存泄漏,当我们需要分配内存的时候,系统会频繁的发起GC操作,而GC操作是一项耗时操作,会导致应用出现明显的卡顿,如果严重的内存泄漏,甚至会直接出现内存溢出而应用崩溃。

应用卡顿和崩溃都不是我们想看到的,那如何避免内存泄漏呢?

内存泄漏的实际情况多种多样:(八种常见内存泄漏:http://blog.nimbledroid.com/2016/05/23/memory-leaks-zh.html)我们只要针对内存泄漏的产生的原因就可以减少内存泄漏:明确知道后面不会再用到的对象及时释放内存,长生命周期尽量不要持有段生命周期的引用,尤其是较大对象。(看起来是空话,确实也是空话,第四和第五另找时间详细填坑)


五、Android中内存优化(关键词:Android内存优化)

 

在想想自己要攒钱的时候,我们通常会想到这样一个词语,开源节流。我们把自己的小金库抽象成了一个容器,有流向资金库的源,和流出资金库的流。当我们想要攒钱时后,想办法让流入资金库的源增加,减少流出资金库的流,而最主要就是控制住入口和出口。我们在Android开发中,内存资源是十分宝贵的有限的,我们想要保证应用的体验足够好,就需要做到“节源开流”。所谓节源就是尽量让最必须的对象和资源进入内存,所谓开流就是尽早及时的清除释放不会再使用的对象。

Android 内存在线转换_Android 内存在线转换_04

 

图5.0内存出入抽象示意图

接下来从两个角度去优化合理使用内存:1、内存入口:节源,2、内存出口:开流;

 

5.1、从内存入口角度分析如何合理利用内存

我们作为内存入口的把控者,如何才能让尽可能少而小的对象进入内存呢?

1、尽量减少新分配出来的对象占用内存的大小,尽量使用更加轻量的对象。

1)使用轻量级的数据结构

2、内存中已有的对象重复利用

1)复用系统资源

 

5.2 从内存出口角度分析如何合理利用内存

1)及时关闭和释放:及时的释放系统资源,及时关闭数据库查询cursor,及时释放不会再用的图片,

2)避免逻辑内存泄漏:如匿名内部类持有外部类的引用导致的内存泄漏,静态对象导致的内存泄漏

3)及时合理的利用好系统回调:onLowMemory()与onTrimMemory()

 

 

参考:

http://blog.nimbledroid.com/2016/05/23/memory-leaks-zh.html

http://blog.nimbledroid.com/2016/05/23/memory-leaks-zh.html

https://en.wikipedia.org/wiki/Paging


https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

http://book.51cto.com/art/201306/399197.htm


http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0920/3478.html