oom_adj分析

postgres 大表merge join的时候占用了大量的work_mem导致进程被杀。

参考网上程序写了如下程序进行测试

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PAGE_SZ (1<<12)
int gb=3;
int main(){
   int i=0;
   for(i=0;i<((unsigned long)gb<<30)/PAGE_SZ;i++){
        void *m=malloc(PAGE_SZ);
        if(!m) break;
        memset(m,0,1);
   }
   printf("%d KB",i*4);
   getchar();
   return 0;
}

我的测试环境为内存4G,swap4G

程序执行后每次会申请PAGE_SZ内存,直到 (gb<<30) 内存

测试结果如下


当执行程序c333时候,d333时候。总体虚拟内存占用6G多,由于部分内存可以swap进入硬盘,程序并未崩溃

再次执行的一个程序时候会导致最先运行的程序被oom机制 kill掉。

例如下图中运行3个时候,进行到一定时候,最先运行的程序会被kill



当执行运行单个程序申请20G内存的时候一样会被kill掉,如下图。


当对a20G 这个程序进行保护。执行如下命令。

pgrep -"a20G" | while read PID; do echo -17 > /proc/$PID/oom_adj;done

-17为OOM机制对此进程失效。结果后果很严重如下图


我所有连接上的ssh都被中断,想必原因是kill不了a20G这个程序,只能把其他的能杀的都杀了吧。

最后急急忙忙去机房重启机器,查看内核日志,好像未有什么明显错误。

ssh中断后我已经无法知道还会继续发生何事。

但是我可以接上显示器去机房看的。不过为时已晚,我第一件想到的就是重启机器了。

不过大胆猜想估计也就是机器卡那边,不会有什么大的异常发生吧。

<think>我们正在讨论Android系统中的OOM(Out-Of-Memory)管理,特别是AMS(ActivityManagerService)中更新进程的oom_adj值的两个关键方法:`updateOomLevels()`和`applyOomAdjLocked()`。根据引用内容,我们可以总结如下: 1. **updateOomLevels()**:这个方法在AMS的`updateConfiguration()`过程中被调用,用于更新整个系统的OOM级别。它向`/sys/module/lowmemorykiller/parameters/`目录下的`minfree`和`adj`节点写入信息(引用[4])。这些信息定义了内存阈值(minfree)和对应的oom_adj值(adj),当可用内存低于某个阈值时,就会杀死oom_adj值大于等于对应adj值的进程。 2. **applyOomAdjLocked()**:这个方法用于将计算后的adj值应用到具体的进程。它通常在进程状态发生变化(如组件状态改变)时被调用(引用[1])。它通过`computeOomAdjLocked()`计算出一个进程的adj值(RawAdj),然后经过调整,将这个adj值设置到进程(通过写入`/proc/<pid>/oom_score_adj`)并通知lmkd(Low Memory Killer Daemon)。如果在这个过程中发现进程需要被杀死,则返回false(引用[2])。 另外,引用[3]提到,在`updateOomAdjLocked`的过程中,AMS还会通过`app.thread.scheduleTrimMemory`向应用程序发送通知,以便应用程序进行内存整理(例如回收资源)。 现在,我们来详细解释这两个方法在OOM管理中的作用和流程: ### 1. **updateOomLevels()** - **功能**:更新系统级别的OOM killer参数(minfree和adj数组)。这些参数决定了在内存不足时,系统如何选择进程来杀死。 - **调用时机**:当系统配置发生变化(如屏幕旋转、语言切换)或者系统启动时,会调用`updateConfiguration()`,进而调用`updateOomLevels()`。 - **操作**:将预设的minfree(内存阈值,单位为页)和adj(对应的oom_adj值)数组写入sysfs节点: - `/sys/module/lowmemorykiller/parameters/minfree` - `/sys/module/lowmemorykiller/parameters/adj` - **示例**:默认配置可能为: ```bash minfree: "18432,23040,27648,32256,55296,80640" adj: "0,100,200,300,900,906" ``` 这表示当可用内存低于18432页时,杀死oom_adj≥0的进程;当可用内存低于23040页时,杀死oom_adj≥100的进程,以此类推(引用[4])。 ### 2. **applyOomAdjLocked()** - **功能**:将计算出的oom_adj值应用到具体进程,并通知lmkd。 - **调用时机**:当进程的状态发生变化(例如,一个Activity被放到后台,或者Service启动/停止)时,会调用`updateOomAdjLocked()`,该方法会遍历所有需要更新的进程,对每个进程调用`computeOomAdjLocked()`计算adj值,然后调用`applyOomAdjLocked()`应用这个值。 - **操作**: 1. 通过`computeOomAdjLocked()`计算进程的原始adj值(RawAdj)。这个计算基于进程的当前状态(如是否在前台、是否有客户端连接等)。 2. 对原始adj值进行修整(例如,根据进程的类型和系统当前状态进行微调)。 3. 将修整后的adj值转换为内核使用的`oom_score_adj`值(转换公式见下文),并写入`/proc/<pid>/oom_score_adj`。 4. 如果进程的adj值发生变化,还会通过socket通知lmkd,更新lmkd的内部状态。 5. 如果进程的adj值达到某个需要立即杀死的级别(如CACHED_APP_MAX_ADJ),则返回false,表示需要杀死该进程(引用[1][2])。 - **转换公式**:oom_adjoom_score_adj的转换公式为: $$ \text{oom\_score\_adj} = \begin{cases} 1000 & \text{if } \text{oom\_adj} = 15 \\ \frac{\text{oom\_adj} \times 1000}{17} & \text{otherwise} \end{cases} $$ 其中,15是OOM_ADJUST_MAX,-17是OOM_DISABLE(引用[4]的注释)。 ### 3. **updateOomAdjLocked()** 虽然用户的问题集中在`updateOomLevels`和`applyOomAdjLocked`,但我们也需要了解`updateOomAdjLocked()`,因为它是触发`applyOomAdjLocked`的入口方法。 - **功能**:更新一个或多个进程的oom_adj值。它会在以下情况被调用: - 组件状态变化(Activity、Service、BroadcastReceiver、ContentProvider的状态变化)。 - 进程绑定关系变化。 - 系统内存状态变化等。 - **操作**: - 遍历需要更新的进程(通常是一个进程组,或者所有进程)。 - 对每个进程调用`computeOomAdjLocked()`计算adj值。 - 调用`applyOomAdjLocked()`将adj值应用到进程并处理可能的进程杀死。 - 同时,它还会通过`app.thread.scheduleTrimMemory()`向应用程序发送内存整理通知(引用[3])。 ### 异常处理与问题排查 如果在更新oom_adj的过程中遇到问题,可以检查以下方面: - **权限问题**:写入`/proc/<pid>/oom_score_adj`需要进程有足够的权限(通常是system用户或root)。 - **lmkd通信**:如果lmkd没有正确接收到adj更新,可能导致进程在内存不足时没有被正确杀死。可以通过`dumpsys lmk`查看lmkd的状态。 - **adj值计算错误**:如果某个进程的adj值计算不正确,可能是由于其状态判断错误(例如,一个后台进程被错误地标记为前台进程)。可以通过`dumpsys activity oom`查看所有进程的当前adj值。 ### 示例:查看进程的oom_score_adj ```bash adb shell cat /proc/<pid>/oom_score_adj ``` ### 示例:查看系统当前的OOM配置 ```bash adb shell cat /sys/module/lowmemorykiller/parameters/minfree adb shell cat /sys/module/lowmemorykiller/parameters/adj ``` ### 总结 - `updateOomLevels()`:配置系统级别的OOM策略(minfree和adj数组),影响整个系统的内存回收行为。 - `applyOomAdjLocked()`:将计算出的进程级别的adj值应用到具体进程(写入`/proc/<pid>/oom_score_adj`),并通知lmkd。 这两个方法共同协作,确保在内存不足时,系统能够根据进程的重要性(adj值)和当前内存状况(minfree阈值)合理地回收内存。 §§相关问题§§ 1. 如何调试Android系统中进程被杀的问题? 2. lmkd(Low Memory Killer Daemon)是如何工作的? 3. 除了oom_adj,还有哪些因素影响进程的内存回收优先级? 4. 如何定制特定进程的oom_adj值?</think>### Android OOM_adj 更新机制详解 在 Android 系统中,`updateOomLevels()` 和 `applyOomAdjLocked()` 是内存管理的关键方法,共同负责动态调整进程的 OOM_adj 值(内存优先级)。以下是它们的协同工作机制: #### 1. **updateOomLevels() - 系统级阈值更新** - **功能**:配置系统全局的内存回收策略 - **触发时机**: - 系统配置变更(如屏幕旋转、语言切换) - 设备启动时初始化 - 手动发送配置变更广播 (`adb shell am broadcast -a android.intent.action.CONFIGURATION_CHANGED`) - **核心操作**: 1. 向内核写入内存阈值参数: ```bash echo "18432,23040,27648" > /sys/module/lowmemorykiller/parameters/minfree ``` 表示当空闲内存低于 18432 页(约 72MB)时触发回收[^4] 2. 写入对应的 OOM_adj 阈值: ```bash echo "0,100,200" > /sys/module/lowmemorykiller/parameters/adj ``` 表示当触发第一级回收时,杀死 adj≥0 的进程[^4] - **数学关系**: 设内存阈值数组为 $M = [m_1, m_2, ..., m_n]$, 对应 adj 阈值数组为 $A = [a_1, a_2, ..., a_n]$, 当空闲内存 $f < m_i$ 时,杀死所有满足 $\text{oom\_adj} \geq a_i$ 的进程 #### 2. **applyOomAdjLocked() - 进程级优先级调整** - **功能**:为单个进程计算并应用 OOM_adj 值 - **触发时机**: - Activity/Service 状态变化(启动/停止/绑定等) - 广播接收器注册/注销 - 内容提供者连接/断开 - **工作流程**: ```mermaid graph TD A[组件状态变化] --> B(updateOomAdjLocked) B --> C{进程是否存活?} C -- 是 --> D[computeOomAdjLocked] C -- 否 --> E[返回false] D --> F[计算RawAdj值] F --> G[applyOomAdjLocked] G --> H[写入/proc/pid/oom_score_adj] H --> I[通知lmkd] I --> J{需要杀进程?} J -- 是 --> K[返回false] J -- 否 --> L[返回true] ``` - **关键步骤**: 1. `computeOomAdjLocked()`:基于进程状态计算原始 adj 值 - 前台应用:adj = 0 - 可见应用:adj = 100 - 后台服务:adj = 200-400 - 缓存应用:adj ≥ 900 2. 转换公式: $$ \text{oom\_score\_adj} = \begin{cases} 1000 & \text{if } \text{oom\_adj} = 15 \\ \frac{\text{oom\_adj} \times 1000}{17} & \text{otherwise} \end{cases} $$ 3. 写入内核接口:`/proc/<pid>/oom_score_adj` 4. 通过 socket 通知 lmkd 更新策略[^1][^2] #### 3. **异常处理场景** 1. **进程优先级错误**: - 检查点:`adb shell cat /proc/<pid>/oom_score_adj` - 常见原因:组件状态未正确更新(如 Service 未解绑) - 修复:确保 `onDestroy()` 中释放所有资源 2. **频繁进程回收**: - 检查:`dumpsys activity oom` - 阈值配置问题:`minfree` 值设置过高 ```bash adb shell cat /sys/module/lowmemorykiller/parameters/minfree ``` 3. **lmkd 通信失败**: - 日志标记:`lmkd` 相关的 `E/` 级别日志 - 解决方案:重启 lmkd 服务 ```bash adb shell stop lmkd && adb shell start lmkd ``` #### 4. **调试工具** ```bash # 查看所有进程的当前 adj 值 adb shell dumpsys activity oom # 监控实时 adj 变化 adb shell watch -n 1 "cat /proc/*/oom_score_adj | grep -v 0" # 检查 lmkd 配置 adb shell getprop | grep lmkd ``` ### 典型问题解决流程 1. **定位异常进程**:`dumpsys meminfo` 2. **检查 adj 值**:`cat /proc/<pid>/oom_score_adj` 3. **分析组件状态**:`dumpsys activity processes <package>` 4. **验证阈值配置**:检查 `/sys/module/lowmemorykiller/parameters/adj` 5. **检查 lmkd 日志**:`logcat -b events | grep lmkd` > **关键提示**:当组件状态复杂时(如绑定服务+前台通知),`computeOomAdjLocked()` 会取**最高优先级**作为最终 adj 值[^2]。确保在组件生命周期回调中正确更新状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值