cron定时启动Java程序报无法创建本地线程OOM错误排查
问题描述
我有一个Java程序,在UNIX web服务器上通过普通shell命令(java -jar xxx.jar)启动时运行完全正常。
但我尝试通过cron配置定时运行该程序时,收到如下错误:
[0.075s][warning][os,thread] Failed to start thread - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 0k, detached. Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached at java.base/java.lang.Thread.start0(Native Method) at java.base/java.lang.Thread.start(Thread.java:801)
经初步判断,该问题并非Java编码错误,因为通过shell启动时程序运行无任何异常;也并非cron服务本身故障,因为其他Java程序可通过cron正常执行。
我了解到cron运行环境存在系统资源配额限制,因此首先检查了系统的用户资源限制配置:
$ ulimit -a core file size (blocks, -c) unlimited data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 1545091 max locked memory (kbytes, -l) 65536 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) unlimited cpu time (seconds, -t) unlimited max user processes (-u) 62987 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited
随后我调整了cron服务对应的systemd配置,执行命令如下:
$ systemctl stop cron $ sudo systemctl edit cron $ systemctl daemon-reload $ systemctl start cron
在systemctl edit cron打开的配置文件中,我参考ulimit与systemd参数的映射规则,配置了与ulimit一致的资源限制值:
TasksMax=unlimited LimitCORE=unlimited LimitDATA=unlimited LimitFSIZE=unlimited LimitSIGPENDING=1545091 LimitMEMLOCK=65536 LimitRSS=unlimited LimitNOFILE=1024 LimitSTACK=unlimited LimitCPU=unlimited LimitNPROC=62987 LimitAS=unlimited LimitLOCKS=unlimited
但上述调整并未解决问题,cron启动该程序时仍然抛出相同错误。
解决方案
你修改cron服务的systemd全局限制不生效的核心原因是:cron执行任务时默认会通过PAM模块加载独立的资源限制,不会直接继承cron服务本身的systemd limit配置,按以下顺序排查修复即可:
- 检查cron的PAM限制配置
打开/etc/security/limits.conf以及/etc/security/limits.d/目录下的所有配置文件,确认运行Java程序的对应用户,有没有单独配置nproc(最大进程/线程数)、stack(栈大小)、as(虚拟内存)的硬限制。多数发行版默认会给普通用户设置远低于全局ulimit的nproc硬限制,你在交互式shell中看到的ulimit值是登录会话加载的配置,和cron走PAM的加载路径完全不一致。
配置参考(将your_username替换为实际运行程序的用户名):
配置完成后无需重启cron服务,下次任务触发时会自动加载新规则。your_username soft nproc 65535 your_username hard nproc 65535 your_username soft as unlimited your_username hard as unlimited your_username soft stack unlimited your_username hard stack unlimited - 校验cron环境实际生效的资源限制
交互式shell会自动加载/etc/profile、~/.bashrc等环境配置,cron默认环境变量极简,不会加载上述配置,很可能出现资源限制、JVM参数和你手动执行时不一致的问题。你可以先在cron任务中增加打印当前资源限制的逻辑,确认实际生效的配置:
等任务触发后查看* * * * * ulimit -a > /tmp/cron_ulimit.log 2>&1; /usr/bin/java -jar /path/to/xxx.jar > /tmp/app_run.log 2>&1/tmp/cron_ulimit.log,重点核对max user processes、virtual memory、stack size三个值是否和手动执行shell时的ulimit结果一致,如果存在差异,说明资源限制没有正确加载。 - 检查systemd用户切片默认限制
即使正确配置了cron服务的Limit参数,systemd给每个用户分配的user.slice默认存在TasksMax限制,默认值通常为系统总可用PID数的1/4左右,cgroup层面的限制会覆盖ulimit配置,触发pthread_create的EAGAIN错误。
执行sudo systemctl edit user.slice,填入以下配置:
保存后执行[Slice] TasksMax=infinity LimitNPROC=infinitysystemctl daemon-reload即可生效。 - 临时绕过方案
如果以上配置调整后仍不生效,可以直接在cron任务中通过systemd-run启动Java程序,手动指定资源限制,完全绕开cron的默认限制规则:* * * * * /usr/bin/systemd-run --user --scope -p TasksMax=infinity -p LimitNPROC=infinity /usr/bin/java -jar /path/to/xxx.jar
额外排查点:如果服务器开启了SELinux,也可能拦截cron环境下的进程资源申请,可以临时执行
setenforce 0测试,如果问题消失再针对性调整SELinux规则即可,不建议长期关闭SELinux。
内容的提问来源于stack exchange,提问作者Ingo
相关产品推荐
相关产品推荐

