诺志
软硬件开发技术笔记
如何控制服务或进程,当出现内存泄漏或超过设置的阈值后,可以杀掉进程
2025-02-26

先说结论:

目前已知的3种方法都有一定的局限性,不能很完美实现当进程内存泄漏时杀掉进程
1. systemd的服务配置中增加MemoryMax=XXX的配置项,该配置项仅能限住物理内存,如何服务器未配置swap,内存达到时则会触发oom杀掉进程。如果服务器配置swap,将继续消耗swap直到swap也消耗完,systemd还有一个MemorySwapMax=XXX配置项可以限制swap,但是除非很新的操作系统使用了cgroup2该配置项才能生效。
2. systemd配置的MemoryMax=XXX参数,会同时作用到被主进程拉起的其他进程,如主进程拉起mysql进程,虽然systemd的MemoryMax参数是作用在主进程上,但是mysql会继承该限制。原因是MemoryMax参数是通过cgroup生效的,systemd启动的主进程和子进程都将在该cgroup组中。
3. ulimit和prlimit设置进程最大内存地址,这两种方式是作用在系统函数行为上,比如malloc分配内存时被限制了内存上限,内存分配不出来的时候panic,但是如果上层调用捕获了这个异常,程序就不会退出了,并不会处罚oom杀掉进程。

实验过程:

方式一.通过在systemd服务中配置MemoryMax:

当前机器cgroup为v1,若是v2将显示为cgroup2fs

[root@test ~]# stat -fc %T /sys/fs/cgroup
tmpfs

测试脚本
该脚本使用python构造一个很大的内存,触发方式是在/root下创建start文件,touch /root/start,用其他语言可以类似

# cat test.py
import os
while True:
    if os.path.exists("/root/start"):
        b = []
        for i in range(1000000000):
            b.append(i)

创建服务脚本

[root@test ~]# cat /usr/lib/systemd/system/mem-test.service 
[Unit]
Description=mem-test

[Service]
MemoryMax=512M
MemorySwapMax=0
ExecStart=/usr/bin/python /root/test.py

[Install]
WantedBy=multi-user.target

启动服务

[root@test ~]# systemctl daemon-reload
[root@test ~]# systemctl restart mem-test
[root@test ~]# systemctl status mem-test
● mem-test.service - mem-test
     Loaded: loaded (/usr/lib/systemd/system/mem-test.service; disabled; vendor preset: disabled)
     Active: active (running) since Wed 2025-02-26 09:10:14 CST; 3s ago
   Main PID: 3485 (python)
      Tasks: 1 (limit: 47360)
     Memory: 3.9M (max: 512.0M swap max: 0B available: 508.0M)
        CPU: 3.600s
     CGroup: /system.slice/mem-test.service
             └─ 3485 /usr/bin/python /root/test.py

Feb 26 09:10:14 test systemd[1]: Started mem-test.

结论
top中看到的RSS将达到MemoryMax上线后不再增长,若服务器未配置swap,则服务会立刻被oom-kill,若服务器配置swap,swap将增加直到消耗完后被oom-kill
注意:由于swap本质用的是存储资源,因此当swap快速增长时,实际是存储迅速有大量IO,会导致服务器有卡顿。

补充测试
当将服务器改为cgoupv2后,可以支持MemorySwapMax配置项,即使服务器配置了swap,当达到MemorySwapMax后会立刻oom-kill
若以下stat未显示cgroup2fs,说明1.grub配置失效,2.操作系统内核太低不支持

[root@test ~]# cat /proc/cmdline
BOOT_IMAGE=... crashkernel=512M systemd.unified_cgroup_hierarchy=1

[root@test ~]# stat -fc %T /sys/fs/cgroup
cgroup2fs

[root@test ~]# find /sys/fs/cgroup -name "mem-test*"
/sys/fs/cgroup/system.slice/mem-test.service

[root@test ~]# cat /sys/fs/cgroup/system.slice/mem-test.service/memory.swap.max
0
[root@test ~]# cat /sys/fs/cgroup/system.slice/mem-test.service/memory.max
536870912

方式二.通过在ulimit/prlimit限制进程

ulimit的单位为KB,prlimit单位为B
待进程内存超过限制后,将异常退出,退出原因是程序执行过程未捕获异常

[root@test ~]# ulimit  -v 524288 && python test.py
Traceback (most recent call last):
  File "/root/test.py", line 5, in <module>
    for i in range(1000000000):
MemoryError

[root@test ~]# prlimit --rss=536870912:536870912 --as=536870912:536870912 python test.py
Traceback (most recent call last):
  File "/root/test.py", line 5, in <module>
    for i in range(1000000000):
MemoryError

修改代码增加异常捕获

[root@test ~]# cat test.py
import os
while True:
    try:
        if os.path.exists("/root/start"):
                b = []
                for i in range(1000000000):
                    b.append(i)
    except:
        pass

再次执行,进程将不能退出

[root@test ~]# ulimit  -v 524288 && python test.py
err
err
err
err
err
...

[root@test ~]# prlimit --rss=536870912:536870912 --as=536870912:536870912 python test.py 
err
err
err
err
err
...

分类
2篇
c
1篇
8篇
18篇
8篇
2篇
搜索