Android App内存优化简单说明

说明

本文是学习内存优化时个人的总结,由于本人是刚开始接触Android的性能优化方面的知识,肯定有很多知识点上的不足和错漏,请各位谅解。

App内存组成以及限制

Android 给每个 App 分配一个 VM ,让App运行在 dalvik 上,这样即使 App 崩溃也不会影响到系统。系统给 VM 分配了一定的内存大小, App 可以申请使用的内存大小不能超过此硬性逻辑限制,就算物理内存富余,如果应用超出 VM 最大内存,就会出现内存溢出 crash 。由程序控制操作的内存空间在 heap 上,分 java heapsize 和 native heapsize
Java申请的内存在 vm heap 上,所以如果 java 申请的内存大小超过 VM 的逻辑内存限制,就会出现内存溢出的异常。native层内存申请不受其限制, native 层受 native process 对内存大小的限制。那么如何查看系统对APP的内存限制呢?
(1)如果你的手机root过,我们可以通过 adb shell 在 命令行窗口查看,命令如下:
adb shell cat /system/build.prop

image.png

这里主要关注三个属性即可:
1.heapstartsize:App启动的初始分配内存
2.heapgrowthlimit:APP能够分配到的最大限制
3.heapsize:开启largeHeap=‘true’的最大限制
作为应用的开发者,这几个值我们是无法改变的(root过或者手机系统开发者除外),我呢只需要知道有这么几个值即可。
(2)通过代码获取

ActivityManager activityManager =(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)
activityManager.getMemoryClass();//以m为单位

Android内存分配与回收机制

内存分配
Android的Heap空间是一个 Generational Heap Memory 的模型,最近分配的对象会存放在 Young
Generation 区域,当一个对象在这个区域停留的时间达到一定程度,它会被移动到 Old
Generation ,最后累积一定时间再移动到 Permanent Generation 区域。


image.png

1、Young Generation(新生代)
由一个Eden区和两个Survivor区组成,程序中生成的大部分新的对象都在Eden区中,当Eden区满时,还存活的对象将被复制到其中一个Survivor区,当次Survivor区满时,此区存活的对象又被复制到另一个Survivor区,当这个Survivor区也满时,会将其中存活的对象复制到年老代。
2、Old Generation(老年代)
一般情况下,年老代中的对象生命周期都比较长。
3、Permanent Generation(持久代)
用于存放静态的类和方法,持久代对垃圾回收没有显著影响。

总结:内存对象的处理过程如下:
1、对象创建后在Eden区。
2、执行GC后,如果对象仍然存活,则复制到S0区。
3、当S0区满时,该区域存活对象将复制到S1区,然后S0清空,接下来S0和S1角色互换。
4、当第3步达到一定次数(系统版本不同会有差异)后,存活对象将被复制到Old Generation。
5、当这个对象在Old Generation区域停留的时间达到一定程度时,它会被移动到Old
Generation,最后累积一定时间再移动到Permanent Generation区域。

系统在Young Generation、Old Generation上采用不同的回收机制。每一个Generation的内存区域都
有固定的大小。随着新的对象陆续被分配到此区域,当对象总的大小临近这一级别内存区域的阈值时,
会触发GC操作,以便腾出空间来存放其他新的对象。
执行GC占用的时间与Generation和Generation中的对象数量有关:
Young Generation < Old Generation < Permanent Generation
Gener中的对象数量与执行时间成正比。

4、Young Generation GC
由于其对象存活时间短,因此基于Copying算法(扫描出存活的对象,并复制到一块新的完全未使用的控件中)来回收。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在Young Generation区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。
5、Old Generation GC
由于其对象存活时间较长,比较稳定,因此采用Mark(标记)算法(扫描出存活的对象,然后再回收未被标记的对象,回收后对空出的空间要么合并,要么标记出来便于下次分配,以减少内存碎片带来的效率损耗)来回收。

可回收对象的判定

可达性算法:
从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。


image.png

Java定义的GC Roots对象:
虚拟机栈(帧栈中的本地变量表)中引用的对象。
方法区中静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI引用的对象。


image.png

GC类型

kGcCauseForAlloc:分配内存不够引起的GC,会Stop World。由于是并发GC,其它线程都会停止,直到GC完成。
kGcCauseBackground:内存达到一定阈值触发的GC,由于是一个后台GC,所以不会引起Stop World。
kGcCauseExplicit:显示调用时进行的GC,当ART打开这个选项时,使用System.gc时会进行GC。

GC算法

1.标记清除算法
分为两步
标价: 标记的过程其实就是,遍历所有的GC Roots,然后将所有的 GC Roots可达的对象标记为存活的对象。
清除:清除的过程将遍历堆中所有的对象中没有标记的对象全部清除掉

image.png

特点:
(1)扫描两次
(2)位置不连续,存在碎片
(3)两遍扫描

2.复制算法
描述:
(1)复制算法将内存划分为两个区间,在任意时间点,所有动态分配的对象都只能分配在其中一个区间(称为活动区间),而另外一个区间(称为空闲区间)则是空闲的。
(2)当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。接下来GC线程会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。
(3)此时,空闲区间已经与活动区间交换,而垃圾对象现在已经全部留在了原来的活动区间,也就是现在的空闲区间。事实上,在活动区间转换为空间区间的同时,垃圾对象已经被一次性全部回收。
特点:
(1)实现简单,运行高效
(2)空间利用率只有一半
(3)没有碎片

image.png

3.标记整理算法
描述:
y与标记/清除算法类似,分为两步
(1)标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
(2)整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
特点:
(1)没有内存碎片
(2)效率偏低
(3)两遍扫描,指针需要移动


image.png

Android低内存杀进程机制

Anroid基于进程中运行的组件及其状态规定了默认的五个回收优先级:


image.png

Empty process(空进程)
Background process(后台进程)
Service process(服务进程)
Visible process(可见进程)
Foreground process(前台进程)

系统需要进行内存回收时最先回收空进程,然后是后台进程,以此类推最后才会回收前台进程(一般情况
下前台进程就是与用户交互的进程了,如果连前台进程都需要回收那么此时系统几乎不可用了)。


image.png

ActivityManagerService 会对所有进程进行评分(存放在变量adj中),然后再讲这个评分更新到内核,由内核去完成真正的内存回收( lowmemorykiller , Oom_killer )。这里只是大概的流程,中间过程还是很复杂的

什么是OOM

OOM(OutOfMemoryError)内存溢出错误,在常见的Crash疑难排行榜上,OOM绝对可以名列前茅并且经久不衰。因为它发生时的Crash堆栈信息往往不是导致问题的根本原因,而只是压死骆驼的最后一根稻草。


image.png

OOM分类

image.png

内存泄露的解决方法

1.常见的分析工具
(1)MAT
(2)Memory Profile
(3)LeakCanary

Memory Profile检测内存泄露
首先我们写一个测试Demo,在MainActivity中打开SecondActivity,在伴随对象中持有SeconnActivity的实例,然后关闭SecondActivity的实例。secndActivity的到代码如下:

class SecondActivity : AppCompatActivity() {

    private lateinit var mButton: AppCompatButton
    private var mWeakRef: WeakReference<String>? = null


    companion object {
        var context123: Context? = null
        var weakReferenceObj: WeakReference<String>? = null
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        context123 = this
        setContentView(R.layout.activity_second)
        mButton = findViewById(R.id.btn_click)
        mWeakRef = WeakReference("lala")
        weakReferenceObj = mWeakRef
        mButton.setOnClickListener {
            finish()
        }
    }
}

首先我们用Memory Proflile 分析该内存泄露
步骤如下
(1)运行项目
(2)点击Profile进入分析界面,点击左上角的+添加分析的项目


image.png

image.png

(3)绑定成功回进入分析界面,点击memory,进入内存分析


image.png

(4)打开SecondActivity页面后在关闭该页面,点击Capture head dump,再按下record捕捉内存视图
image.png

(5)选择show activity/fragment Leaks 既可以看到发生内存泄露的相关activity或fragment
image.png

(6)点击下方的Instance List的相关实例,点击Reference便可以看到相关对象的持有情况。
image.png

(7)分析相关的引用持有情况,这里可以看到,我们自己写的mContext123,分析该mContext何时被赋值的

 companion object {
        var context123: Context? = null
        var weakReferenceObj: WeakReference<String>? = null
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        context123 = this
        setContentView(R.layout.activity_second)
        mButton = findViewById(R.id.btn_click)
        mWeakRef = WeakReference("lala")
        weakReferenceObj = mWeakRef
        mButton.setOnClickListener {
            finish()
        }
    }

发现该mContext123持有了一个SecondActivity的实例,当该SecondActivity对象想要执行销毁时,因为被mContext123持有而无法被销毁,从而造成了内存泄露。


image.png

至此Meomery Profile的内存泄露检测说明完毕

LeakCanary检测内存泄露
(1)首先引入LeakCanary

    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

(2)然后运行项目,LeakCanary会自动检测内存泄露的点,如果检测到会在通知栏显示一条通知


image.png

(3)点击后进入Leack Canary 可以看到发生了泄露


image.png

(4)点击该条目,可以看到发生泄露的点,可以看到与Memory Profile找的泄漏点一致。
image.png

至此,LeakCanary检测内存泄露的说明讲解完毕

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342