美文网首页
Android App内存优化简单说明

Android App内存优化简单说明

作者: 取了个很好听的名字 | 来源:发表于2021-11-01 16:31 被阅读0次

    说明

    本文是学习内存优化时个人的总结,由于本人是刚开始接触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检测内存泄露的说明讲解完毕

    相关文章

      网友评论

          本文标题:Android App内存优化简单说明

          本文链接:https://www.haomeiwen.com/subject/hzpialtx.html