基础知识
内存概念
VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS- Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS- Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存
虚拟内存
虚拟地址空间大小
影响大小的3个因素
页大小/地址长度/页表级数,大的在前小的在后 L3~L0
Android默认的页面大小配置为CONFIG_ARM64_4K_PAGES
,即4KB,如果使用48位的地址空间需要4级页表来支持。
Android 默认的虚拟地址的长度配置为CONFIG_ARM64_VA_BITS=39
,
同时使用3级页表CONFIG_PGTABLE_LEVELS=3
进行管理,
故Android的64位应用可使用的地址空间一般为2^39=512GB
进程空间分布
- 代码段:存放用户进程代码,以及部分常量;
- 数据段:存放用户进程初始化全局变量和静态变量;
- BSS段:存放用户进程未初始化全局变量和静态变量;
- 堆:存放用户进程动态申请的数据,堆是从低地址向高地址生长;
- 内存映射区:用户动态链接库的加载、文件的映射,起始地址存储在mm结构体中的mmap_base变量;
- 栈:存放环境变量、参数字符串、局部变量、函数栈帧。栈的首地址为STACK_TOP,一般默认开启栈随机化,因此实际的起始地址是STACK_TOP减去一个随机值。
Java堆
堆配置
dalvik.vm.heapgrowthlimit: [256m] (每个应用程序一般情况下堆最大内存可分配到内存)
dalvik.vm.heapsize: [512m] (每个应用程序最大堆内存可分配到内存,largeHeap=true的情况)
dalvik.vm.heapmaxfree: [8m] (堆最大空闲内存)
dalvik.vm.heapminfree: [6M] (堆最小空闲内存)
dalvik.vm.heapstartsize: [8m] (表示应用程序启动后为其分配的初始大小)
dalvik.vm.heaptargetutilization]: [0.75] (堆的目标利用率)
堆内存分布
Graphic
EGL mtrack: gralloc分配的内存,主要是窗口系统,SurfaceView/TextureView和其他的由gralloc分配的GraphicBuffer总和
GL mtrack: 驱动上报的GL内存使用情况。 主要是GL texture大小,GL command buffer,固定的全局驱动程序RAM开销等的总和
查看内存命令/工具
procrank (only for Android)
它从/proc/pid/maps中读取信息来进行统计。源码位于:/system/extras/procrank
内存耗用:VSS/RSS/PSS/USS
• VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
• RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
• PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
• USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
cat /proc/pid/status
VmPeak 虚拟内存使用量的峰值,取mm->total_vm和mm->hiwater_vm的大值。
VmSize:当前虚拟内存的实际使用量。
VmLck:PG_mlocked属性的页面总量,常被mlock()置位。
VmPin:不可被移动的Pined Memory内存大小。
VmHWM:HWM是High Water Mark的意思,表示rss的峰值。
VmRSS:应用程序实际占用的物理内存大小,这里和VmSize有区别。VmRss要小于等于VmSize。
RssAnon:匿名RSS内存大小。
RssFile:文件RSS内存大小。
RssShmem:共享内存RSS内存大小。
VmData:程序数据段的所占虚拟内存大小,存放了初始化了的数据。
VmStk:进程在用户态的栈大小。
VmExe:进程主程序代码段内存使用量,即text段大小。
VmLib:进程共享库内存使用量。
VmPTE:进程页表项Page Table Entries内存使用量。
VmPMD:进程PMD内存使用量。
VmSwap:进程swap使用量。
复制代码
Name: com.xxx.xxx
Umask: 0077
State: S (sleeping)
Tgid: 16962
Ngid: 0
Pid: 16962
PPid: 629
TracerPid: 0
Uid: 10215 10215 10215 10215
Gid: 10215 10215 10215 10215
FDSize: 1024
Groups: 3001 3002 3003 9997 20215 50215
VmPeak: 3854080 kB
VmSize: 3072956 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 750696 kB
VmRSS: 371612 kB
RssAnon: 229320 kB
RssFile: 141892 kB
RssShmem: 400 kB
VmData: 2078184 kB
VmStk: 8192 kB
VmExe: 24 kB
VmLib: 304248 kB
VmPTE: 3612 kB
VmPMD: 16 kB
VmSwap: 11652 kB
Threads: 275
SigQ: 0/21576
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000001204
SigIgn: 0000000000000001
SigCgt: 00000006400084f8
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000000000
CapAmb: 0000000000000000
Seccomp: 2
Speculation_Store_Bypass: unknown
Cpus_allowed: ff
Cpus_allowed_list: 0-7
Mems_allowed: 1
Mems_allowed_list: 0
voluntary_ctxt_switches: 670221
nonvoluntary_ctxt_switches: 198816
复制代码
/proc/< pid> /status简要分析 - ArnoldLu - 博客园
maps文件读取
只能被自己读取,其他用户没权限
adb shell run-as com.xxx.xxx showmap 23308
adb shell run-as com.xxx.xxx cat /proc/16962/smaps
adb shell run-as com.xxx.xxx pmap xx
linux proc maps文件分析_jiazheng.li的CSDN博客-CSDN博客_maps文件
eac52000-eac8a000 rw-p 00000000 00:00 0 [anon:dalvik-threa local mark ]
[anon:dalvik-threa local mark ]
对有名来说,是映射的文件名。对匿名映射来说,是此段虚拟内存在进程中的角色。[stack]表示在进程中作为栈使用,[heap]表示堆。其余情况则无显示
adb shell dumpsys meminfo
adb shell dumpsys meminfo
Applications Memory Usage (in Kilobytes):
Uptime: 289354670 Realtime: 348478673
** MEMINFO in pid 16962 [com.xxxx] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 200662 200532 0 918 245504 222307 23196
Dalvik Heap 69943 69860 0 74 50813 26237 24576
Dalvik Other 18671 18668 0 0
Stack 108 108 0 0
Ashmem 242 4 0 0
Gfx dev 23460 23276 184 0
Other dev 131 4 124 0
.so mmap 29702 2232 21904 24
.jar mmap 6 0 4 0
.apk mmap 28506 11156 6704 0
.ttf mmap 247 0 36 0
.dex mmap 64055 55240 4752 0
.oat mmap 3793 0 612 0
.art mmap 8124 5644 768 2
Other mmap 3260 32 2076 1
EGL mtrack 35136 35136 0 0
GL mtrack 104280 104280 0 0
Unknown 6002 5976 0 14
TOTAL 597361 532148 37164 1033 296317 248544 47772
App Summary
Pss(KB)
------
Java Heap: 76272
Native Heap: 200532
Code: 102640
Stack: 108
Graphics: 162876
Private Other: 26884
System: 28049
TOTAL: 597361 TOTAL SWAP PSS: 1033
Objects
Views: 1605 ViewRootImpl: 1
AppContexts: 11 Activities: 2
Assets: 13 AssetManagers: 0
Local Binders: 306 Proxy Binders: 56
Parcel memory: 52 Parcel count: 208
Death Recipients: 5 OpenSSL Sockets: 6
WebViews: 20
SQL
MEMORY_USED: 7370
PAGECACHE_OVERFLOW: 5553 MALLOC_SIZE: 215
DATABASES
pgsz dbsz Lookaside(b) cache Dbname
4 308 103 765/61/25 /data/user/0/com.xxx.xxx/databases/mbridge.msdk.db
4 24 34 7/25/4 /data/user/0/com.xxx.xxx/databases/omDB.db
4 128 109 293/39/22 /data/user/0/com.xxx.xxx/databases/vungle_db
4 108 85 416/82/25 /data/user/0/com.xxx.xxx/databases/google_app_measurement.db
4 40 33 1/24/2 /data/user/0/com.xxx.xxx/databases/com.google.android.datatransport.events
4 40 70 315/23/6 /data/user/0/com.xxx.xxx/databases/dt_event.db
4 304 49 77/130/4 /data/user/0/com.xxx.xxx/databases/db_we_show
4 8 0/0/0 (attached) temp
4 304 44 29/17/2 /data/user/0/com.xxx.xxx/databases/db_we_show (2)
4 20 64 1/19/2 /data/user/0/com.xxx.xxx/databases/godap_download.db
4 18244 54 2058/20/5 /data/user/0/com.xxx.xxx/no_backup/androidx.work.workdb (2)
13877622/11861/25 /data/user/0/com.xxx.xxx/no_backup/androidx.work.workdb
Asset Allocations
: 155K
: 157K
: 160K
: 1494K
: 5873K
复制代码
EGL mtrack/GL mtrack
EGL mtrack: gralloc分配的内存,主要是窗口系统,SurfaceView/TextureView和其他的由gralloc分配的GraphicBuffer总和
GL mtrack: 驱动上报的GL内存使用情况。 主要是GL texture大小,GL command buffer,固定的全局驱动程序RAM开销等的总和
android Profiler
developer.android.com/studio/prof…
内存信息
- Java:从 Java 或 Kotlin 代码分配的对象的内存。
- Native:从 C 或 C++ 代码分配的对象的内存。
即使您的应用中不使用 C++,您也可能会看到此处使用了一些原生内存,因为即使您编写的代码采用 Java 或 Kotlin 语言,Android 框架仍使用原生内存代表您处理各种任务,如处理图像资源和其他图形。
- Graphics:图形缓冲区队列为向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。(请注意,这是与 CPU 共享的内存,不是 GPU 专用内存。)
- Stack:您的应用中的原生堆栈和 Java 堆栈使用的内存。这通常与您的应用运行多少线程有关。
- Code:您的应用用于处理代码和资源(如 dex 字节码、经过优化或编译的 dex 代码、.so 库和字体)的内存。
- Others:您的应用使用的系统不确定如何分类的内存。
- Allocated:您的应用分配的 Java/Kotlin 对象数。此数字没有计入 C 或 C++ 中分配的对象
内存指标采集
内存
1.通过 ActivityManager 的 getProcessMemoryInfo => Debug.MemoryInfo 获取内存信息数据。
2.通过 Debug.MemoryInfo 的 getMemoryStats() 方法(os v23 及以上)可以获得 Memory Profiler 中的多项数据,进而获得 细分内存的使用情况。
3.接着,通过 Runtime 获取 DalvikHeap信息
4.最后,通过 Debug.getNativeHeapAllocatedSize 获取 NativeHeap
虚拟内存
读取/proc/self/status文件
读取/proc/self/smap文件
内存监测
OOM类型
1.pthread_create问题
2.文件描述符超限问题
3.堆内存超限
堆内存超限原因
1.单次配过大
2.累计使用过大
如何发现问题
提供场景信息
activity 事件信息
fragment 事件信息
WebView/SurfaceView/TextView attach/detach事件信息
场景进入退出信息
监测水位触顶
方法
1.通过 ActivityManager 的 getProcessMemoryInfo => Debug.MemoryInfo 获取内存信息数据。
2.通过 hook Debug.MemoryInfo 的 getMemoryStat 方法(os v23 及以上)可以获得 Memory Profiler 中的多项数据,进而获得 细分内存的使用情况。
3.接着,通过 Runtime 获取 DalvikHeap。
4.最后,通过 Debug.getNativeHeapAllocatedSize 获取 NativeHeap
阀值
1.堆内存超过最大值的85%的限制,GC 会变得更加频发,容易造成 OOM 和 卡顿。
2.32位机器虚拟内存超过3.7G会大概率挂
监测泄漏
1.Activity/Fragment/Service/Provider组件
Application.ActivityLifecycleCallbacks;
FragmentManager.FragmentLifecycleCallbacks;
需要注册三个包之下
android.app.Fragment
androidx.fragment.app.Fragment
android.support.v4.app.Fragment
2.大对象主动监测
播放对象/大的缓存对象等在业务结束的时候加入监控
监测大图
AOP插桩方式
1.监测 ImageVIew的setImageResource/setImageDrawable/setImageBitmap,判断图片大小是否大于View的宽高
2.监测Bitmap的createBitmap()/createScaledBitmap,记录大小超过阀值的图片信息,堆栈,
3.监测BitmapFactory.decodeFile()/decodeResuorce/decodeStream,记录大小超过阀值的图片信息,堆栈
Java hook方式
使用epic 同样直接hook以上方法
监测GC事件
1.Cleaner机制 ART虚拟机 | Finalize的替代者Cleaner - 掘金
2.bhook __android_log_write 接口获取GC日志信息
3.从 API 级别 30 开始,可通过调用 __android_set_log_writer 更改日志记录函数
developer.android.com/studio/comm…
4.参考ActivityThread.attach()中的方法,使用WeakReference持有覆盖finalize()方法的对象,在finalize()再重新生成WeakReference,参考
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
if (dalvikUsed > ((3*dalvikMax)/4)) {
if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ " total=" + (runtime.totalMemory()/1024)
+ " used=" + (dalvikUsed/1024));
mSomeActivitiesChanged = false;
try {
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
}
}
}
});
复制代码
监测内存分配
JVMTI功能
利用Android9.0虚拟机的JVMTI技术实现一些黑科技_z1032689332的博客-CSDN博客_安卓9虚拟机
1.类加载事件
2.GC启动和结束事件
3.内存分配事件
堆栈聚合
基于对象聚合
基于业务聚合
内存归因
ClassLoader
StatisticLogger
ImageLoader
ALog
监控线程创建
1.使用epic hook thread的start方法
2.记录线程创建的堆栈和信息
3.定时dump所有线程信息
分析heap时分析大对象的 RetainedSize/小对象的引用链路
1.加载全部对话信息
2.增加多层缓存,内存->磁盘缓存
3.缓存失效和清理
实际例子
1.Feed流中,LoadMore每次都加载新的视频数据,缓存数据可能一直增大
2.可以使用多级缓存,内存缓存超过一定大小,写到磁盘中
开源工具
Java hook
Epic
PLT/GOT hook
bhook
JavaHeapDump
hprof的格式是什么
工具
KOOM
Tailor
androidperformance.com/2015/04/11/… 内存优化之二 - MAT使用进阶](androidperformance.com/2015/04/11/…)
还原hprof文件
python3 ~/Desktop/work/opesource/tailor/library/src/main/python/decode.py -i ~/Desktop /mini.hprof -o ~/Desktop/target.hprof
hprof-conv转换
/Users/cy/Library/Android/sdk/platform-tools/hprof-conv /Users/cy/Desktop/target.hprof ~/Desktop/mat.hprof
Native内存
Raphel
python3 ~/Desktop/work/opesource/memory-leak-detector/library/src/main/python/raphael.py -r /Users/cy/Desktop/oom/report -o /Users/cy/Desktop/oom/leak.txt
python3 ~/Desktop/work/opesource/memory-leak-detector/library/src/main/python/mmap.py -m /Users/cy/Desktop/oom/maps
adb shell am broadcast -a com.apm.ACTION_DUMP_ALL
adb shell am broadcast -a com.apm.ACTION_NATIVE_DUMP
优化
泄漏问题
1.activity/fragment/service/provider泄漏问题
2.view泄漏问题
- 主动释放泄漏的 Activity 持有的 主动回收View和View使用的资源
1.activity可以调用setContentView(null)
2.遍历view释放ViewTree 的背景图和 ImageView 中的图
图片合理性
1.图片大小的合理性
资源图片:
资源图片放错目录会放大
资源图片直接把留白和核心内容切了一整张大图,可以只切核心内容部分
能有drawable写,比如规则渐变色等直接使用代码写
其他图片:
其他图片加载需要考虑显示区域大小
2.缓存的图片不要过多
1.RecycleView item回收的时候,要清空使用状态
2.控制图片框架加载的 Cache 大小一般不超过2屏大小
3.Android O以上可以使用硬件位图
4.质量要求不要情况下,inPreferConfig可以降低到565配置
3.复用
1.底图或者框架图,可以使用全局实例
4.内存吃紧的情况下主动释放图片缓存。
onLowMemory/onTrimMemory中调用Glide.clearMemoryCache()
系统资源限制
1.文件描述符限制,O 以下设置为1k,新的一般设置为32k
2.线程个数限制,pthread_create问题
3.收敛线程
EL/EGL 内存
1.Window的个数不要太多
2.GPU显存使用问题,硬件加速中图片是先上传到纹理中的
3.SurfaceView和TextureView问题
Native内存
线程收敛、监控
线程栈泄漏自动修复
FD 泄漏监控
虚拟内存监控、优化
64 位专项
保障体系建设
1.提供哪些数据能分析得到准确的问题场景和原因
2.如何防止劣化
开发阶段
测试阶段
灰度阶段
上线阶段
案例
1. CursorWindow from Parcel due to error -12, process fd count=707
android.database.CursorWindowAllocationException: Could not create CursorWindow from Parcel due to error -12, process fd count=707
at android.database.CursorWindow.nativeCreateFromParcel(Native Method)
at android.database.CursorWindow.<init>(CursorWindow.java:167)
at android.database.CursorWindow.<init>(CursorWindow.java:45)
at android.database.CursorWindow$1.createFromParcel(CursorWindow.java:716)
at android.database.CursorWindow$1.createFromParcel(CursorWindow.java:714)
at android.database.BulkCursorDescriptor.readFromParcel(BulkCursorDescriptor.java:75)
at android.database.BulkCursorDescriptor$1.createFromParcel(BulkCursorDescriptor.java:34)
at android.database.BulkCursorDescriptor$1.createFromParcel(BulkCursorDescriptor.java:30)
at android.content.ContentProviderProxy.query(ContentProviderNative.java:426)
at android.content.ContentResolver.query(ContentResolver.java:946)
at android.content.ContentResolver.query(ContentResolver.java:881)
at androidx.core.content.ContentResolverCompat.query(SourceFile:81)
at androidx.loader.content.CursorLoader.loadInBackground(SourceFile:63)
at androidx.loader.content.CursorLoader.loadInBackground(SourceFile:41)
at androidx.loader.content.AsyncTaskLoader.onLoadInBackground(SourceFile:307)
at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground(SourceFile:60)
at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground(SourceFile:48)
at androidx.loader.content.ModernAsyncTask$2.call(SourceFile:141)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
复制代码
2.pthread_create (1040KB stack) failed
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:883)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:975)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1382)
at androidx.arch.core.executor.DefaultTaskExecutor.executeOnDiskIO(SourceFile:59)
at androidx.arch.core.executor.ArchTaskExecutor.executeOnDiskIO(SourceFile:96)
at androidx.arch.core.executor.ArchTaskExecutor$2.execute(SourceFile:53)
at androidx.room.InvalidationTracker.refreshVersionsAsync(SourceFile:442)
at androidx.room.RoomDatabase.endTransaction(SourceFile:368)
at me.vd.lib.file.manager.db.dao.PrivateFileDao_Impl.insertPrivateFiles(SourceFile:204)
at me.vd.lib.file.manager.manager.PrivateFolderManager.getPrivateFiles(SourceFile:597)
at me.vd.lib.file.manager.manager.PrivateFolderManager$getPrivateFiles$1.invokeSuspend(Unknown Source:11)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(SourceFile:33)
at kotlinx.coroutines.DispatchedTask.run(SourceFile:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.a(SourceFile:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(SourceFile:2738)
复制代码
3.graphic内存暴涨
1.基于场景切换发现
4.在Native层,Crash是vss超了3.7G后,随机出现某个场景
5.HandlerThread泄漏
6.EventBus泄漏
7.Feed流页面
首页推荐列表的每一次 Loadmore 操作,都不会清理之前缓存起来的视频对象,导致用户长时间停留在推荐 Feed 时,存起来的视频对象过多会导致内存方面的压力