一、簡介
進程的啟動分冷啟動和熱啟動,當用戶退出某一個進程的時候,并不會真正的將進程退出,而是將這個進程放到后臺,以便下次啟動的時候可以馬上啟動起來,這個過程名為熱啟動,這也是Android的設計理念之一。這個機制會帶來一個問題,每個進程都有自己獨立的內存地址空間,隨著應用打開數量的增多,系統(tǒng)已使用的內存越來越大,就很有可能導致系統(tǒng)內存不足。
lmkd(Low Memory Killer Daemon)是低內存終止守護進程,用來監(jiān)控運行中android系統(tǒng)內存的狀態(tài),通過終止最不必要的進程來應對內存壓力較高的問題,使系統(tǒng)以可接受的水平運行。
這里以Android7.1和Android10為對象分析
二、實現(xiàn)原理
所有應用進程都是從zygote孵化出來的,記錄在AMS中mLruProcesses列表中,由AMS進行統(tǒng)一管理,AMS中會根據進程的狀態(tài)更新進程對應的oom_adj值,這個值會通過socket傳遞給lmkd。lmdk根據內核的版本情況,或傳遞給kernel或自身處理低內存回收機制。為騰出更多的內存空間,在內存達到一定閥值時會觸發(fā)清理oom_adj值高的進程。
三、必備知識
1.內存基礎概念
在linux下表示內存的耗用情況有四種不同的表現(xiàn)形式:
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
系統(tǒng)內存總體情況查詢及各個進程的占用情況。例如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.改動建議
因系統(tǒng)根據RAM和設備屏幕大小經過一輪初始化,一般來說,minfree也不用特定更改。
但是對于1G或2G的RAM,涉及內存使用情況,也可能需要調節(jié)。這里就直接overlay
config_lowMemoryKillerMinFreeKbytesAbsolute/config_lowMemoryKillerMinFreeKbytesAdjust即可
六、日志分析
關注TAG(lowmemorykiller) 查看是否有對應的kill行為
Android 9及之前 kill行為由內核層操作,同步在kernel日志中關注kswapd
Android 10 kill行為直接由lmkd操作,主要關注logcat日志即可
七、參考學習
http://www.itdecent.cn/p/221f4a246b45
http://www.itdecent.cn/p/980b6ce4e051
http://www.itdecent.cn/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