Android开发高手课之内存优化

内存问题

  1. 异常
  2. 卡顿 :Java内存不足会导致频繁GC
//通过发送SIGQUIT信号获得ANR日志
adb shell ps -l //查看进程pid
adb shell kill -S QUIT PID
adb pull /data/anr/traces.txt

Android Bitmap内存分配的变化

  1. Android 3.0之前,Bitmap放在Java堆,而像素数据放在Native内存中,如果不手动调用recycle,Bitmap Native内存的回收完全依赖finalize函数回调。
  2. Android 3.0 ~ Android 7.0将Bitmap对象和像素数据统一放到Java堆中,这样即使不调用recycle,Bitmap内存也会随着对象一起被回收。造成的问题是,第一Bitmap内存消耗太大,第二会引起大量的GC。
  3. Android 8.0将Bitmap内存放到Native中,使用NativeAllocationRegistry来辅助回收Native内存,可以做到和对象一起释放,8.0还增加了硬件位图Hardware Bitmap,可以减少图片内存并提升绘制效率。

误区:Native内存不用管

将Bitmap放到native内存中的黑科技

//申请一张空的Native Bitmap
//通过调用libandroid_runtime.so中的Bitmap构造函数,可以得到一张空的Bitmap对象,而它的内存是放到Native堆中。不同版本有差异,需要适配
Bitmap nativeBitmap = nativeCreateBitmap(dstWidth, dstHeight, nativeConfig, 22)

//申请一张普通的Java Bitmap
Bitmap srcBitmap = BitmapFactory.decode(res, id)

//使用java Bitmap将内容绘制到Native Bitmap中
mNativeCanvas.setBitmap(nativeBitmap)
mNativeCanvas.drawBitmap(srcBitmap, mSrcRect, mDstRect, mPaint)

//释放Java Bitmap内存
srcBitmap.recycle()
srcBitmap = null;

以上方法存在的问题:

  1. 兼容性问题,需要适配
  2. 频繁申请释放Java Bitmap容易导致内存抖动

内存测量方法

adb shell dumpsys meminfo <package_name|pid> [-d]

Java内存分配

工具:

  1. Allocation Tracker
    1. 获取的信息过于分散,中间夹杂着其他的信息
    2. 无法做到自动化分析,每次都需要开发者手动开始 / 结束
    3. 在停止时,直接把数据dump出来之前,会造成手机卡死
  2. MAT

Native内存分配

Native内存调试的两种方法

  1. Malloc调试:可以调试Native内存的一些使用问题
adb shell setprop wrap.<APP> ' "LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper" '
  1. Malloc 钩子:Android P之后,Android的libc支持拦截在程序运行期间发生的所有分配 / 释放调用。
adb shell setprop wrap.<APP> ' "LIBC_HOOKS_ENABLE=1" '

设备分级

  1. 使用device-year-class的策略对设备分级,对于低端机用户可以关闭复杂动画或者某些功能,使用565格式的图片,使用更小的缓存内存等。
  2. 缓存管理,使用onTrimMemory回调,根据不同的状态决定释放多少内存,统一缓存管理可以更好的监控每个模块的缓存大小。
  3. 进程模型,减少应用启动的进程数,减少常驻进程
  4. 安装包大小,安装包中的代码、资源、图片以及so库的体积,跟他们占用的内存有很大的关系。

Bitmap优化

  1. 统一图片库
  2. 统一监控
    1. 大图片监控:需要注意某张图片内存占用是否过大
    2. 重复监控:Bitmap像素数据完全一致的图片
    3. 图片总内存:统计应用所有图片占用的内存

内存泄漏

  1. Java内存泄漏
  2. OOM监控
  3. Native内存泄漏监控
  4. 针对无法重编so的情况
  5. 针对可重编的so情况

内存监控

  1. 采集方式

可以每5分钟采集一次PSS、Java堆、图片总内存,采样部分用户

  1. 计算指标

内存异常率:可以反映内存占用的异常情况,PSS值可以通过Debug.MemoryInfo拿到

内存UV异常率 = PSS超过400MB的UV / 采集UV

触顶率:可以反映java内存的使用情况

内存UV触顶率 = Java堆占用超过最大堆限制的85%的UV / 采集UV

计算是否触顶的方法

long javaMax = runtime.maxMemory()
long javaTotal = runtime.totalMemory()
long javaUsed = javaTotal - runtime.freeMemory()
float proportion = (float)javaUsed / javaMax

内存分配次数和大小

Debug.getRuntimeStat("art.gc.gc-count")
Debug.getRuntimeStat("art.gc.gc-time")
Debug.getRuntimeStat("art.gc.blocking-gc-count")
Debug.getRuntimeStat("art.gc.blocking-gc-time")

Dalvik日志消息

D/dalvikvm: <GC_Reason 原因> <Amount_freed 释放量>, <Heap_stats 堆统计数据>, <External_memory_stats 外部内存统计数据>, <Pause_time 暂停时间>

D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms

垃圾回收原因

  1. GC_CONCURRENT:在堆开始占用内存时可以释放的内存的并发垃圾回收
  2. GC_FOR_MALLOC:堆已满而系统不得不停止您的应用程序并回收内存,您的应用尝试分配内存而引起的垃圾回收
  3. GC_HPROF_DUMP_HEAP:当请求创建HPROF文件来分析堆时出现的垃圾回收
  4. GC_EXPLICIT:显示垃圾回收,调用gc()时
  5. GC_EXTERNAL_ALLOC:仅适用于API级别10以及以下,外部分配内存的垃圾回收。

释放量

从此次垃圾回收中回收的内存量

堆统计数据

堆的可用空间百分比与(活动对象数量) / (堆总大小)

外部内存统计数据

API级别10及更低级别的外部分配内存 (已分配内存量) / (发生回收的限制)

暂停时间

堆越大,暂停时间越长,并发暂停时间显示了两个暂停,一个出现在回收开始时,一个出现在回收快要完成时。

ART日志消息

I/art: <GC_Reason 原因> <GC_Name 名称> <Objects_freed 释放对象>(<Size_freed> 释放大小) AllocSpace Objects, <Large_objects_freed 释放的大型对象>(<Large_object_size_freed> 释放的大型对象大小) <Heap_stats 堆统计数据> LOS objects, <Pause_time(s) 暂停事件>

I/art : Explicit concurrent mark sweep GC freed 104710(7MB) AllocSpace objects, 21(416KB) LOS objects, 33% free, 25MB/38MB, paused 1.230ms total 67.216ms

原因

  1. Concurrent:不会暂停应用线程的并发垃圾回收
  2. Alloc:在堆已满时尝试分配内存引起的垃圾回收
  3. Explicit:调用gc()产生的垃圾回收。
  4. NativeAlloc:Native层出现的内存回收,如位图或RenderScript分配对象
  5. CollectorTransition:堆转换引起的回收
  6. HomogeneousSpaceCompact:空间压缩,减少RAM使用量并对堆进行碎片整理。
  7. DisableMovingGc
  8. HeapTrim

名称

  1. Concurrent mark sweep (CMS):会释放和回收映像空间以外的所有其他空间
  2. Concurrent partial mark sweep:会回收除了映像空间和 zygote 空间以外的所有其他空间。
  3. Concurrent sticky mark sweep:只释放上次垃圾回收之后分配的对象,次垃圾回收比完整或部分垃圾清除运行的更频繁,因为更快速且暂停时间更短。
  4. Marksweep + semispace:对堆进行碎片整理

释放的对象

此次垃圾回收从非大型对象空间回收的对象数量

释放的大小

此次垃圾回收从非大型空间回收的字节数量

释放的大型对象

此次垃圾回收从大型对象空间回收的对象数量

释放的大型对象大小

此次垃圾回收从大型对象空间回收的字节数量

堆统计数据

空闲百分比 (活动对象数量)/ (堆总大小)

暂停时间

通常情况下,暂停时间与垃圾回收运行时修改的对象引用数量成正比。

扩展阅读

https://developer.android.com/studio/profile/investigate-ram?hl=zh-cn

https://source.android.com/devices/tech/debug/native-memory

https://mp.weixin.qq.com/s/b_lFfL1mDrNVKj_VAcA2ZA?

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

推荐阅读更多精彩内容