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和电量。在使用白名单低内存过滤,需要考虑到应用的实际需求 尽量避免对系统和其他应用造成负面影响。

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

相关文章
|
6月前
|
存储 弹性计算 缓存
阿里云服务器ECS经济型、通用算力、计算型、通用和内存型选购指南及使用场景分析
本文详细解析阿里云ECS服务器的经济型、通用算力型、计算型、通用型和内存型实例的区别及适用场景,涵盖性能特点、配置比例与实际应用,助你根据业务需求精准选型,提升资源利用率并降低成本。
454 3
|
8月前
|
机器学习/深度学习 存储 算法
NoProp:无需反向传播,基于去噪原理的非全局梯度传播神经网络训练,可大幅降低内存消耗
反向传播算法虽是深度学习基石,但面临内存消耗大和并行扩展受限的问题。近期,牛津大学等机构提出NoProp方法,通过扩散模型概念,将训练重塑为分层去噪任务,无需全局前向或反向传播。NoProp包含三种变体(DT、CT、FM),具备低内存占用与高效训练优势,在CIFAR-10等数据集上达到与传统方法相当的性能。其层间解耦特性支持分布式并行训练,为无梯度深度学习提供了新方向。
315 1
NoProp:无需反向传播,基于去噪原理的非全局梯度传播神经网络训练,可大幅降低内存消耗
|
2月前
|
设计模式 缓存 Java
【JUC】(4)从JMM内存模型的角度来分析CAS并发性问题
本篇文章将从JMM内存模型的角度来分析CAS并发性问题; 内容包含:介绍JMM、CAS、balking犹豫模式、二次检查锁、指令重排问题
118 1
|
7月前
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
240 0
|
4月前
|
缓存 监控 Linux
CentOS系统如何查看当前内存容量。
以上方法都不需要特殊软件或者复杂配置即可执行,在CentOS或其他Linux发行版中都适合运行,并且它们各自透露出不同角度对待问题解答方式:从简单快速到深入详尽;从用户态到核心态;从操作层数到硬件层数;满足不同用户需求与偏好。
349 8
|
5月前
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
268 4
AI代理内存消耗过大?9种优化策略对比分析
|
5月前
|
存储 缓存 监控
手动清除Ubuntu系统中的内存缓存的步骤
此外,只有系统管理员或具有适当权限的用户才能执行这些命令,因为这涉及到系统级的操作。普通用户尝试执行这些操作会因权限不足而失败。
888 22
|
9月前
|
监控 Linux Python
Linux系统资源管理:多角度查看内存使用情况。
要知道,透过内存管理的窗口,我们可以洞察到Linux系统运行的真实身姿,如同解剖学家透过微观镜,洞察生命的奥秘。记住,不要惧怕那些高深的命令和参数,他们只是你掌握系统"魔法棒"的钥匙,熟练掌握后,你就可以骄傲地说:Linux,我来了!
305 27
|
9月前
|
存储 Java
课时4:对象内存分析
接下来对对象实例化操作展开初步分析。在整个课程学习中,对象使用环节往往是最棘手的问题所在。
|
9月前
|
Java 编译器 Go
go的内存逃逸分析
内存逃逸分析是Go编译器在编译期间根据变量的类型和作用域,确定变量分配在堆上还是栈上的过程。如果变量需要分配在堆上,则称作内存逃逸。Go语言有自动内存管理(GC),开发者无需手动释放内存,但编译器需准确分配内存以优化性能。常见的内存逃逸场景包括返回局部变量的指针、使用`interface{}`动态类型、栈空间不足和闭包等。内存逃逸会影响性能,因为操作堆比栈慢,且增加GC压力。合理使用内存逃逸分析工具(如`-gcflags=-m`)有助于编写高效代码。
187 2

热门文章

最新文章