Android系统 实现低内存白名单防LMK原理分析

简介: Android系统 实现低内存白名单防LMK原理分析

背景知识

Android系统为了保证系统流畅性和稳定性,在内存不足时会采取一些措施来释放内存,比如杀死一些后台进程。系统会根据每个进程的重要性和占用内存大小来决定杀死哪些进程,这个过程称为低内存杀死(Low Memory Killer,LMK)。系统会为每个进程分配一个OOM_ADJ值,表示该进程的重要性,值越低表示越重要,越不容易被杀死。系统还会定义一组OOM_MINFREE值,表示不同重要性的进程在内存不足时的最小保留内存。当系统可用内存低于某个OOM_MINFREE值时,系统就会杀死所有OOM_ADJ值高于或等于该值的进程。

但是,有些情况下,我们可能需要让某些进程不受LMK的影响,即使它们的OOM_ADJ值很高,也不会被杀死。这时候,我们就可以使用白名单低内存过滤来指定哪些进程可以绕过LMK的检查,而不需要修改它们的OOM_ADJ值。白名单低内存过滤是一个字符串数组,存储在frameworks/base/core/res/res/values/config.xml文件中,名为low_memory_killer_tracker_whitelist。我们可以在这个数组中添加我们想要保护的进程的包名,例如com.xxx.r58_test。

  1. 通过adb命令获取某个应用占用的内存:
    你可以使用以下的adb shell命令来获取某个应用占用的内存信息:
130|rk3568_r:/ # dumpsys meminfo package_name
Applications Memory Usage (in Kilobytes):
Uptime: 1385734 Realtime: 1385734
** MEMINFO in pid 3307 [package_name] **
                   Pss  Private  Private     Swap      Rss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty    Total     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------   ------
  Native Heap    20510    20428        0        0    23216    29372    19336     6423
  Dalvik Heap     2189     2036        0        0     6516     4664     2333     2331
 Dalvik Other     1346      744        0        0     2540
        Stack      688      688        0        0      696
       Ashmem        2        0        0        0        8
    Other dev       20        0       20        0      252
     .so mmap     4803      208        0        0    55596
    .jar mmap     1725        0        0        0    27404
    .apk mmap      931        0        0        0    25092
    .dex mmap     2975     2964        0        0     3076
    .oat mmap       73        0        0        0     2164
    .art mmap     7812     7368        4        0    20052
   Other mmap       62       32        0        0     1028
      Unknown      480      456        0        0     1160
        TOTAL    43616    34924       24        0    43616    34036    21669     8754
 App Summary
                       Pss(KB)                        Rss(KB)
                        ------                         ------
           Java Heap:     9408                          26568
         Native Heap:    20428                          23216
                Code:     3172                         114612
               Stack:      688                            696
            Graphics:        0                              0
       Private Other:     1252
              System:     8668
             Unknown:                                    3708
           TOTAL PSS:    43616            TOTAL RSS:   168800      TOTAL SWAP (KB):        0
 Objects
               Views:        8         ViewRootImpl:        1
         AppContexts:        7           Activities:        2
              Assets:        6        AssetManagers:        0
       Local Binders:       11        Proxy Binders:       32
       Parcel memory:        6         Parcel count:       24
    Death Recipients:        0      OpenSSL Sockets:        0
            WebViews:        0
 SQL
         MEMORY_USED:        0
  PAGECACHE_OVERFLOW:        0          MALLOC_SIZE:        0

其中,<package_name>是你想要查询的应用的包名。这个命令会返回一份详细的内存使用报告,包括PSS,Private Dirty,Private Clean等内存使用情况。

dumpsys meminfo输出,应用内存使用和其他重要值的详细表格:

项目 简介
应用包名 package_name 应用的包名
总PSS 43616 KB 估计的应用实际使用的内存量
总RSS 168800 KB 应用在物理内存中的实际占用
Java堆 Java对象存储的地方
- PSS 9408 KB 估计的Java堆实际使用的内存量
- RSS 26568 KB Java堆在物理内存中的实际占用
Native堆 C和C++代码分配的内存
- PSS 20428 KB 估计的Native堆实际使用的内存量
- RSS 23216 KB Native堆在物理内存中的实际占用
- 已分配 19336 KB 已分配的Native堆内存
- 未使用 6423 KB 未使用的Native堆内存
代码 应用代码的内存
- PSS 3172 KB 估计的代码实际使用的内存量
- RSS 114612 KB 代码在物理内存中的实际占用
堆栈 应用线程的堆栈内存
- PSS 688 KB 估计的堆栈实际使用的内存量
- RSS 696 KB 堆栈在物理内存中的实际占用
系统 系统使用的内存
- PSS 8668 KB 估计的系统实际使用的内存量
对象 应用创建的对象数量
- Views 8 应用中的视图数量
- Activities 2 应用中的活动数量
SQL 应用使用的SQLite数据库的内存信息
- MEMORY_USED 0 KB 使用的数据库内存

根据我查阅的资料 , 可以关注总PSS值、Native堆的已分配和未使用内存,这些通常是最关键的指标。

分析建议:

  • 关注PSS: PSS是一个很好的指标,它考虑了共享内存。如果PSS值很高,那么应用可能正在使用大量内存。
  • Java和Native Heap: 查看Java和Native Heap的大小、已分配和未使用的内存。如果已分配的内存接近总大小,并且可用的内存很少,那么应用可能很快就会耗尽内存。
  • 内存映射: 如果应用加载了大量的库或资源,那么这些内存映射可能会很大。这可能是一个优化的地方,特别是如果应用加载了不需要的库或资源。
  • 对象数量: 如果某些对象的数量异常地高,那么可能存在内存泄漏。
  1. 通过adb命令获取某个应用的OOM_ADJ值:

在Android系统中,OOM_ADJ值被用来决定当系统内存不足时,哪些进程应该被杀死。你可以使用以下的adb shell命令来获取某个应用的OOM_ADJ值:

  1. 使用ps -A | grep com.xxx.r58_test找到了应用的进程ID,即3307
  2. 使用cat /proc/3307/oom_score_adj命令查看了该进程的oom_score_adj值,其值为0

这意味着该应用的oom_score_adj值为200,表示该应用是一个后台进程。这个值越高,进程被杀死的可能性就越大;这个值越低,进程被杀死的可能性就越小。

要手动修改一个进程的 oom_score_adj 值,需要具有root权限。如果设备已经root,可以按照以下步骤操作:

  1. 获取root权限:
adb shell su
  1. 修改oom_score_adj:
    你可以使用echo命令将新的值写入oom_score_adj文件。例如,要将com.btf.r58_test的值设置为900,可以执行:
echo 900 > /proc/3307/oom_score_adj
  1. 验证修改:
    再次使用cat命令来确认oom_score_adj的值已经被修改:
cat /proc/3307/oom_score_adj

修改oom_score_adj值可能会导致进程在内存紧张时更容易被系统杀死。

如果设备没有root权限,那么无法修改oom_score_adj值。在这种情况下,可能需要考虑其他方法来模拟内存压力或测试应用的行为。

  1. OOM_ADJ值的范围:

在Android系统中,OOM_ADJ的值范围是从-1000(永远不会被杀死)到1000(总是被优先杀死)。这个值越高,进程被杀死的可能性就越大。以下是OOM_ADJ值的详细表格:

OOM_ADJ值 描述 备注
-1000 系统进程 这些进程是系统的一部分,不会被杀死。
-900 前台应用 用户正在与这些应用交互,或者这些应用正在执行某些用户期望的操作(例如播放音乐)。这些应用最后被杀死。
-800 可见应用 这些应用对用户可见,但用户并未与它们交互。例如,它们可能在屏幕上显示一个对话框,但用户正在与另一个应用交互。
-700 次要服务 这些应用正在运行一些用户可能注意到的服务。例如,正在下载或上传文件。
-600 后台进程 用户已经停止与这些应用交互,但它们仍然在运行一些服务。这些应用在内存不足时会被优先杀死。
-500 内容提供者 这些应用提供了其他应用可能正在使用的内容。
-400 空服务 这些应用没有运行任何代码,但系统仍然保留它们,以便在需要时快速启动。
0至1000 缓存进程 这些应用没有运行任何代码,也没有被任何活动的应用引用。它们只是在内存中,以便在需要时快速启动。这些应用在内存不足时会首先被杀死。

修改方法

要增加白名单低内存过滤,我们只需要在low_memory_killer_tracker_whitelist数组中添加我们想要保护的进程的包名即可。例如,如果我们想要让com.xxx.r58_test这个应用不受LMK的影响,那么我们可以在数组中添加com.xxx.r58_test这个字符串。具体的修改代码如下:

--- a/frameworks/base/core/res/res/values/config.xml
+++ b/frameworks/base/core/res/res/values/config.xml
@@ -4403,4 +4403,10 @@
 
     <!-- Component names of the services which will keep critical code path warm -->
     <string-array name="config_keep_warming_services" translatable="false" />
+    <string-array translatable="false" name="low_memory_killer_tracker_whitelist" >
+  <item>"com.xxx.r58_test"</item>
+ </string-array>
 </resources>
diff --git a/frameworks/base/core/res/res/values/symbols.xml b/frameworks/base/core/res/res/values/symbols.xml
index 3ef0a8dc9d..fc86ead8b6 100644
--- a/frameworks/base/core/res/res/values/symbols.xml
+++ b/frameworks/base/core/res/res/values/symbols.xml
@@ -22,6 +22,8 @@
        Can be referenced in java code as: com.android.internal.R.<type>.<name>
        and in layout xml as: "@*android:<type>/<name>"
   -->
+   <java-symbol type="array" name="low_memory_killer_tracker_whitelist" />
   <java-symbol type="id" name="account_name" />
   <java-symbol type="id" name="account_row_icon" />
   <java-symbol type="id" name="account_row_text" />

但是,这样修改只是改变了白名单低内存过滤中的内容,并没有改变LMK的实际行为。也就是说,即使我们在白名单中添加了com.xxx.r58_test这个字符串,LMK还是会根据该应用的OOM_ADJ值和OOM_MINFREE值来判断是否要杀死它。因此,我们还需要在OomAdjuster.java文件中修改LMK的逻辑,让它在检查每个进程是否需要被杀死时,先判断该进程是否在白名单中。如果是,则跳过该进程,不进行后续的检查和操作;如果否,则按照原来的逻辑继续执行。需要在applyOomAdjLocked方法中添加一个isInWhitelist方法来判断一个进程是否在白名单中,并在设置每个进程的oom_adj值时调用该方法。具体的修改代码如下:

diff --git a/frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java b/frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
index faa7dce316..4fc8a4698f 100644
--- a/frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import android.content.res.Resources;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
@@ -71,6 +72,7 @@ import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
 import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
+import java.util.List;
 import android.app.ActivityManager;
 import android.app.ApplicationExitInfo;
 import android.app.usage.UsageEvents;
@@ -2144,6 +2146,25 @@ public final class OomAdjuster {
         }
     }
 
+
+private List<String> mLmKillerBypassPackages = new ArrayList<>();
+
+private String[] lmKillerTrackerWhitelist = Resources.getSystem().getStringArray(
+    com.android.internal.R.array.low_memory_killer_tracker_whitelist);
+private List<String> lmKillerBypassPackages = Arrays.asList(lmKillerTrackerWhitelist);
+
+    private boolean isInWhitelist(ProcessRecord pr) {
+        String pkgName = pr.info.packageName;
+    
+        for (String token : mLmKillerBypassPackages) { 
+            if (pkgName.startsWith(token)) {
+                return true; 
+            }
+        }
+        return false;
+    }
+
+
     /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
     @GuardedBy("mService")
     private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
@@ -2163,6 +2184,30 @@ public final class OomAdjuster {
                 // Perform a minor compaction when a perceptible app becomes the prev/home app
                 // Perform a major compaction when any app enters cached
                 // reminder: here, setAdj is previous state, curAdj is upcoming state
+           boolean isAppWhiteProcess = false;
+            if( isInWhitelist(app) && (app.curAdj > ProcessList.PERSISTENT_SERVICE_ADJ))
+                isAppWhiteProcess = true;
+            if(isAppWhiteProcess){
+                Slog.d(TAG,"isAppWhiteProcess so not kill!");
+               ProcessList.setOomAdj(app.pid, app.uid, ProcessList.PERSISTENT_SERVICE_ADJ);
+        if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) {
+                    String msg = "Set " + app.pid + " " + app.processName + " adj "
+                            + app.curAdj + ": " + app.adjType;
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+                }    
+                app.setAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
+                app.verifiedAdj = ProcessList.INVALID_ADJ;
+            }else{
+                ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
+                if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) {
+                    String msg = "Set " + app.pid + " " + app.processName + " adj "
+                            + app.curAdj + ": " + app.adjType;
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+                }
+                app.setAdj = app.curAdj;
+                app.verifiedAdj = ProcessList.INVALID_ADJ;
+ }
+
                 if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
                         (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
                                 app.curAdj == ProcessList.HOME_APP_ADJ)) {

修改效果

修改后,com.xxx.r58_test这个应用不会被LMK杀死,即使它的OOM_ADJ值很高,也不会受到OOM_MINFREE值的限制。这样可以保证该应用的后台服务和功能正常运行,比如接收消息、执行任务等。这也可能带来一些性能问题和资源浪费,比如该应用占用过多内存导致其他应用被杀死或无法启动,或者该应用后台执行一些无用或恶意的操作消耗CPU和电量。在使用白名单低内存过滤,需要考虑到应用的实际需求 尽量避免对系统和其他应用造成负面影响。

希望这篇文章能对您有所帮助。如果您还有其他问题或建议,请留言与私信。

相关文章
|
9天前
|
存储 监控 调度
Android系统服务:WMS、AMS相关知识
参考文献 Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析 Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析 Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析 Android窗口管理服务WindowManagerService显示窗口动画的原理分析
|
11天前
|
程序员 C语言 C++
【C语言基础】:动态内存管理(含经典笔试题分析)-2
【C语言基础】:动态内存管理(含经典笔试题分析)
|
11天前
|
程序员 编译器 C语言
【C语言基础】:动态内存管理(含经典笔试题分析)-1
【C语言基础】:动态内存管理(含经典笔试题分析)
|
1天前
|
安全 搜索推荐 Android开发
探索安卓和iOS系统的优劣与特点
在移动操作系统领域,安卓和iOS一直是最热门的两个选择。本文将探讨安卓和iOS系统的优劣与特点,帮助读者更好地了解这两个操作系统,并为选择合适的移动设备提供参考。
5 0
|
1天前
|
存储 Java
JavaSE 面向对象程序设计初级 静态static 包package 常量final 代码块 代码实操理论内存原理详解
JavaSE 面向对象程序设计初级 静态static 包package 常量final 代码块 代码实操理论内存原理详解
6 0
|
4天前
|
缓存 Java Linux
Android 匿名内存深入分析
Android 匿名内存深入分析
9 0
|
12天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的安卓的微博客系统的详细设计和实现
基于SpringBoot+Vue+uniapp的安卓的微博客系统的详细设计和实现
11 0
|
Java 调度 Android开发
android体系课-系统启动流程-之zygote进程启动过程源码分析
笔者刚开始学习Android的时候也和大部分同学一样,只会使用一些应用层面的知识,对于一些比较常见的开源框架如<mark>RxJava</mark>,<mark>OkHttp</mark>,<mark>Retrofit</mark>,以及后来谷歌推出的<mark>协程</mark>等,都只在使用层面,对于他们<mark>内部原理</mark>,基本没有去了解觉得够用就可以了,又比如Activity,Service等四大组件的使用原理,系统开机过程,Launcher启动过程等知之甚少,知其然而不知其所以然,结果就是出现某些问题,不知道从哪里找原因,只能依赖万能的百度,但是百度看多了,你会发现自己
|
Java 调度 Android开发
android体系课-系统启动流程-之SystemServer启动过程源码分析
笔者刚开始学习Android的时候也和大部分同学一样,只会使用一些应用层面的知识,对于一些比较常见的开源框架如<mark>RxJava</mark>,<mark>OkHttp</mark>,<mark>Retrofit</mark>,以及后来谷歌推出的<mark>协程</mark>等,都只在使用层面,对于他们<mark>内部原理</mark>,基本没有去了解觉得够用就可以了,又比如Activity,Service等四大组件的使用原理,系统开机过程,Launcher启动过程等知之甚少,知其然而不知其所以然,结果就是出现某些问题,不知道从哪里找原因,只能依赖万能的百度,但是百度看多了,你会发现自己