Linux 系统下应避免因内存耗尽导致系统 Hang 住或频繁读写 Swap 的情况!
内存不足(OOM
) 是计算机操作经常不希望出现的状态,在这种状态下,无法分配其他内存供程序或操作系统使用。这样的系统将无法加载任何其他程序,并且由于许多程序可能在执行期间将其他数据加载到内存中,因此这些程序将无法正常运行。这通常是因为已分配了所有可用内存,包括磁盘交换空间。
- 内存溢出
申请的内存超出了程序能提供的内存大小,此时称之为溢出。
- 内存泄露
程序申请使用完的内存没有得到及时释放,导致其他程序不能再次使用该内存,此时这段内存就泄露了。因为申请者不用了,而又不能被其他程序使用。
1. 了解 OOM 机制
Linux 系统下应避免因内存耗尽导致系统 Hang 住或频繁读写 Swap 的情况!
如果运维过服务器的话,肯定或多或少会遇到系统 OOM
的情况,这种情况多出现在系统内存严重不足的时候。在系统 OOM
之前,我们会发现系统巨卡,如果上面部署服务的话,肯定会严重影响使用的。
- 理解
OOM
的作用
在 Linux
系统中,out of memory
指的是由于系统内存压力,系统会选择保护一些系统进程,而将一些其他的进程 kill
掉,来释放内存,缓解系统内存压力。
- 理解
swap
交换的作用
在 Linux
系统中,当物理内存不够用的时候,而又有新的程序需要分配内存,此时 Linux
内核就会选择将其他程序(已运行)暂时不用的数据交换到物理磁盘上(swap out
),等该程序需要用的时候再读进来(swap in
)。而交换是有代价的,而磁盘 IO
代价大家都懂的。
2. 简单处理方式
Linux 系统下应避免因内存耗尽导致系统 Hang 住或频繁读写 Swap 的情况!
这是因为,在系统 OOM
之前会做一些“清理”工作,比如 Linux
会尝试清理内存页缓存会清空所有 buffer
,但是这种危急情况下系统会进入呆滞状态,响应非常缓慢。
- Why are low memory conditions handled so badly?
- Is it possible to make the OOM killer intervent earlier?
这个时候系统就非常卡了,很容易遇到系统 Hang
住的情况,到最后不得不重启机器。而我们日常的清理内存操作,因为系统内存足够,所以在我们手动执行如下命令之后,开始时会比较卡一会儿,而并不会出现死机的情况。
# 清理内存
$ sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
因为这个问题,我们苦苦挣扎,辗转难以入睡。希望操作系统一直保持正常运行,即便提前干掉耗内存大的进程。但是,使用内核提供的 OOM Killer
无法实现这一目标。
如果“临时”使用的话,可以在我们启动服务之前,通过 choom
命令来进行优先级的设置。这样当系统内存不足时,会优先/延后杀掉我们的服务。
[program:app]
directory=/data/app
command=sudo choom -n -500 -- sudo -u app python3 run.py
3. 优雅处理方式
Linux 系统下应避免因内存耗尽导致系统 Hang 住或频繁读写 Swap 的情况!
而在用户空间中,我们可以做任何我们想做的事情,包括“自杀”。所以,我们监控在系统内存不足的情况下,决定杀掉它它它,来释放内存。当然这里我们不用自己去实现上述逻辑来删除系统进程,只要使用 EarlyOOM 这个工具就可以完成我们的心愿。
当系统内存不足时,earlyOOM
会在系统可用内存以及 swap
都必须低于 10%
(默认值-PERCENT
)的话,这时 earlyoom
才会给占用内存最多的进程发 SIGTERM
信号,如果可用内存继续下降到 5%
(默认值-PERCENT/2
)的话,将发送 SIGKILL
信号终止内存最大的进程(即 oom_score
最大的)从而释放内存。
- earlyoom
Early OOM Daemon for Linux
config: /etc/default/earlyoom
- nohang
A sophisticated low memory handler for Linux
# install
$ sudo apt install earlyoom
# handle
$ git clone https://github.com/rfjakob/earlyoom.git
$ cd earlyoom
$ make
$ sudo make install
# setting(recommend)
$ sudo systemctl start earlyoom
$ sudo systemctl enable earlyoom
$ sudo systemctl start earlyoom
其实最好的解决方法,还是从代码层面进行修改和调整。根据以往的经验来说,大部分情况下都是因为代码的逻辑或者使用方式不对,导致某个/多个操作需要占用大量内存且短时间不会释放。与此同时,程序可以限制下内存的使用,尽量避免导致系统 OOM
出现。最后是,通过装像 earlyoom
这类 pre-oom
的工具来保障系统和服务的正常使用。
4. 工具使用方式
Linux 系统下应避免因内存耗尽导致系统 Hang 住或频繁读写 Swap 的情况!
通过上述命令安装完成之后,就可以根据我们系统的实际情况来修改配置文件了。earlyoom
的配置比较简单,基本是零配置。可能唯一需要注意的是,如果内存太大,建议通过 -M
来指定触发的时机点。通过执行 man earlyoom
命令,可以看到完整的帮助信息。
- 增加
-m 10
参数 - 增加
-M 1048576
参数- 将触发
terminate
的时机限定为min(10%, 1G)
- 这样避免在内存充足(还有
xG
内存)的情况下,进程被强制退出
- 将触发
- 增加
-S xxx
参数- 设置
swap
的最小交换分区大小(单位为KiB
)
- 设置
- 增加
-r xxx
参数- 设置内存报告间隔(默认为
1
秒),设置为0
表示完全禁用
- 设置内存报告间隔(默认为
- 增加
--avoid xxx
参数- 指定避免杀掉的进程 - 正则表达式
- 实例说明:
earlyoom --avoid '^(foo|bar)$'
- 增加
--prefer xxx
参数- 设置优先杀掉的进程 - 正则表达式
- 实例说明:
earlyoom --prefer '^(foo|bar)$'
- 增加
-p
参数- 将 Earlyoom 的优先级设置为-20,将 oom_score_adj 设置为-100
- 增加
--dryrun
参数- 设置为干跑模式(不会删除任何进程)
# systemctl
# /etc/default/earlyoom
EARLYOOM_ARGS="-r 3600 -n --avoid '(^|/)(init|systemd|Xorg|sshd)$'"
# systemctl
# /etc/default/earlyoom
EARLYOOM_ARGS="-m 10 -M 1048576 -r 60 \
-r 3600 -n --avoid '(^|/)(init|systemd|Xorg|sshd)$' \
--prefer '(^|/)(java|chromium)$"
- 如果是通过源代码编译安装,则直接启动就可以使用了。
- 输出的信息,包括使用的内存和
swap
状态。
# 直接启动(二进制编译)
$ ./earlyoom
earlyoom v1.4-6-ga4021ae
mem total: 9823 MiB, swap total: 9823 MiB
sending SIGTERM when mem <= 10 % and swap <= 10 %,
SIGKILL when mem <= 5 % and swap <= 5 %
Could not lock memory - continuing anyway: Cannot allocate memory
mem avail: 5091 of 9823 MiB (51 %), swap free: 9823 of 9823 MiB (100 %)
mem avail: 5084 of 9823 MiB (51 %), swap free: 9823 of 9823 MiB (100 %)
mem avail: 5086 of 9823 MiB (51 %), swap free: 9823 of 9823 MiB (100 %)
[...]
- 如果是通过
systemd
服务安装的话,则会自动后台运行。 - 输出的信息,包括使用的内存和
swap
状态。
# 后台运行(通过apt安装)
$ systemctl status earlyoom
systemd[1]: Started Early OOM Daemon.
earlyoom[73837]: earlyoom 1.6.2
earlyoom[73837]: mem total: 1806 MiB, swap total: 2047 MiB
earlyoom[73837]: sending SIGTERM when mem <= 10 % and swap <= 10 %,
earlyoom[73837]: SIGKILL when mem <= 5 % and swap <= 5 %
earlyoom[73837]: mem avail: 1237 of 1806 MiB (68 %), swap free: 2039 of 2047 MiB (99 %)
earlyoom[73837]: mem avail: 1239 of 1806 MiB (68 %), swap free: 2039 of 2047 MiB (99 %)
earlyoom[73837]: mem avail: 1239 of 1806 MiB (68 %), swap free: 2039 of 2047 MiB (99 %)
earlyoom[73837]: mem avail: 1238 of 1806 MiB (68 %), swap free: 2039 of 2047 MiB (99 %)
5. 完整参数选项
Linux 系统下应避免因内存耗尽导致系统 Hang 住或频繁读写 Swap 的情况!
- 命令的完整参数选项,如下所示:
./earlyoom -h
earlyoom v1.6-preview
Usage: ./earlyoom [OPTION]...
-m PERCENT[,KILL_PERCENT] set available memory minimum to PERCENT of total
(default 10 %).
earlyoom sends SIGTERM once below PERCENT, then
SIGKILL once below KILL_PERCENT (default PERCENT/2).
-s PERCENT[,KILL_PERCENT] set free swap minimum to PERCENT of total (default
10 %).
Note: both memory and swap must be below minimum for
earlyoom to act.
-M SIZE[,KILL_SIZE] set available memory minimum to SIZE KiB
-S SIZE[,KILL_SIZE] set free swap minimum to SIZE KiB
-i user-space oom killer should ignore positive
oom_score_adj values
-n enable d-bus notifications
-d enable debugging messages
-v print version information and exit
-r INTERVAL memory report interval in seconds (default 1), set
to 0 to disable completely
-p set niceness of earlyoom to -20 and oom_score_adj to
-100
--prefer REGEX prefer to kill processes matching REGEX
--avoid REGEX avoid killing processes matching REGEX
--dryrun dry run (do not kill any processes)
-h, --help this help text