ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【性能优化】安卓性能优化之内存优化

【性能优化】安卓性能优化之内存优化

安卓性能优化之内存优化

内存优化

基础概念
常见内存泄漏
Java 内存划分

堆、程序计数器、方法区、本地方法栈、虚拟机栈,其中方法区和堆是线程共享的

  1. 方法区:线程共享,存储类信息、静态变量、常量、即时编译出来的代码数据,可造成OOM

  2. 堆:线程共享,存放几乎所有的对象实例,GC的主要区域

  3. 程序计数器

  4. 本地方法栈

  5. 虚拟机栈

对象存活判断算法
  1. 引用计数法
  1. 可达性分析法
  1. GC Root对象
垃圾回收算法
Android Studio Profiler

各项指标实时获取

线程数量

合理的线程使用可提高应用程序的运行效率,过度使用反而会增加CPU及内存的负担。为避免这一情况的发生,可结合进程状态及当前的线程列表进行分析:

当前系统的内存信息
ActivityManageractivityManager=(ActivityManager)getSystemService(ACTIVITY_SERVICE);ActivityManager.MemoryInfomemoryInfo=newActivityManager.MemoryInfo();activityManager.getMemoryInfo(memoryInfo);Log.i(TAG,"可用内存:"+memoryInfo.availMem);Log.i(TAG,"总内存:"+memoryInfo.totalMem);Log.i(TAG,"是否内存低:"+memoryInfo.lowMemory);
查看JNI 引用数量
privatevoiddumpJNIRef(){try{Class.forName("dalvik.system.VMDebug").getMethod("dumpReferenceTables").invoke(null);}catch(Exceptione){}}

查看日志有JNI引用表

global reference table dump:Last10entries(of611):610:0x12c9dca0java.lang.ref.WeakReference(referent is aandroid.view.inputmethod.InputMethodManager$ImeInputEventSender)609:0x12c9dc80android.hardware.input.InputManager$InputDevicesChangedListener608:0x12c9dc60android.view.ViewRootImpl$AccessibilityInteractionConnection607:0x12c9dc48android.os.Binder606:0x12c9dc30java.lang.ref.WeakReference(referent is aandroid.view.ViewRootImpl$WindowInputEventReceiver)605:0x12c9dc10android.view.ViewRootImpl$W604:0x12c9dbf0android.view.accessibility.AccessibilityManager$1603:0x12c9dbd0android.view.ViewRootImpl$AccessibilityInteractionConnection602:0x12c9dbb8android.os.Binder601:0x12c9dba0java.lang.ref.WeakReference(referent is aandroid.view.ViewRootImpl$WindowInputEventReceiver)Summary:319ofjava.lang.Class(243unique instances)253ofjava.nio.DirectByteBuffer(253unique instances)4ofjava.lang.ref.WeakReference(4unique instances)3ofandroid.opengl.EGLContext(2unique instances)3ofandroid.opengl.EGLDisplay(2unique instances)3ofandroid.opengl.EGLSurface(2unique instances)2ofdalvik.system.PathClassLoader(1unique instances)2ofjava.lang.String(2unique instances)2ofjava.lang.ThreadGroup(2unique instances)2ofandroid.os.Binder(2unique instances)2ofandroid.app.LoadedApk$ReceiverDispatcher$InnerReceiver(2unique instances)2ofandroid.view.ViewRootImpl$AccessibilityInteractionConnection(2unique instances)2ofandroid.view.ViewRootImpl$W(2unique instances)2ofandroid.view.accessibility.AccessibilityManager$1(2unique instances)1ofjava.nio.ByteOrder1ofdalvik.system.VMRuntime1ofandroid.app.ActivityThread
虚拟内存

获取更大对空间:AndroidManifest 配置application.largeHeap
版本差异:8.0之前,图片缓存容易消耗大量堆空间,8.0及之后像素数据被修改到了native层

FD数量

Linux把一切设备都视作文件,File Descriptor(文件描述符)为设备相关的编程提供了统一的方法。当我们执行IO、Socket及线程等相关操作时,都存在与之相对应的FD。进程的FD信息可通过读取/proc下的虚拟文件来获取:- - 大小限制:读取进程状态/proc/pid/limits,并解释Max open files字段

adb shell"getprop | grep dalvik"[dalvik.vm.appimageformat]:lz4[dalvik.vm.dex2oat-Xms]:64m[dalvik.vm.dex2oat-Xmx]:512m[dalvik.vm.dex2oat-max-image-block-size]:524288[dalvik.vm.dex2oat-minidebuginfo]:true[dalvik.vm.dex2oat-resolve-startup-strings]:true[dalvik.vm.dex2oat-updatable-bcp-packages-file]:/system/etc/updatable-bcp-packages.txt[dalvik.vm.dexopt.secondary]:true[dalvik.vm.heapmaxfree]:8m[dalvik.vm.heapminfree]:512k[dalvik.vm.heapsize]:[512m]# 单个进程可用的最大内存[dalvik.vm.heapstartsize]:[8m]# 堆分配的起始大小[dalvik.vm.heaptargetutilization]:0.75[dalvik.vm.image-dex2oat-Xms]:64m[dalvik.vm.image-dex2oat-Xmx]:64m[dalvik.vm.isa.arm.features]:default[dalvik.vm.isa.arm.variant]:cortex-a9[dalvik.vm.isa.arm64.features]:default[dalvik.vm.isa.arm64.variant]:generic[dalvik.vm.lockprof.threshold]:500[dalvik.vm.minidebuginfo]:true[dalvik.vm.usejit]:true[dalvik.vm.usejitprofiles]:true[persist.debug.dalvik.vm.core_platform_api_policy]:just-warn[persist.sys.dalvik.vm.lib.2]:libart.so[ro.dalvik.vm.native.bridge]:0
Native内存

通常说的Native内存是相对于Java堆而言的,Java堆区的内存有虚拟机代为申请和释放,Java层的业务代码无需关心。Native内存主要说的是由业务动态申请的内存,一般是业务so库,业务代码是c/c++实现的,常用的方式就是调用 malloc函数申请内存,调用free释放内存。这些内存的申请都需要合理的释放,否则会导致内存不足。可结合Debug.getMemoryInfo()以及/proc/pid/smap文件来分析。

native内存泄漏的检测
  1. 可以使用KOOM和matrix进行监控
  2. 二者的原理都是通过hook C的内存分配和释放(malloc与free函数)
  3. KOOM使用FP Unwind(frame pointer unwind)进行回溯(并不是所有的库都支持),matrix集成了unwind库进行堆栈回溯
  4. 根据堆栈,再通过add2line工具进行函数定位
  5. KOOM使用和原理
  6. 字节流动-LeakTracer集成检测
// 配置检测 Android native 内存泄漏的工具MemoryHook.INSTANCE//单独配置so的方式 ".*libasr\\.so$",".*libgram\\.so$",".*libvad\\.so$"// .addHookSo(".*com\\.hjl\\.test.*\\.so$") //全匹配的方式 ".*com\\.byd\\.dm.*\\.so$".addHookSo(".*").enableStacktrace(true).stacktraceLogThreshold(0).enableMmapHook(true);
MAT

问题排查案例

Java堆暴增导致OOM

由于项目使用消息队列的方式进行处理,因为某些消息处理比较耗时,导致消息堆积(消息携带byte数组),最终导致内存暴增

Native内存不断增长
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进