《Android高性能编程》-内存笔记

《Android高性能编程》-内存笔记

2018, Apr 19    

预览

内存如何工作

垃圾回收

内存共享

android并未使用虚拟内存技术。这就导致,除了释放不再使用的对象外,没有其他方法能够为程序提供更多控件。
android中的共享内存是为了再大量不同的进城之间更好的处理资源。

paging和memory-mapping技术:paging是指再二级存储中奖内存分成大小相同的块(page)。memory-mapping是指在内存中通过映射将二级存储中的相关文件关联起来,当做主存来使用。系统通过分页内存映射文件来保存Dalvik代码文件、应用程序资源或原生代码文件,当系统需要为其他进程分配内存时,这些文件可以再多个进程之间共享。

运行时

Dalvik,Android runtime(ART,api21开始)

运行时垃圾回收:当内存的使用达到上限时,垃圾回收启动它的任务,暂停其他每个方法、任务、线程和进程的执行。当垃圾回收结束后,这些对象才会恢复执行。
垃圾回收执行的时间越多,留给系统用于准备那些要被渲染到屏幕上帧的时间就越少。

ART减少了垃圾回收的步骤,为位图对象添加了一个特殊的内存块,使用了更新更快的算法。

AndroidN即时编译器

ART运行时使用的提前编译(android-of-time compilation, AOT), 在应用程序首次安装时执行编译。优势:电量耗费下降,执行速度更快,改善了内存管理和垃圾回收;代价:安装时间长。

AndroidN的即时编译器(justi-in-time compiler, JIT),应用程序执行期间只在需要时编译,使用了代码分析技术,作为AOT的补充。

配置文件指导编译:能够根据应用程序及以设备的实际使用情况,管理每个应用的AOT/JIT编译。

内存泄漏

一个不再使用的对象被另一个还存活着的对象引用。

内存抖动

在短时间内,大量的新对象被实例化,运行时无法承担内存分配,垃圾回收时间大量调用。

引用

nromal > soft > week > phantom

内存相关的项目

Butter:垂直同步与缓冲区,改善设备中的响应能力, 16

Svelte:减少了内存占用,内存管理方面改进,支持低内存设备 17

Volta:设备电池的生命周期,21

AndroidN中的变化:网络变化的广播只有通过动态注册并且处于前台才会接收;拍照存储后不再触发new_picture的action,视频同。

开发应用程序时,应避免使用隐式广播,以减少他们对后台操作带来的影响,因为这会导致非必要的内存浪费和电量消耗。

最佳实践

数据类型

使用自动装箱会带来许多额外消耗,应尽量避免。

Sparse数组集:SparseXXXArray对应 HashMap<Integer, XXX>,引申SparseArray和HashMap的原理,SparseArray所使用的内存区域是连续的,经过压实(Compaction)和调整(Resize)。优势:内存高效,能够通过索引进行迭代不涉及对象;劣势:不如HashMap快,因为HashMap在避免冲突上有很好的实现方式。节省内存空间需要以牺牲CPU计算量作为代价。(1. 量1000以内且无大量增删,2. 量不大但是存在大量迭代)

ArrayMap:折中的最佳解决方案。

语法

集合: for(item: T : set)最高效,应尽量避免使用iterator
枚举:4个枚举值会被转换成4个对象,分别有name, ordinal, array 和一个包装类。应避免使用枚举,采用 @intDef .但混淆器可以帮助我们减少枚举对应用程序性能带来的影响。

常量

代码中的常量应该用static和final进行修饰,会被存储在DEX文件中,不需要过多的内存分配,仅适用于基本类型和字符串常量。原理:

对象管理

少量有用的对象,比大量很少使用的对象更加内存安全,尽量少创建临时对象,避免实例化非必要的对象。

String string = new String (“dgafaf”); 2个对象

StringBuffer(线程安全的,慢)和StringBuilder;已知大小应实例化时指定。

本地变量提取出来,只需要被分配一次。
如果所处理对象的数量固定不变,原始的数组比集合更加内存高效。释放I/O流。在使用try-catch语句时,总是应该用finally关键字对内存进行释放。

内存设计模式

目标:在需要大量使用对象时,减少内存分配,需要注意不要引入内存泄漏

对象池模式
【目的】面对需要大量分配高成本对象时,通过对象重用尽量减少内存分配以及垃圾回收对系统产生的影响(避免内存抖动),【方法】避免一个将来可能会被重用的对象进行垃圾回收。
【包括】:(1)可重用对象(ReusableObject)(2)客户(3)对象池(ObjectPool):单例对象,有上限,可以再创建时预先分配一定数量的可重用对象。 注意:在用完后应尽快归还,不然会因为不能再创建而造成用户一直等待;在传递给另一个客户前需要恢复成一致状态。
【实现】:ObjectPool中保存两个SparseArray,freePool中放置可重用对象,lentPool正在被使用的对象池,在初始化时在freePool中创建一定的对象,在获取时遍历freepool找到不为空的对象就返回,注意要加锁保证使用中不被改变,<在找到后>将其从freePool中移除,放入lentPool中,如果<未找到>就根据是否已满(已被使用的和freeSize和)来决定能否创建一个新的。释放时相反,从放入freePool从lentPool中移除。这个遍历有什么意义?找不到不就是空的了么,size为0啊

例子:AsyncTask,RecyclerView

享元模式
【目的】通过节省所有对象共享的状态,以减少载入内存的量。【方法】对每个对象只创建一个实例,实现内部状态的重用。【适用】大量粒度小,变化小,对象之间相似的情况。
两种状态类型:内部状态(唯一标识对象),外部状态(可共享字段)
三个角色:享元对象(FlyWeightObjects),享元工厂(FlyWeightFactory),客户
【实现】:FlyWeightFactory中保存一个SparseArray,存储不同类型的对象,Client调用时返回特定类型的对象,根据需要调用方法。

android组件泄漏

静态字段
【现象】静态持有Acitivity
【现象】静态View,View需要关联Activity,静态就会造成内存泄漏

非静态内部类 【现象】非静态内部类 内部类需要再它的整个生命周期内都能访问外部类,所以Activity被销毁但是内部类仍在工作时就会发生泄漏,例如AsyncTask。(而且可能因为activity中view被销毁而导致崩溃。) 【方法】弱引用,如果使用的asyncTask可以在ondestroy中销毁

单例

【现象】单例中持有activity 【方法】弱引用,在ondestroy中取消这种关联

匿名内部类 匿名内部类同样需要依赖外部类,方法同上

Handlers

postdelay,handlers持有一个runnable匿名内部类,持有外部类的引用,而handler中的looperthread利用消息队列来执行runnable,未执行activity就被销毁时,就会造成内存泄漏。是不是延迟消息,队列太大都有可能造成内存泄漏。

【方法】将runnable导出为static类,并且其中不能引用activity内的view组件,不然一样会出现问题,如果有这种情况需要用弱引用,并且在使用view的时候判断是否未空。在ondestroy中移除所有的消息,对性能也有帮助。

Service 让一个不再使用的Service保持活跃就会阻碍系统清理空间,所以一定要记得关闭停止(内部用stopself,外部用stopService),尽量使用intentservice,它会再后台工作执行后自动结束。

进程

通过不同的进程分离内存的载入,这种情况需要脱饭管理内存,否则会增加消耗。

  1. 减少进程间的耦合,少用公共对象 2.ui只在一个进程中进行
  2. 进程被依赖是无法删除的,所以小心使用那些能访问多个进程的组件,比如ContentProvider和Service

内存API

<application android:largeHeap=”true”/>会向系统发送请求,并不能保证进程得到的堆空间多。但堆内存越高,内存回收的限制点也越高,回收需要花费的时间也越多。永远不要通过这种方法避免OOM ActivityManager可以用来获取当前内存的消耗/可用情况,getMemoryCloass, getMemoryInfo等。

System.gc(), 可以任意地方使用但不保证会触发。

主要组件和内存管理

BroadcastReceiver是四大组件中唯一不需要特定内存管理的组件,其生命周期只和onReceiver()方法有关 其他的㢟都实现了ComponentCallback接口,这个接口中OnLowMemory方法在系统处于低内存状态时,开始杀死进程前,会调用该方法。但是开始杀死进程时调用已经不合适,所以出现了ComponentCallback2接口,其中onTrimMemory()为我们提供了内存消耗的各种临界值。LRU策略来进行回收。visible,invisible对应的lru位置。

状态 可见? 内存状态 LRU位置
TRIM_MEMORY_RUNNING_MODERATE 可见 内存较少 LRU顶部
TRIM_MEMORY_RUNNING_LOW 可见 内存低 LRU顶部
TRIM_MEMORY_RUNNING_CRITICAL 可见 内存紧张 LRU顶部
TRIM_MEMORY_UI_HIDDEN 不可见 —— LRU顶部
TRIM_MEMORY_BACKGROUNG 不可见 内存低 LRU顶部
TRIM_MEMORY_MODERATE 不可见 内存低 LRU中部
TRIM_MEMORY_COMPLETE 不可见 内存低 LRU底部

当系统要开始杀死进程时,它会分析程序内对内存的消耗情况,以决定杀死哪个,所以占内存越少越不容易被杀死且能快速恢复。

调试工具

Logcat

Dalvik:D/dalvikvm: ,,, ART: I/art: () AllocSpace Objects, () LOS objects art只打印那些主动请求的GC事件以及GC暂停时间超过5毫秒,或执行时间超过100毫秒的事件

用的时候查去吧!

ActivityManager

setWatchHeapLimit 设置监控的最大内存,当堆的大小达到指定值时,会自动生成一个堆转储,能够分析是否有内存泄漏 clearWatchHeapLimit

ACTION_REPORT_HEAP_LIMIT 并发送这个action 23

StrictMode

开启后会检测特定策略发出通知。
可监控的事件:activity泄漏,存在未被关闭可被关闭的对象,context销毁时时候有serviceconnection或broadcastReceiver泄漏,其他异常行为等
通知的方式:杀掉进程,发送到dropboxmanager 记录到logcat中
在测试或调试版本中开启,因为会消耗一定性能。

Dumpsys

adb shell dumpsys meminfo 查看内存信息 PSS( Proportional Set Size ) 应用程序占用的内存总大小 procstats 查看进程的内存信息 VSS( Virtual Set Size ) 设置 - 开发者模式 - 进程 19