高负载多线程Java项目OOM异常技术求助
高负载多线程Java+MySQL项目OOM异常排查与解决思路
我来帮你分析这个棘手的OOM问题,先把你提供的关键信息整理清楚,再结合日志细节给出针对性的排查方向和解决方案:
问题背景
你负责的高负载多线程Java+MySQL存储项目突发OOM Exception,奇怪的是:
- 应用崩溃时监控未检测到内存突增,系统剩余内存充足,CPU、I/O状态正常
- jstack未发现线程死锁,MySQL无慢查询或错误日志
- hs_err文件显示线程处于
_thread_blocked状态,但未明确触发原因
关键信息汇总
hs_err文件核心片段
Java运行时环境无法继续执行,本地内存分配(mmap)无法映射1633681408字节以提交预留内存。
可能原因:
- 系统物理内存或交换空间耗尽
- 32位模式下达到进程大小限制
解决方案建议:- 降低系统内存负载
- 增加物理内存或交换空间
- 检查交换空间是否已满
- 在64位操作系统上使用64位Java
- 减小Java堆大小(-Xmx/-Xms)
- 减少Java线程数量
- 减小Java线程栈大小(-Xss)
- 通过-XX:ReservedCodeCacheSize=设置更大的代码缓存
此输出文件可能已截断或不完整。
内存不足错误(os_linux.cpp:2627),pid=11980,tid=0x00007f27148c8700
JRE版本:Java(TM) SE Runtime Environment (8.0_92-b14) (build 1.8.0_92-b14)
Java虚拟机:Java HotSpot(TM) 64-Bit Server VM (25.92-b14 mixed mode linux-amd64 compressed oops)
核心转储写入失败,核心转储已禁用。如需启用核心转储,请在启动Java前执行ulimit -c unlimited
线程调用栈关键信息
当前线程为VMThread,调用栈显示OOM发生在GC扩容新生代的过程中:
V [libjvm.so+0xabd65a] VMError::report_and_die()+0x2ba V [libjvm.so+0x4fb4db] report_vm_out_of_memory(char const*, int, unsigned long, VMErrorType, char const*)+0x8b V [libjvm.so+0x91d713] os::Linux::commit_memory_impl(char*, unsigned long, bool)+0x103 V [libjvm.so+0x91dc69] os::pd_commit_memory(char*, unsigned long, unsigned long, bool)+0x29 V [libjvm.so+0x917f6a] os::commit_memory(char*, unsigned long, unsigned long, bool)+0x2a V [libjvm.so+0x98c343] PSVirtualSpace::expand_by(unsigned long)+0x53 V [libjvm.so+0x98d748] PSYoungGen::resize_generation(unsigned long, unsigned long)+0xf8 V [libjvm.so+0x98c8a2] PSYoungGen::resize(unsigned long, unsigned long)+0x22 V [libjvm.so+0x989b7b] PSScavenge::invoke_no_policy()+0xf3b V [libjvm.so+0x98a301] PSScavenge::invoke()+0x41 V [libjvm.so+0x941410] ParallelScavengeHeap::failed_mem_allocate(unsigned long)+0x70 V [libjvm.so+0xabf077] VM_ParallelGCFailedAllocation::doit()+0x97 V [libjvm.so+0xac6aa5] VM_Operation::evaluate()+0x55 V [libjvm.so+0xac4e7a] VMThread::evaluate_operation(VM_Operation*)+0xba V [libjvm.so+0xac51fe] VMThread::loop()+0x1ce V [libjvm.so+0xac5670] VMThread::run()+0x70 V [libjvm.so+0x91fad8] java_start(Thread*)+0x108
VM操作类型为ParallelGCFailedAllocation,说明是Parallel Scavenge GC在尝试分配内存时失败触发的OOM。
阻塞线程列表
多个线程池线程处于_thread_blocked状态(这是GC安全点的正常现象,所有非VM线程会暂停等待GC完成,不是死锁):
0x00007f25a8052800 JavaThread "pool-27-thread-32" [_thread_blocked, id=26278, 栈区间(0x00007f2147523000,0x00007f2147624000)] 0x00007f24dc07f000 JavaThread "pool-33-thread-55" [_thread_blocked, id=26277, 栈区间(0x00007f2147624000,0x00007f2147725000)] 0x00007f25bc06b800 JavaThread "pool-28-thread-48" [_thread_blocked, id=26272, 栈区间(0x00007f2147725000,0x00007f2147826000)] 0x00007f2568060000 JavaThread "pool-24-thread-39" [_thread_blocked, id=26267, 栈区间(0x00007f2147826000,0x00007f2147927000)] 0x00007f25bc06a000 JavaThread "pool-28-thread-47" [_thread_blocked, id=26262, 栈区间(0x00007f2147927000,0x00007f2147a28000)]
JVM启动参数
jvm_args : -Xmx16G -XX:ReservedCodeCacheSize=256M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/DDT/CPA -XX:-OmitStackTraceInFastThrow
系统内存状态(free -m)
total used free shared buffers cached Mem: 64298 37022 27275 5 593 9466 -/+ buffers/cache: 26962 37336 Swap: 15847 957 14889
系统资源限制(ulimit -a)
core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 256455 max locked memory (kbytes, -l) 64 max memory size (kbytes, -m) unlimited open files (-n) 64000 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 256455 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited
问题根源分析
从hs_err的调用栈和系统信息来看,这不是传统的堆内存耗尽OOM,而是JVM在尝试扩容新生代时,无法通过mmap获取连续的大块物理内存,核心原因可能是:
- 内存碎片化:Linux系统虽然有足够的剩余内存,但这些内存分散成了很多小块,没有足够大的连续内存块满足JVM的扩容请求(mmap需要连续的物理内存页)。
- JVM内存参数配置缺失:你只设置了堆最大值和代码缓存,但没有固定新生代大小,导致Parallel Scavenge GC在高负载下频繁尝试扩容新生代,加剧了内存碎片化的影响。
- 线程栈内存占用过高:ulimit显示线程栈大小为8M(8192k),而项目有大量线程池线程,累计栈内存占用可能超过预期,进一步挤压了系统的连续内存空间。
解决方案建议
紧急临时修复
- 启用核心转储:执行
ulimit -c unlimited后重启应用,下次崩溃时生成core文件,后续可以用jmap -dump:format=b,file=heap.hprof <pid>和jstack <pid>分析内存细节。 - 固定新生代大小:在JVM参数中添加
-Xmn4G(或者根据业务调整,比如新生代占堆的1/4),避免JVM动态扩容新生代时触发内存分配失败。
长期优化方案
- 缓解内存碎片化:
- 升级Linux内核到较新版本(4.14+版本对内存碎片化的管理有显著提升)。
- 定期执行
echo 1 > /proc/sys/vm/compact_memory(需要root权限)手动触发内存压缩,整理碎片化内存。
- 优化JVM参数:
- 调整线程栈大小:如果业务线程不需要8M的栈空间,将
-Xss设置为1M或2M,减少单个线程的内存占用,比如添加-Xss1M。 - 限制直接内存:如果项目大量使用NIO的DirectByteBuffer,添加
-XX:MaxDirectMemorySize=2G,防止直接内存无限制占用系统内存。 - 调整GC策略:可以尝试使用G1GC替代Parallel Scavenge,G1对内存碎片化的处理更好,适合大堆场景,参数为
-XX:+UseG1GC。
- 调整线程栈大小:如果业务线程不需要8M的栈空间,将
- 线程池优化:
- 检查各个线程池的核心线程数和最大线程数配置,避免创建过多线程。建议核心线程数设置为
CPU核心数*2,最大线程数根据任务类型(CPU密集/IO密集)调整,比如IO密集型可以设置为CPU核心数*4~8。
- 检查各个线程池的核心线程数和最大线程数配置,避免创建过多线程。建议核心线程数设置为
- 完善监控体系:
- 增加对JVM非堆内存、直接内存、新生代/老年代内存变化的监控,用Prometheus+Grafana或JMX工具跟踪GC时的内存分配情况。
- 监控系统内存碎片化状态,查看
/proc/buddyinfo文件,了解不同大小内存块的剩余数量,提前发现内存碎片化问题。
内容的提问来源于stack exchange,提问作者MisterLotus
相关产品推荐
相关产品推荐

