一、简介
进程的启动分冷启动和热启动,当用户退出某一个进程的时候,并不会真正的将进程退出,而是将这个进程放到后台,以便下次启动的时候可以马上启动起来,这个过程名为热启动,这也是Android的设计理念之一。这个机制会带来一个问题,每个进程都有自己独立的内存地址空间,随着应用打开数量的增多,系统已使用的内存越来越大,就很有可能导致系统内存不足。
lmkd(Low Memory Killer Daemon)是低内存终止守护进程,用来监控运行中android系统内存的状态,通过终止最不必要的进程来应对内存压力较高的问题,使系统以可接受的水平运行。
这里以Android7.1和Android10为对象分析
二、实现原理
所有应用进程都是从zygote孵化出来的,记录在AMS中mLruProcesses列表中,由AMS进行统一管理,AMS中会根据进程的状态更新进程对应的oom_adj值,这个值会通过socket传递给lmkd。lmdk根据内核的版本情况,或传递给kernel或自身处理低内存回收机制。为腾出更多的内存空间,在内存达到一定阀值时会触发清理oom_adj值高的进程。
三、必备知识
1.内存基础概念
在linux下表示内存的耗用情况有四种不同的表现形式:
VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
VSS:VSS表示一个进程可访问的全部内存地址空间的大小。这个大小包括了进程已经申请但尚未使用的内存空间。在实际中很少用这种方式来表示进程占用内存的情况,用它来表示单个进程的内存使用情况是不准确的。
RSS:表示一个进程在RAM中实际使用的空间地址大小,包括了全部共享库占用的内存,这种表示进程占用内存的情况也是不准确的。
PSS:表示一个进程在RAM中实际使用的空间地址大小,它按比例包含了共享库占用的内存。假如有3个进程使用同一个共享库,那么每个进程的PSS就包括了1/3大小的共享库内存。这种方式表示进程的内存使用情况较准确,但当只有一个进程使用共享库时,其情况和RSS一模一样。
USS:表示一个进程本身占用的内存空间大小,不包含其它任何成分,这是表示进程内存大小的最好方式!
总结
VSS>=RSS>=PSS>=USS
2.ADJ值可在ProcessList中查询
注:adj越大,越容易被kill,对于同等的adj值,内存占有越大的越容易被kill.
static final int UNKNOWN_ADJ = 1001;
// This is a process only hosting activities that are not visible,
// so it can be killed without any disruption.
static final int CACHED_APP_MAX_ADJ = 906;
static final int CACHED_APP_MIN_ADJ = 900;
// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
static final int SERVICE_B_ADJ = 800;
// This is the process of the previous application that the user was in.
// This process is kept above other things, because it is very common to
// switch back to the previous app. This is important both for recent
// task switch (toggling between the two top recent apps) as well as normal
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
static final int PREVIOUS_APP_ADJ = 700;
// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
// because the user interacts with it so much.
static final int HOME_APP_ADJ = 600;
// This is a process holding an application service -- killing it will not
// have much of an impact as far as the user is concerned.
static final int SERVICE_ADJ = 500;
// This is a process with a heavy-weight application. It is in the
// background, but we want to try to avoid killing it. Value set in
// system/rootdir/init.rc on startup.
static final int HEAVY_WEIGHT_APP_ADJ = 400;
// This is a process currently hosting a backup operation. Killing it
// is not entirely fatal but is generally a bad idea.
static final int BACKUP_APP_ADJ = 300;
// This is a process only hosting components that are perceptible to the
// user, and we really want to avoid killing them, but they are not
// immediately visible. An example is background music playback.
static final int PERCEPTIBLE_APP_ADJ = 200;
// This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear.
static final int VISIBLE_APP_ADJ = 100;
static final int VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1;
// This is the process running the current foreground app. We'd really
// rather not kill it!
static final int FOREGROUND_APP_ADJ = 0;
// This is a process that the system or a persistent process has bound to,
// and indicated it is important.
static final int PERSISTENT_SERVICE_ADJ = -700;
// This is a system persistent process, such as telephony. Definitely
// don't want to kill it, but doing so is not completely fatal.
static final int PERSISTENT_PROC_ADJ = -800;
// The system process runs at the default adjustment.
static final int SYSTEM_ADJ = -900;
// Special code for native processes that are not being managed by the system (so
// don't have an oom adj assigned by the system).
static final int NATIVE_ADJ = -1000;
四、查询命令
1.adb shell dumpsys activity o
可查询app对应的adj等级,同时可通过OOM levels查看在某一个等级升级内存清理
注:
(ActivityManagerService dump 可查 activity o)
C:\Users\99418>adb shell dumpsys activity o
[dump_debug] dumpAppId:-1, dumpPackage:null
OOM levels:
-900: SYSTEM_ADJ ( 73,728K)
-800: PERSISTENT_PROC_ADJ ( 73,728K)
-700: PERSISTENT_SERVICE_ADJ ( 73,728K)
0: FOREGROUND_APP_ADJ ( 73,728K)
100: VISIBLE_APP_ADJ ( 92,160K)
200: PERCEPTIBLE_APP_ADJ ( 110,592K)
250: PERCEPTIBLE_LOW_APP_ADJ ( 129,024K)
300: BACKUP_APP_ADJ ( 221,184K)
400: HEAVY_WEIGHT_APP_ADJ ( 221,184K)
500: SERVICE_ADJ ( 221,184K)
600: HOME_APP_ADJ ( 221,184K)
700: PREVIOUS_APP_ADJ ( 221,184K)
800: SERVICE_B_ADJ ( 221,184K)
900: CACHED_APP_MIN_ADJ ( 221,184K)
999: CACHED_APP_MAX_ADJ ( 322,560K)
Process OOM control (43 total, non-act at 1, non-svc at 1):
PERS #42: sys F/ /PER trm: 0 2811:system/1000 (fixed)
oom: max=-900 curRaw=-900 setRaw=-900 cur=-900 set=-900 //-900 adj最高级别SYSTEM_ADJ
state: cur=PER set=PER lastPss=144MB lastSwapPss=21MB lastCachedPss=0.00
cached=false empty=false hasAboveClient=false
···
Proc # 0: fore R/A/BFGS trm: 0 3930:com.android.launcher3/u0a98 (service)
com.android.launcher3/com.android.quickstep.TouchInteractionService<=Proc{2979:com.android.systemui/u0a97}
oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0 //0 ADJ级别FOREGROUND_APP_ADJ
state: cur=BFGS set=BFGS lastPss=39MB lastSwapPss=20MB lastCachedPss=0.00
cached=false empty=false hasAboveClient=false
···
Proc #31: cch+75 B/ /CEM trm: 0 4303:com.android.externalstorage/u0a49 (cch-empty)
oom: max=1001 curRaw=975 setRaw=975 cur=975 set=975 //975 ADJ值太大,容易被清理
state: cur=CEM set=CEM lastPss=6.7MB lastSwapPss=0.00 lastCachedPss=6.7MB
cached=true empty=true hasAboveClient=false
mHomeProcess: ProcessRecord{6b95785 3930:com.android.launcher3/u0a98}
mPreviousProcess: null
2.adb shell dumpsys activity p
针对进程的oom查询更加精确
(ActivityManagerService dump 可查 activity p)
C:\Users\99418>adb shell dumpsys activity p
···
*APP* UID 10098 ProcessRecord{6b95785 3930:com.android.launcher3/u0a98} //uid及pid
user #0 uid=10098 gids={50098, 20098, 9997}
mRequiredAbi=arm64-v8a instructionSet=null
dir=/product/priv-app/MtkLauncher3QuickStep/MtkLauncher3QuickStep.apk //安装位置 publicDir=/product/priv-app/MtkLauncher3QuickStep/MtkLauncher3QuickStep.apk data=/data/user/0/com.android.launcher3 //app数据位置
packageList={com.android.launcher3} //包名
compat={240dpi always-compat}
thread=android.app.IApplicationThread$Stub$Proxy@9e5af
pid=3930 starting=false
lastActivityTime=-1h2m37s269ms lastPssTime=-1m3s479ms pssStatType=0 nextPssTime=+13m56s441ms
adjSeq=834 lruSeq=234 lastPss=38MB lastSwapPss=13MB lastCachedPss=0.00 lastCachedSwapPss=0.00 //pss 内存情况
procStateMemTracker: best=1 (1=1 1.5x, 2=1 2.25x)
cached=false empty=false
oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0 //oom相关情况
lastCompactTime=3986304 lastCompactAction=4 mCurSchedGroup=2 setSchedGroup=2 systemNoUi=false trimMemoryLevel=0
curProcState=6 mRepProcState=6 pssProcState=6 setProcState=6 lastStateTime=-1h1m37s411ms
hasShownUi=true pendingUiClean=true hasAboveClient=false treatLikeActivity=false
reportedInteraction=true time=-1h6m45s79ms
hasClientActivities=false foregroundActivities=true (rep=true)
lastTopTime=-1h1m37s411ms
startSeq=17
mountMode=DEFAULT
lastRequestedGc=-1h5m21s110ms lastLowMemory=-1h5m21s110ms reportLowMemory=false
whitelistManager=true
Activities:
- ActivityRecord{550ca96 u0 com.android.launcher3/.Launcher t4}
Recent Tasks:
- TaskRecord{c8c4dbc #4 I=com.android.launcher3/.Launcher U=0 StackId=0 sz=1}
···
Services:
- ServiceRecord{a7b3c5e u0 com.android.launcher3/.notification.NotificationListener}
···
Published Providers:
- com.android.launcher3.LauncherProvider
-> ContentProviderRecord{2d49145 u0 com.android.launcher3/.LauncherProvider}
···
Connected Providers:
- 15f9e1/com.android.providers.settings/.SettingsProvider->3930:com.android.launcher3/u0a98 s1/1 u0/0 +1h6m44s879ms
Receivers:
- ReceiverList{6197c9 3930 com.android.launcher3/10098/u0 remote:78f0bd0}
···
···
3.adb shell dumpsys meminfo
系统内存总体情况查询及各个进程的占用情况。例如RAM总共多大、launcher3使用内存情况等等
(ActivityManagerService中MemBinder dump 可查 meminfo
ActivityManagerService.setSystemProcess:
ServiceManager.addService("meminfo", new MemBinder(this));
)
C:\Users\99418>adb shell dumpsys meminfo
Applications Memory Usage (in Kilobytes):
Uptime: 4300431 Realtime: 4384400
Total PSS by process:
159,633K: system (pid 2811)
96,309K: com.android.systemui (pid 2979)
···
672K: lmkd (pid 467)
669K: thermal (pid 677)
575K: usb_mode_switch (pid 325)
Total PSS by OOM adjustment:
644,944K: Native
79,077K: surfaceflinger (pid 468)
60,618K: zygote (pid 2683)
35,930K: app_process32 (pid 17377)
33,885K: zygote64 (pid 2682)
21,714K: init (pid 1)
···
159,633K: System
159,633K: system (pid 2811)
145,094K: Persistent
96,309K: com.android.systemui (pid 2979)
···
21,633K: Persistent Service
21,633K: com.android.bluetooth (pid 3709)
41,161K: Foreground
41,161K: com.android.launcher3 (pid 3930 / activities)
17,132K: Visible
8,801K: android.ext.services (pid 3657)
8,331K: com.android.smspush (pid 4100)
18,848K: Perceptible
18,848K: com.android.inputmethod.latin (pid 3583)
122,876K: Cached
28,258K: com.mediatek.security (pid 4325)
···
Total PSS by category:
332,730K: .so mmap
160,164K: EGL mtrack
81,429K: Native
67,972K: .apk mmap
56,812K: .jar mmap
···
Total RAM: 3,921,160K (status normal)
Free RAM: 2,671,288K ( 122,876K cached pss + 695,064K cached kernel + 1,853,348K free)
Used RAM: 1,234,709K (1,048,445K used pss + 186,264K kernel)
Lost RAM: 163,273K
ZRAM: 44,100K physical used for 217,916K in swap (2,156,632K total swap)
Tuning: 384 (large 512), oom 322,560K, restore limit 107,520K (high-end-gfx)
4.adb shell dumpsys procstats -a
procstats工具用于分析应用内存在一段时间内的使用情况(而不像 meminfo 一样在特定时间点捕获快照)。
(ProcessStatsService dump 可查 procstats
ActivityManagerService.setSystemProcess:
ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
)
C:\Users\99418>adb shell dumpsys procstats -a
···
* com.android.launcher3 / u0a17 / v25:
Process com.android.launcher3 (unique, 5 entries):
SOn /Norm/Top : +1h16m18s862ms
Home : +10m38s557ms
Mod /Home : +4m9s959ms
Low /Top : +10m0s237ms
Home : +20h51m49s712ms
TOTAL : +22h32m57s327ms
myID=1d6fe68 mCommonProcess=1d6fe68 mPackage=com.android.launcher3
···
Memory usage:
Kernel : 140MB (16 samples)
Native : 573KB (16 samples)
Persist: 52MB (12 samples)
Top : 5.6MB (33 samples)
ImpFg : 56 (9 samples)
Service: 5.3MB (23 samples)
Receivr: 418 (17 samples)
Home : 4.6MB (3 samples)
CchEmty: 16MB (13 samples)
Cached : 204MB (16 samples)
Free : 1.3GB (16 samples)
Z-Ram : 4.0KB (16 samples)
TOTAL : 1.7GB
Start time: 2013-01-18 16:50:09
Total elapsed time: +2h20m46s35ms (partial) (swapped-out-pss) libart.so
···
五、配置
lmkd目的为了保护设备在低内存下的正常运行。
1.查询
Android 9及以下
adj阀门值:
/sys/module/lowmemorykiller/parameters/adj
minfree阀门值:
/sys/module/lowmemorykiller/parameters/minfree
例如
Q6-Q260S(S0):/ $ cat /sys/module/lowmemorykiller/parameters/minfree
18432,23040,27648,32256,36864,46080
Q6-Q260S(S0):/ $ cat /sys/module/lowmemorykiller/parameters/adj
0,100,200,300,900,906
Android 10
adb shell getprop sys.lmk.minfree_levels
例如:
C:\Users\99418>adb shell getprop sys.lmk.minfree_levels
18432:0,23040:100,27648:200,32256:250,55296:900,80640:950
xxx:xx //minfree:adj 注意:minfree * 4 = 真实值kb
2.原理
a.框架
system_server <--> lmkd (<--> kernel)
system_server: ActivityManagerService (ProcessList)
lmkd:system/core/lmkd
b.adj和minfree的值都有AMS通过socket传递到lmkd -- 注:Android 7.1源码
关注核心类、方法、变量:
com.android.server.am.ProcessList
// Memory pages are 4K.
static final int PAGE_SIZE = 4*1024;
// These are the various interesting memory levels that we will give to
// the OOM killer. Note that the OOM killer only supports 6 slots, so we
// can't give it a different value for every possible kind of process.
private int[] mOomAdj = new int[] {
FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
};
// These are the low-end OOM level limits. This is appropriate for an
// HVGA or smaller phone with less than 512MB. Values are in KB.
private final int[] mOomMinFreeLow = new int[] {
12288, 18432, 24576,
36864, 43008, 49152
};
// These are the high-end OOM level limits. This is appropriate for a
// 1280x800 or larger screen with around 1GB RAM. Values are in KB.
private final int[] mOomMinFreeHigh = new int[] {
73728, 92160, 110592,
129024, 147456, 184320
};
//方法参数:displayWidth\displayHeight 关注设备屏幕尺寸。注:adb shell wm size 可查
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
// Scale buckets from avail memory: at 300MB we use the lowest values to
// 700MB or more for the top values.
float scaleMem = ((float)(mTotalMemMb-350))/(700-350);//mTotalMemMb 关注设备RAM总量
// Scale buckets from screen size.
int minSize = 480*800; // 384000
int maxSize = 1280*800; // 1024000 230400 870400 .264
float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
if (false) {
Slog.i("XXXXXX", "scaleMem=" + scaleMem);
Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth
+ " dh=" + displayHeight);
}
float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
if (scale < 0) scale = 0;
else if (scale > 1) scale = 1;
int minfree_adj = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);
int minfree_abs = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);
if (false) {
Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs);
}
final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
//第一次:更新mOomMinFree数据,由设备RAM和屏幕尺寸决定
for (int i=0; i<mOomAdj.length; i++) {
int low = mOomMinFreeLow[i];
int high = mOomMinFreeHigh[i];
if (is64bit) {
// Increase the high min-free levels for cached processes for 64-bit
if (i == 4) high = (high*3)/2;
else if (i == 5) high = (high*7)/4;
}
mOomMinFree[i] = (int)(low + ((high-low)*scale));
}
//第二次:更新mOomMinFree数据
//由framework-res自定义的config_lowMemoryKillerMinFreeKbytesAbsolute值决定
//minfree_abs的取值 一般比mOomMinFree[mOomAdj.length - 1]的值大一点或小一点
if (minfree_abs >= 0) {
for (int i=0; i<mOomAdj.length; i++) {
mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i]
/ mOomMinFree[mOomAdj.length - 1]);
}
}
//第三次:更新mOomMinFree数据
//由framework-res自定义的config_lowMemoryKillerMinFreeKbytesAdjust值决定
//minfree_adj的取值 就是从mOomMinFree[i]中增加或减小一点,属于微调型
if (minfree_adj != 0) {
for (int i=0; i<mOomAdj.length; i++) {
mOomMinFree[i] += (int)((float)minfree_adj * mOomMinFree[i]
/ mOomMinFree[mOomAdj.length - 1]);
if (mOomMinFree[i] < 0) {
mOomMinFree[i] = 0;
}
}
}
// The maximum size we will restore a process from cached to background, when under
// memory duress, is 1/3 the size we have reserved for kernel caches and other overhead
// before killing background processes.
mCachedRestoreLevel = (getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024) / 3;
// Ask the kernel to try to keep enough memory free to allocate 3 full
// screen 32bpp buffers without entering direct reclaim.
int reserve = displayWidth * displayHeight * 4 * 3 / 1024;
int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust);
int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute);
if (reserve_abs >= 0) {
reserve = reserve_abs;
}
if (reserve_adj != 0) {
reserve += reserve_adj;
if (reserve < 0) {
reserve = 0;
}
}
if (write) {
ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
buf.putInt(LMK_TARGET);
for (int i=0; i<mOomAdj.length; i++) {
buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);//PAGE_SIZE 内存页占用4kb
buf.putInt(mOomAdj[i]);
}
writeLmkd(buf);//将mOomAdj和mOomMinFree通过socket传递给lmkd
SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
}
// GB: 2048,3072,4096,6144,7168,8192
// HC: 8192,10240,12288,14336,16384,20480
}
注:
sys.sysctl.extra_free_kbytes
可用于保留启动后台回收 (kswapd)的阈值和启动直接回收(通过分配进程)的阈值之间的额外可用内存
c.总结
adj值。保持不变
minfree值。可通过overlay自定义config_lowMemoryKillerMinFreeKbytesAbsolute/config_lowMemoryKillerMinFreeKbytesAdjust
3.改动建议
因系统根据RAM和设备屏幕大小经过一轮初始化,一般来说,minfree也不用特定更改。
但是对于1G或2G的RAM,涉及内存使用情况,也可能需要调节。这里就直接overlay
config_lowMemoryKillerMinFreeKbytesAbsolute/config_lowMemoryKillerMinFreeKbytesAdjust即可
六、日志分析
关注TAG(lowmemorykiller) 查看是否有对应的kill行为
Android 9及之前 kill行为由内核层操作,同步在kernel日志中关注kswapd
Android 10 kill行为直接由lmkd操作,主要关注logcat日志即可
七、参考学习
https://www.jianshu.com/p/221f4a246b45
https://www.jianshu.com/p/980b6ce4e051
https://www.jianshu.com/p/7bd3d0ee8a56
http://gityuan.com/2018/05/19/android-process-adj/
https://blog.csdn.net/zhzhangnews/article/details/109671845
https://lightingsui.github.io/2021/01/21/Android%E4%B8%AD%E7%9A%84LowMemoryKiller%E6%9C%BA%E5%88%B6/
https://source.android.com/devices/tech/perf/low-ram
https://source.android.google.cn/devices/architecture/kernel/reqs-interfaces?hl=zh-cn
https://source.android.google.cn/devices/tech/perf/lmkd?hl=zh-cn
https://github.com/reklaw-tech/reklaw_blog/blob/master/Android/ULMK
https://source.android.com/devices/tech/debug/procstats?hl=zh-cn
https://blog.csdn.net/Invoker123/article/details/108632459
https://blog.csdn.net/liaochaoyun/article/details/103035222
https://blog.csdn.net/mcsbary/article/details/104609109