Android内存优化

不是所有GC指令都执行的又快又好,以下将介绍内存及它如何影响系统运行。

普遍认为 多数程序语言接近硬件或高性能,如C、C++和Fortran。通常工程师会自己管理内存,高级工程师对内存的分配会慎重处理,并在结束使用时再次分配。一旦确认何时及怎样分配内存,内存管理的质量就依赖于工程师的能力(技能跟效率) ,责任重大。
实际情况是工程师不都会去追踪那些零碎的内存碎片,程序开发是件混乱又疯狂的过程,内存通常都没办法完全被释放,这些无法释放的内存叫做内存泄漏(Memory Leak),内存泄漏占用了大量资源,这些资源其实可以更好地使用
Android内存优化_第1张图片

这些语言在运行时追踪内存分配,以便当程序不再需要时释放系统内存,完全不用工程师亲自操作。
Android内存优化_第2张图片

内存回收再内存管理环境下叫垃圾清理,这个概念是在1959年,当初为了解决lisp语音问题,由John McCarthy发明的。

垃圾清理的基本概念:

  1. 找到无法存取的数据。例如所有不受指令操控的内存
  2. 回收被利用过的资源。

原理很简单,但是在上百万行代码和4gigs(?)的分配,在实际操作时却非常困难。如果现在,在程序中有20000个分配,垃圾清理定会让人困惑:哪一个是没用的?何时启动垃圾清理释放内存?
Android内存优化_第3张图片

这些问题其实很复杂,但解决方法就是Android Runtime中的垃圾清理,比McCarthy最初的方法更高级,速度快且是非侵入性的。通过分配类型及系统如何有效地组织分配以及利用GC的运行,并重新配置。所有影响android runtime的内存堆都被分割到不同空间中,根据这些不同内存空间的特点决定哪些数据适合放到什么空间。(L以下DALVIK L及以上版本L Android runtime)L版本号为5.0/5.1
Android内存优化_第4张图片
最重要的一点,每个空间都有预设的大小,在分配内存时要追踪综合大小,如果空间不断扩大,系统需要执行垃圾清理以确保内存分配(新的)正常运行。
Android内存优化_第5张图片

注意使用不同的Android runtime GC的运行方式就会不同。例如Dalvik中很多GC是停止全局事件,意味着很多指令的运行直到操作完成才会停止。
Android内存优化_第6张图片
当这些GCs所用时间超过一般值,或者一大堆任务一起执行会消耗大量的帧像时间(Android 每帧16ms)。

首先程序在任意帧内执行GCs所用的时间越多,用于更新渲染界面的时间就越少。
Android内存优化_第7张图片
如果有许多GCs或一大串指令一个接一个地操作,一旦GC的时间和跟新渲染的时间超过16ms,就会出现掉帧现象,如果出现一次掉帧,会导致隐形的卡顿可闪动,用户就会感觉画面不是很顺畅,如果出现多次掉帧,用户就会明显感觉到卡顿。
Android内存优化_第8张图片

其次指令流程可能造成GCs强制执行的次数增多或执行时间超过正常数值。
例如:在一个长期运行的最里层循环内创建对象,很多数据就会占用(污染)堆内存,马上就会有许多GCs启动,由于这一额外的内存压力,虽然内存环境管理良好,但内存泄漏仍会产生。内存泄漏的常见情形可参照《Android 内存泄漏分析心得》
Android内存优化_第9张图片

泄漏的内存无法被释放,导致可用内存降低。迫使一段周期内有更多的GC事件的执行。
Android内存优化_第10张图片
如果要减少任意帧内启动GC的次数,需要着重优化程序的内存使用。

Java不用手动管理内存,但这样也会造成一些不易发现的问题。
划分到Android运行时的堆内存,是根据声明类型和利于垃圾清理的角度来分配的,每一块区域都有预设内存空间,当所需内存接近上限时GC就会启动。当花费在GC上的时间越多,在其他事情花费的时间就会越少(渲染,播放,发送录音等)。

内存泄漏是造成垃圾清理的常见因素。内存泄漏是指:无用对象无法被垃圾收集器识别出来,一直在堆内存中,占用内存空间,无法被删除。随着内存不断泄漏,可用空间不断变小,会导致GC的次数越来越多。
Android内存优化_第11张图片

很容易造成内存泄漏的有:对没有使用对象的循环引用。
Android内存优化_第12张图片
还有一些复杂的:类载入器未完成时就强制执行
这里也有介绍《Android 内存泄漏分析心得》
下面介绍一些内存分析工具
Heap Viewer :DDMS工具之一,一个可以查看内存状态、空间占用率的情况 通过Heap Viewer 可以知道特定时间内的内存使用量。
Heap Viewer启动方式:Android Device Monitor -> DDMS->Heap
Allocation Tracker:DDMS工具之一。追踪内存分配信息,按顺序排列,这样我们就能清晰看出来某一个操作的内存是如何一步一步分配出来的。比如在有内存抖动的可疑点,我们可以通过查看其内存分配轨迹来看短时间内有多少相同或相似的对象被创建,进一步找出发生问题的代码。
使用条件
• Root手机
• 开发者选项可用
当然Android Studio 3.0采用全新的Android Profiler 窗口取代 Android Monitor 工具,详情产考官方文档https://developer.android.com/studio/profile/

参考Android性能优化(四)之内存优化实战
关于Android面试中如何应对内存优化
Manage your app’s memory
Java编程中尽可能要做到的一些地方
内存优化的套路:

解决所有的内存泄漏

集成LeakCanary,可以方便的定位出90%的内存泄漏问题;
通过反复进出可疑界面,观察内存增减的情况,Dump Java Heap获取当前堆栈信息使用MAT进行分析。
内存泄漏的常见情形可参照《Android 内存泄漏分析心得》
handler造成内存泄漏解决方案(使用静态内部类+WeakRefrence。静态内部类生命周期)
单例造成的内存泄露(单例的静态特性导致其生命周期同应用一样长。)
1. 原因:单例持有Activity Service 的Context,持有View
解决方案:.如果传入Context,使用ApplicationContext;
解决方案:使用弱引用的方式持有的View。

**内部类和匿名内部类造成的内存泄漏**
在Java中,非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类,但是,静态内部类却不会。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。
解决方案:

1. 将内部类变成静态内部类;
2. 如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用;
3. 在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务(如匿名的Thread)
Activity Context 的不正确使用
静态变量持有Activity的引用,静态变量生命周期大于Activity的生命周期 导致Activity无法被回收,而造成内存泄露。

  1. 使用ApplicationContext代替ActivityContext,因为ApplicationContext会随着应用程序的存在而存在,而不依赖于activity的生命周期;
    2.对Context的引用不要超过它本身的生命周期,慎重的对Context使用“static”关键字。Context里如果有线程,一定要在onDestroy()里及时停掉。

case 5. 注册监听器的泄漏
系统服务可以通过Context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果Context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有Activity 的引用,如果在Activity onDestory时没有释放掉引用就会内存泄漏
1.使用ApplicationContext代替ActivityContext;
2.在Activity执行onDestory时,调用反注册;
case 6. Cursor,Stream没有close,View没有recyle

case 7. 集合中对象没清理造成的内存泄漏
在Activity退出之前,将集合里的东西clear,然后置为null,再退出程序。

case 8. WebView造成的泄露
当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其占用的内存长期也不能被回收,从而造成内存泄露。

case 9 ListView 构造Adapter时,没有使用缓存的ConvertView

避免内存抖动

原因:大量的对象被创建又在短时间内马上被释放。
避免在循环中创建临时对象;
避免在onDraw中创建Paint、Bitmap对象等。
Bitmap的使用
使用三方库加载图片一般不会出内存问题,但是需要注意图片使用完毕的释放,而不是被动等待释放。
使用优化过的数据结构
HashMap,ArrayMap,SparseArray源码分析及性能对比
编程语言提供的某些类未针对在移动设备上使用进行优化。例如,通用 HashMap实现的内存效率可能非常低,因为每个映射都需要单独的入口对象。

Android框架包括若干优化的数据的容器,其中包括 SparseArray,SparseBooleanArray,和LongSparseArray。例如,这些SparseArray类更高效,因为它们避免了系统需要自动 复制 密钥并且有时需要值(这会为每个条目创建另一个对象或两个对象)。

如有必要,您可以随时切换到原始数组以获得精简的数据结构。

使用onTrimMemory根据不同的内存状态做相应处理
Library的使用
去掉无用的Library,对生成的Apk进行反编译查看使用到的Library,避免出现无用的Lib仍然被打进Apk;
避免引入巨大的Library;
使用Proguard进行混淆、压缩。

你可能感兴趣的:(android)