/   今日科技快讯   /

昨日,美国商务部产业安全局在其官网上宣布,将28家中国组织和企业列入“实体清单”,其中包括海康威视、科大讯飞、旷世科技、大华科技、厦门美亚柏科信息有限公司、依图科技、颐信科技有限公司共8家人工智能公司。

/   作者简介   /

本篇文章来自Overried的投稿,分享了Android开发中内存优化的相关知识,希望对大家有所帮助!同时也感谢作者贡献的精彩文章。

Overried的博客地址:

https://www.jianshu.com/u/75711cf32043

/   内存的划分   /

分类的标准 procrank dumpsys meminfo android studio profile JMM

划分区域

VSS/RSS/PSS/USS

Native/Dalvik/Cursor/Ashmem..

java/native/graphics/stack/code/othe

方法区/java堆/java栈/native栈/程序计数器

procrank是一个adb的root指令,可以查询内存的划分:

  • VSS - Virtual Set Size虚拟耗用内存(包含共享库占用的内存)
  • RSS- Resident Set Size实际使用物理内存(包含所有共享库占用的内存)
  • PSS- Proportional Set Size实际使用的物理内存(按比例分配共享库占用的内存)
  • USS- Unique Set Size进程独自占用的物理内存(不包含共享库占用的内存)
那么最值得关注的是PSS和USS,我们可以用dumpsys meminfo来查询(无需root权限)
dumpsys meminfo查询pss划分
重点字段解读:
  • Native Heap - Native堆内存
  • Dalvik Heap - Dalvik虚拟机内存,Dalvik虚拟机代码在 libdvm.so 主要负责运行时dex解析成机器码(android 5.0+ ART 中已经取消 Dalvik 虚拟机,这里任然出现 Dalvik 目测是没改过来)
  • .art mmap - Android RunTime映射内存,art代码在 android_runtime.so, mmap(是linux C的一个函数接口,用来做内存映射)
  • Private Dirty - 进程独占的内存,内存已经被本进程修改过,只能被自己进程使用
  • Private clean - 进程独占的内存,内存是映射过来的,没有做修改,可以置换给到其他进程使用
  • java heap - Dalvik heap(dirty+ clean) + art heap(dirty + clean)
通过上面图片可得launcher app占用的内存是250M,大部分内存在Native Heap、code、graphics,那如何分析和解决,我们下面讲。
android studio profile是ide提供出来的分类:
  • Total - 整个应用占据的总内存
  • Java - java堆占据的内存
  • Native - Native层调用malloc/new(C/C++)占据的内存
  • Graphics - 图形缓冲区队列向屏幕显示像素,(如果没有用到OpenGL或者不是游戏,可以直接忽略)
  • Stack - 线程栈占据的内存
  • Code - dex+so库占据的内存
  • Others - 未解之谜
JMM 分类
  • 方法区 - 存放常量和静态变量区域
  • java堆 - new出来的对象内存都放在这里面
  • java栈 - 方法中执行的基本类型变量 和 变量引用都在栈中。(类中的内部成员属性的引用在堆中)
  • native栈 - 同java栈,指针引用都在native栈中
  • 程序计数器 - 作用是多线程切换记录上一个线程执行到的点。譬如:A线程 切换到B线程。程序计数器要记录A线程 已经执行到哪一行代码。接着cpu切换到B线程,再切换回来A线程的时候,cpu才知道从A线程哪一行代码继续执行
注意:
  • java栈、native栈、程序计数器是线程私有
  • java堆、方法区是线程共有的
/   Java内存优化   /
Java内存优化
内存泄漏
内存抖动
大内存对象使用
发生的场景
单例、匿名内部类、接口忘记释放 ...
String拼接、循环内重复生成对象 ...
HashMap、ArrayList ...
Java检查泄漏 - LeakCanary使用
LeakCanary结果分析
LeakCanary可以检查Activity Fragment View界面的泄漏问题。通过接入LeakCanary跑上monkey接着静等java内存泄漏的出现:
通过上图可以知道SearchActivity被HistorySource.mContext持有,HistorySource是一个单例,然后最顶层的Thread.contextClassLoader就是GC root(注意:静态变量不是GC root),Thread.contextClassLoader是PathClassLoader类,只要把 SearchActivity的context换成Application那就解决了。
Android中GC root有哪些:
  • 被System ClassLoader加载过的类,继而生成的对象,譬如rt.jar中的类
  • PathClassLoader、DexClassLoader
  • 活着的线程Thread
  • 函数方法中的局部变量(跑在线程中的)
  • JNI中的全局变量和局部变量
LeakCanary的核心原理:
  • 通过registerActivityLifecycleCallbacks()监听各个Activity的退出
  • Activity退出后,拿到Activity的对象封装成KeyedWeakReference弱引用对象。
  • 通过手动Runtime.getRuntime().gc();垃圾回收
  • 通过removeWeaklyReachableReferences()手动移除已经被回收的对象
  • 通过gone()函数判断是否被移除,如果移除了,说明Activity 已经没有其他强引用 在引用它,没有泄露
  • 如果没有移除,通过android原生接口Debug.dumpHprofData(),把Hprof文件搞下来,通过haha这个第三方库去解析是否有指定Activity的残留。(haha是分析Hprof的java库)
小结
那么LeakCanary只能解决界面上的泄漏,其他内存上的优化是做不到的,譬如:线程池的泄漏,内存的抖动,大对象的滥用.. 那么就需要更为强大的工具MAT了。
内存检测工具MAT
MAT是分析内存文件hprof的工具。
抓取步骤
跑几分钟monkey后,退回应用主界面,手动多次点击GC按钮,把可回收的回收掉,为了剔除脏数据。通过Android Studio的Profile把内存文件hprof给dump下来。
  • 进入Android SDK目录:G:\AndroidSDK\platform-tools
  • 把dump下载的文件memory-20190828T162317.hprof拖进platform-tools文件夹
  • 敲入cmd命令hprof-conv memory-20190828T162317.hprof 1.hprof转成可被MAT识别的1.hprof文件
  • 使用MAT打开1.hprof
分析内存
完成以上步骤之后的结果图。
  • 直接点击左上角Histogram查看内存分布
  • objects - 对象数目
  • shallow heap - 对象自身实际占用的堆大小
  • retained heap - 对象被回收后能释放多少内存
  • Inspector - 可以看到对象的GC Root是谁
  • with outgoing references - 表示的是 当前对象,引用了内部哪些成员对象
  • with incoming references - 表示的是 当前对象,被外部哪些对象应用了(重点操作)
  • merge shortest paths to gc roots - 从GC roots到一个或一组对象的公共路径
  • exclude all phantom/weak/soft etc. references - 排除一些类型的引用(如软引用、弱引用、虚引用),留下强引用
为了避免查看太多并不是强相关的对象,直接从本应用的java 类入手,MAT 也提供正则式过滤,直接输入.com.vd.(本应用 packageName)去过滤,结果就非常明显,整个应用自己写的对象占用的内存都在这里。从大的对象下手,是否这个对象有存在的意义,是否需要占这么大的一个内存。是否可以对其做相应的处理。
MAT提供了更加方便的OQL查询,可以找到指定一个名字的对象,包括可以根据本身java对象的成员属性来做条件语句。譬如上图我找长宽都大于100px的图片都有哪些。可以把大图片揪出来。
小结
可先用LeakCanary跑出明显的内存泄漏,再用MAT检查整个应用的内存分布状况,去优化该优化的Java堆内存。
/   Native内存优化   /
native 内存优化
malloc_debug
heapsnap
DDMS
root权限
需要
需要
不需要
环境
python
jni
需要使用sdk18 的 tools/ddms.bat(sdk 18之后就被剔除了)
  • malloc_debug是官方推荐的一种方法,目前效果还不错
  • heapsnap 是一个可以跑在Adnroid的C库github开源库 ,目前只能查询内存泄漏。而且编译不过,原因是缺少了一些库。在它基础上我整合了一份编译成功,有兴趣点击这里
  • DDMS目前被遗弃,在android 9.0没整成功,放弃。
malloc_debug步骤
开启malloc debug模式,打开cmd窗口输入。
//查询所有内存
adb shell setprop wrap.packagename '"LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper"'

//查询内存泄漏
adb shell setprop wrap.packagename '"LIBC_DEBUG_MALLOC_OPTIONS=leak_track logwrapper"'

  • 关掉自身应用,再打开,monkey跑起来
  • 通过adb shell dumpsys meminfo com.all.videodownloader.videodownload查到pid为2968
通过adb shell am dumpheap -n <PID_TO_DUMP> /data/local/tmp/heap.txt把文件抓取出来到/data/local/tmp/heap.txt。
把native内存文件拷贝出来,等下分析。
使用python分析
搭建环境
  • 下载native_heapdump_viewer.py
  • python编译器我选择了PyCharm
  • 新建项目,把native_heapdump_viewer.py和 heap.txt,放到同一个目录,如下图
修改python代码
修改native_heapdump_viewer.py 代码中NDK配置地方:
resByte = subprocess.check_output(["G:/AndroidNDK/android-ndk-r17/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-objdump", "-w", "-j", ".text", "-h", sofile])
p = subprocess.Popen(["G:/AndroidNDK/android-ndk-r17/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line", "-C", "-j", ".text", "-e", sofile, "-f"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
替换def __init__(self):函数中的部分代码,把下面代码:
if len(extra_args) != 1:
print(self._usage)
sys.exit(1)
替换为:
self.symboldir = "C:/Users/chaojiong.zhang/Documents/AndroidStudio/DeviceExplorer/xiaomi-mi_8-4b429b4"
extra_args.append("dump.txt")
self.symboldir - 就是dump.txt 里面的内存地址都需要 通过so库来查找对应的是哪一个函数。而so存放的父路径地址就是self.symboldir,那么也就是说需要把 手机上的/system/lib64、/vendor/lib64/整个 文件夹pull 下来到电脑上,譬如这里是pull到C:/Users/chaojiong.zhang/Documents/AndroidStudio/DeviceExplorer/xiaomi-mi_8-4b429b4。
在def main():函数插入部分代码在函数第一行插入和最后一行插入以下代码,目的是直接把结果log输出到test.txt可以直接查看。
def main():
sys.stdout = open("test.txt", "w")

//...
sys.stdout.close()

跑起来看看。
malloc_debug内存文件分析
字段解读
  • BYTES- 占用的内存大小单位byte
  • %TOTAL - 占总 native 内存百分比
  • %PARENT - 占父帧内存百分比
  • COUNT - 调用了多少次
  • ADDR- 内存地址
  • LIBRARY - 占用的内存所属哪一个 so 库
  • FUNCTION- 占用的内存所属哪一个方法
  • LOCATION - 占用的内存所属哪一行
内存信息分析一
10285756  58.29%  99.95%       49     eac0b276 /system/lib/libhwui.so android::Bitmap::allocateHeapBitmap(SkBitmap*)
可以看得出来 allocateHeapBitmap方法占用了,10M左右的内存,占总native内存 58.29%,占父帧 99.95% (意思是:A-> B ,A方法调用B方法,A方法总共占用了10M,其中9.9M是在B方法中申请的,那么%PARENT 就是99%),调用了49次,动作发生在libhwui.so中的 android::Bitmap::allocateHeapBitmap方法。下面是allocateHeapBitmap被调用的流程:
BitmapFactory.decodeResource -> BitmapFactory.nativeDecodeStream ->BitmapFactory.cpp 中 nativeDecodeStream() -> doDecode() -> SkBitmap.tryAllocPixels() -> ... -> android::Bitmap::allocateHeapBitmap()
Bitmap.createBitmap -> nativeCreate() -> Bitmap.cpp 中的 nativeCreate() -> GraphicsJNI.cpp zhong de allocateJavaPixelRef() -> ... -> android::Bitmap::allocateHeapBitmap()
也就是说java层的bitmap 创建都会跑到allocateHeapBitmap这个函数。那么上面这个占用了10M的 allocateHeapBitmap,究竟是java层哪个类调用下来的,这个目前是无解(包括最近华为的方舟环境平台DevEco也不行),只能在java层去全盘查询了,哪些图片使用了较多的内存。
内存信息分析二
  • WebViewGoogle.apk占用了10M的内存,WebViewGoogle.apk就是应用使用的WebView,android 5.0之前作为模块存在于frameworks/base目录下,并提供接口。android 5.0之后变成了编译为一个独立的apk,包名是 com.android.webview。检查所有的WebView使用情况,譬如:如果场景允许,使用完毕是否有 调用WebView.clearCache()
  • boot-framework.oat 占了5M ,Android framework代码通过dex2oat转成的oat二进制文件(机器码),无需优化
  • libandroid_runtime.so 占了 5M,虚拟机内存,属于按比例划分共享库,无需优化
小结
native内存目前无法很清晰的定位到对应的java层代码,无解。只能看个大概,然后有目的性去排查某个类,或者某个模块。
/   graphics内存优化   /
若应用没有自己接入OpenGL/ GL surfaces/ GL textures开源库,来绘制图形,可不必理会。毕竟已经超出android应用工程师的范围了。
/   stack内存优化   /
解决栈溢出
死循环问题
JDK 1.8之前的HaskMap,避免使用多线程造成死循环问题。
递归问题
避免深层次的递归问题,较深层次的递归可采用尾递归的方法。递归的退出,最好用标识位退出。或者通过线程interrupt(),isInterrupted()去退出递归,确保递归正确退出。递归中如果有Thread.sleep ,要注意中断被消费问题。
Intenet问题
对于Intent传递大对象,或者ArrayList<Info>,Intent的上限是505K 。解决方案:
  • 一般通过 static 持有需要传递的对象解决。
  • 把跳转的页面写成 fragment ,数据可以不需要传递也可获取
  • 通过EventBus RxBus(原理都是通过全局单例来传递)
  • 通过ObjectCache把对象转成json串,保存到本地,获取时候序列化为对象。
解决重复生成局部变量
避免在循环内重复生成局部变量
private void memoryShake() {
ArrayList<Integer> shakes = new ArrayList<>();
for (int i = 0; i < 100; i++) {
Integer shake = new Integer(i);
shakes.add(shake);
}
}

private void memoryShake1() {
ArrayList<Integer> shakes = new ArrayList<>();
Integer shake;
for (int i = 0; i < 100; i++) {
shake = new Integer(i);
shakes.add(shake);
}
}

memoryShake()会在循环内生成100个shake局部变量+100个局部变量的引用,memoryShake1()会在循环内生成100个shake局部变量+1个局部变量的引用,一个对象引用在64bit的环境是8byte 。100*8 = 800 byte = 0.8KB。
String使用问题
循环内字符的拼接不要使用+符号,(使用+符号,编译成字节码后,循环内会生成StringBuilder对象去拼接)。正确应该使用StringBuffer(线程安全)或者StringBuilder(线程不安全)。
/   code内存优化   /
code内存消耗主要是:so库,dex,ttf。

以上三种文件都是要加载到运行内存才能被解析运行,所以它们的体积要算进自身的应用内存中。
so库,可以通过STRIP去掉一些符号表和调试信息,在Android.mk加入 LOCAL_STRIP_MODULE:= true,即可。
dex,是java代码编译成的字节码,没混淆的apk中的dex会大很多,混淆后的dex 会小很多,所以debug包的内存占用会大于release包。Android Studio 3.3带了了一个新特性R8压缩,可以在gradle.properties加入 android.enableR8=true ,减小dex包的体积(完美兼容现有混淆)。当然还要剔除自身应用的无用代码,可使用Android Studio Menu > Refactor > Remove Unused Resources进行排查,这里不再详细展开。
ttf - 如果应用中只用到部分字体,可通过FontZip提取使用的字体。
推荐阅读:
欢迎关注我的公众号
学习技术或投稿

长按上图,识别图中二维码即可关注

讲一讲 Android 内存优化的小秘密相关推荐

  1. 【Android 内存优化】Bitmap 图像尺寸缩小 ( 考虑像素密度、针对从不同像素密度资源中解码对应的 Bitmap 对象 | inDensity | inTargetDensity )

    文章目录 一.像素密度对解码图片的影响 二.不考虑像素密度会导致图片缩小尺寸不准确 三.DisplayMetrics 源码阅读.研究手机资源获取规则 四.像素密度参数设置取值 ( inDensity ...

  2. 【腾讯Bugly干货分享】Android内存优化总结实践

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/2MsEAR9pQfMr1Sfs7cPdWQ 导语 智 ...

  3. Android内存优化(二):一分钟发现内存泄漏

    在上一篇文章Android内存优化(一):Java内存区域中已经大体上介绍了Java中的内存分布情况,这一篇主要讲一下内存泄漏的产生原因.内存泄漏的危害.内存泄漏一键分析与定位.以及代码中常见的内存泄 ...

  4. ANDROID内存优化(大汇总——全)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  5. Android内存优化汇总

    写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在A ...

  6. Android 内存优化——常见内存泄露及优化方案

    如果一个无用对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回 收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费,这中情况就是内存泄 露. 在 Android 开 ...

  7. Android 内存优化总结实践

    原文地址:https://mp.weixin.qq.com/s/2MsEAR9pQfMr1Sfs7cPdWQ 导语 智能手机发展到今天已经有十几个年头,手机的软硬件都已经发生了翻天覆地的变化,特别是A ...

  8. Android内存优化总结实践

    http://www.cnblogs.com/ldq2016/p/6635774.html 本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https:// ...

  9. Android内存优化总结

    [腾讯Bugly干货分享]Android内存优化总结&实践 本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq ...

最新文章

  1. Python远程连接服务器
  2. CSS的单位及css3的calc()及line-height百分比
  3. MSE | 阿里巴巴云原生网关三位一体的选择与实践
  4. 云计算呼叫中心_SaaS云呼叫中心系统只用于销售或客服?
  5. LeetCode LCS 02. 完成一半题目(计数+排序)
  6. 解析MySQL基础架构及一条SQL语句的执行流程和流转
  7. Java List和Array之间的转换
  8. 逆向破解之160个CrackMe —— 007
  9. Android线程和线程Handler基础一览
  10. 如何利用FL Studio中文版做出失真效果
  11. MP4格式转换为AMV格式
  12. 计算机组成原理试题库(含答案),计算机组成原理试题库(含答案)--
  13. HTML写一个简单网页
  14. 形态学图像处理之边界提取与跟踪
  15. 干货收藏!小伙做自媒体半年得5万,分享6大必备素材网
  16. 坦克世界因计算机丢失,坦克世界新版本上线在即,玩家必须知道的几件事。
  17. 操作系统的概念(定义)
  18. 成功解决http error 503.the service is unavailable错误
  19. Linux常用命令-云计算篇-100%亲测超详细讲解
  20. Mysql对应的dul_免费开放几个PRM-DUL企业版LicenseKey

热门文章

  1. Android获取API level
  2. windows7下安装Docker Toolbox
  3. 爬虫工具-爬虫软件-免费爬虫工具软件
  4. 数理统计----协方差公式推导
  5. matlab 图像像素量化,(最新整理)图像采样和量化
  6. 虚拟机服务器开启也显示内部错误,虚拟机内部错误怎么解决?-解决虚拟机提示“内部错误”的方法 - 河东软件园...
  7. 如何在 VirtualBox 中安装 SteamOS
  8. c语言和java哪个有前途_Java和C语言哪个好就业?
  9. maxthon 2 ua
  10. 如何对一个产品编写完整的用户故事?