问题处理记录与深入:系统线程耗尽,Java无法调用native方法新建线程

1. 问题处理记录

1.1 问题描述

  • 公司使用Presto作为OLAP查询引擎,Presto的coordinator节点在运行过程中报错

    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:802)
    
  • 当时,第一反应:有了异常堆栈,大概知道是哪里发生了线程滥用,这是测试环境,先立马重启服务后观察下线程增长情况

  • 重启服务后,不停执行如下命令,发现对应Java进程的线程使用量确实再不断增加

cat /proc/${pid}/status | grep "Threads"

1.2 问题修复与观察

  • 问题原因: 通过异常堆栈,发现是某个方法不停新建HttpClient、却又没有进行close导致

  • 问题修复后,通过如下shell脚本观察线程使用情况:每隔10min打印一次线程数

    #!/bin/bash
    
    while true
    do
        echo -ne $(date +%Y-%m-%dT%H:%M:%S)" "
        cat /proc/${pid}/status | grep "Threads" # 使用时,${pid}替换成对应的Java服务进程号
        sleep 10m
    done
    
  • 定时执行shell脚本,发现线程数得到了控制,观察4小时,最大线程数699 —— 问题成功修复

    nohup ./watch_threads.sh > threads_stat.txt 2>&1 &
    

    在这里插入图片描述

2. 疑问:一个Java进程究竟能创建多少线程?

  • 后来,使用ulimit -a查看系统资源配置时,发现系统允许root用户可以创建的最大进程或线程数为unlimited

  • 因此,笔者有了疑惑:

    • 既然是unlimited,为何会出现unable to create native thread的情况?
    • 还有,从系统资源有限的角度来说,这里的unlimited不应该是无上限,而是相对具体的、类似65636这样的固定值来说,只要系统资源未耗尽就可以创建线程
  • 可惜的是,笔者当时并未查看报错时的线程数,无法判断这个unlimited的上限究竟是多少

2.1 优秀文章的启发:How Many Threads Can a Java VM Support?

  • Baeldung的一篇文章:How Many Threads Can a Java VM Support?,让笔者大概有了一个认识
  • JVM,即一个Java进程,能创建的最大线程数,至少有两个方面的决定因素:
    • JVM本身 + 系统内存
    • 操作系统限制

2.2 决定因素1:JVM本身 + 系统内存

  • JVM中,堆栈的max size由-Xss-XX:ThreadStackSize进行设置
    -Xss1024k # 如果省略unit,默认unit:Byte
    -XX:ThreadStackSize=1024 # unit:KB
    
  • JVM会为每个线程创建自己的堆栈,创建堆栈使用的内存为系统内存,并非JVM Heap memory
  • 因此,有如下计算公式
    • MaxProcessMemory:是指进程的最大寻址空间
    • ReservedOsMemory:保留的操作系统内存,如Native Heap、JNI之类,一般100MB
    Number of threads = (MaxProcessMemory - JVMHeapMemory - ReservedOsMemory) / (ThreadStackSize) 
    

2.3 决定因素2:操作系统限制(以Linux系统为例)

  • 首先,Linux系统级对max thread的限制,threads-max

    cat /proc/sys/kernel/threads-max # 服务器上的threads-max = 6179160
    
  • 其次, Linux系统在内核级别将线程视为进程,因此限制最大进程数的pid_max也会影响JVM可以创建的线程数量

  • 这也是为什么ulimit -a展示的max user processes也被视为线程数限制的原因

    cat /proc/sys/kernel/pid_max # 相对threads-max,值更小,4194303
    
  • 还有,vm.max_map_count 参数,它指定进程可以拥有的虚拟内存区域 (VMAs,Virtual Memory Areas) 的最大数量

    cat /proc/sys/vm/max_map_count # 三者中,max_map_count的值最小,2097152
    
  • 最后,Linux支持针用户级别的资源限制,可以通过ulimit -a查看当前用户的资源限制。其中,max user processes的值也限制了JVM能创建的最大线程数

2.4 总结

  • JVM能创建的最大线程数是由各种因素综合决定的,且一定是这些因素中的min value决定
  • 决定因素1触发unable to create native thread,其根因是内存不足
  • 决定因素2中触发unable to create native thread,其本质是系统资源限制

3. 后记

  • 27
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值