OOM(Out Of Memory) Killer作为linux系统中守护进程,主要在系统内存严重不足时开始工作。出现这种情况是因为服务器上的进程正在消耗大量内存,而系统需要更多的内存分配给其他进程。
当一个进程启动时会向内核请求一块内存,申请的内存通常是很大一块,进程也不需要立即或永远不需要使用如此大的内存(这与平时游戏启动类似,游戏可能占据20g,启动进程可能申请22g,但系统内存可能就8g,它不会也不可能分配22g,但这并不会影响游戏的启动)。 这是因为内核意识到进程会过度申请内存,内核也会过度为进程分配内存,但实际分配时并不会立马满足进程所申请的大小。
一般情况下这并不会有什么问题。但如果有足够多的进程开始使用它们申请的所有内存块,那么系统将没有足够的物理内存分配给这些进程。这种情况下Linux 内核采用的解决方案是调用 OOM Killer来检查所有正在运行的进程并杀死其中一个或多个以释放系统内存来保证系统正常运行。
当系统内存分配失败时就会调用out_of_memory()函数,该函数中调用了select_bad_process()函数,select_bad_process()通过badness()函数获取目标进程对应的分数,而最终分数最高的进程会被OOM Killer清理,badness()函数内部有一套规则来选择目标进程:
1、内核为自己保留运行所需最小内存并尝试回收大量的存;
2、优先剔除内存占用高的进程,尝试杀死最少数量的进程;
3、一些算法可以调整进程被终止的优先级;
以上所有检查项执行完毕,OOM Killer将为每个进程设置oom_score,而OOM Killer最终终止进程所使用的值为oom_score乘上对应进程内存使用大小,与特权用户关联的进程一般具有较低分值,其被OOM Killer终止的机会较小。
#获取目标进程的pid
$ ps -ef | grep elasticsearch
elastic+ 13193 1 3 3月16 ? 05:08:05 /bin/java -Xms8g -Xmx8g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+AlwaysPreTouch -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -XX:-OmitStackTraceInFastThrow -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Djava.io.tmpdir=/tmp/elasticsearch.37r3k1y7 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/lib/elasticsearch -XX:ErrorFile=/var/log/elasticsearch/hs_err_pid%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -Xloggc:/var/log/elasticsearch/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=32 -XX:GCLogFileSize=64m -Des.path.home=/usr/share/elasticsearch -Des.path.conf=/etc/elasticsearch -Des.distribution.flavor=default -Des.distribution.type=rpm -cp /usr/share/elasticsearch/lib/* org.elasticsearch.bootstrap.Elasticsearch -p /var/run/elasticsearch/elasticsearch.pid --quiet
elastic+ 13280 13193 0 3月16 ? 00:00:00 /usr/share/elasticsearch/modules/x-pack-ml/platform/linux-x86_64/bin/controller
#查看目标进程的oom_score
$ cat /proc/13193/oom_score
若用户真不想自己的进程被OOM Killer终止,内核还提供了另一个参数oom_score_adj,用户可以为对应进程添加一个很大的负值,以降低OOM Killer在计算分值时的分数;
#调整目标进程的初始值
$ echo -100 > /proc/13193/oom_score_adj
要想在该进程启动时自动初始化数值,可通过OOMScoreAdjust配置项设置:
$ systemctl status elasticsearch
● elasticsearch.service - Elasticsearch
Loaded: loaded (/usr/lib/systemd/system/elasticsearch.service; enabled; vendor preset: disabled)
Active: active (running) since 三 2022-03-16 19:22:08 CST; 5 days ago
Docs: http://www.elastic.co
Main PID: 13193 (java)
CGroup: /system.slice/elasticsearch.service
├─13193 /bin/java -Xms8g -Xmx8g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+...
└─13280 /usr/share/elasticsearch/modules/x-pack-ml/platform/linux-x86_64/bin/controller
$ vim /usr/lib/systemd/system/elasticsearch.service
[Service]
Restart=on-failure
RestartSec=1
RuntimeDirectory=elasticsearch
PrivateTmp=true
Environment=ES_HOME=/usr/share/elasticsearch
Environment=ES_PATH_CONF=/etc/elasticsearch
Environment=PID_DIR=/var/run/elasticsearch
EnvironmentFile=-/etc/sysconfig/elasticsearch
#配置elasticsearch进程oom_score初始值
OOMScoreAdjust=-1000
当OOM Killer选择一个或多个进程时将会调用oom_kill_task()函数,该函数负责向进程发送kill信号。在内存不足时oom_kill()函数将会调用此函数,它将向进程发送sigkill信号并生成内核日志消息:
kernel: Call Trace:
kernel: [<ffffffffaf779262>] dump_stack+0x19/0x1b
kernel: [<ffffffffaf773c04>] dump_header+0x90/0x229
kernel: [<ffffffffaf1047e2>] ? ktime_get_ts64+0x52/0xf0
kernel: [<ffffffffaf1bfd74>] oom_kill_process+0x254/0x3e0
kernel: [<ffffffffaf1314d1>] ? cpuset_mems_allowed_intersects+0x21/0x30
kernel: [<ffffffffaf1bf81d>] ? oom_unkillable_task+0xcd/0x120
kernel: [<ffffffffaf1bf8c6>] ? find_lock_task_mm+0x56/0xc0
kernel: [<ffffffffaf1c05c6>] out_of_memory+0x4b6/0x4f0
kernel: [<ffffffffaf77471c>] __alloc_pages_slowpath+0x5d6/0x724
kernel: [<ffffffffaf1c6b84>] __alloc_pages_nodemask+0x404/0x420
kernel: [<ffffffffaf214c68>] alloc_pages_current+0x98/0x110
kernel: [<ffffffffaf1bbc77>] __page_cache_alloc+0x97/0xb0
kernel: [<ffffffffaf1be838>] filemap_fault+0x298/0x490
kernel: [<ffffffffc048e55e>] __xfs_filemap_fault+0x7e/0x1d0 [xfs]
kernel: [<ffffffffc048e75c>] xfs_filemap_fault+0x2c/0x30 [xfs]
kernel: [<ffffffffaf1ea29a>] __do_fault.isra.61+0x8a/0x100
kernel: [<ffffffffaf1ea84c>] do_read_fault.isra.63+0x4c/0x1b0
kernel: [<ffffffffaf1ef2fa>] handle_pte_fault+0x22a/0xe20
kernel: [<ffffffffaf214c68>] ? alloc_pages_current+0x98/0x110
kernel: [<ffffffffaf1f200d>] handle_mm_fault+0x39d/0x9b0
kernel: [<ffffffffaf786633>] __do_page_fault+0x213/0x500
kernel: [<ffffffffaf786955>] do_page_fault+0x35/0x90
kernel: [<ffffffffaf782768>] page_fault+0x28/0x30
kernel: Out of memory: Kill process 13193 (java) score 620 or sacrifice child
kernel: Killed process 13193 (java), UID 499, total-vm:368666832kB, anon-rss:15038044kB, file-rss:0kB, shmem-rss:0kB
Linux提供了一种启用和禁用 OOM Killer 的方法(不建议),可以通过编辑panic_on_oom 变量来启用或禁用OOM Killer,用户可随时查看/proc中的值。
$ cat /proc/sys/vm/panic_on_oom
0
用户将该值设置为 0 时意味着发生内存不足错误时引起内核的恐慌机制,反之则会触发:
$ echo 0 > /proc/sys/vm/panic_on_oom
$ echo 1 > /proc/sys/vm/panic_on_oom
除了启用和禁用之外,OOM Killer关于内存还有其他配置。正如前面所说,Linux可以通过分配内存来过度使用内存,这可以通过Linux内核参数vm.overcommit_memory来控制:
0: 启发式策略,内核将决定是否过度使用,这是大多数Linux版本的默认值。
1: 永远允许overcommit,这是一个有风险的设置,因为内核总是将内存过度分配给进程。 这可能导致内核内存不足,因为进程很有可能最终会使用向内核申请的内存。
2: 永远禁止overcommit,这意味着内核不应过度使用大于 overcommit_ratio的内存,overcommit_ratio是另一个内核参数,用户可以指定内核内存可过度使用百分比。如果没有多余的内存空间用于overcommit则内存分配失败,overcommit申请被拒绝。
可以影响OOM Killer行为的第二件事是swappiness配置,该配置用于将页面从物理内存换出到交换空间、从交换空间换入到物理内存空间及从页面缓存中删除页面等,可通过一下命令查看及配置:
# 不同版本或系统该值会存在差异
$ cat /proc/sys/vm/swappiness
30
Swappiness可以设置0到100之间的值,较低的值将尽可能避免使内核交换,而较高的值意味着内核将尝试更积极地使用交换空间。所以该值越大OOM Killer终止进程的机会越小,但它会因为I/O而影响进程处理效率,部署Elasticsearch一般建议关闭交换空间,即对应值为0。
#oom相关配置项,省略部分
$ ls -lrt /proc/sys/vm/
oom_dump_tasks
oom_kill_allocating_task
overcommit_kbytes
overcommit_memory
overcommit_ratio
panic_on_oom
swappiness
上面列出的标准意味着OOM Killer在选择终止的过程时,将优先选择那些内存占用高、有大量子进程且不为系统进程的进程。
我们不必被OOM Killer这个名字所迷惑,该进程能够保证系统在内存分配严重不足时保证系统仍正常运行,它会终止造成内存不足这种情况的负最大责任的进程。若用户希望用户进程免遭OOM Killer终止,那建议将vm.overcommit_memory值设置为2,虽然该配置无法保证百分之百避免被终止,但会降低被终止的概率。