背景
加班进行多Java进程环境的测试准备工作发现一个比较折腾的问题. 我在多核心服务器上面发现java进程的virt内存值异常的高VIRT 74.0g RES 20.2g因为节前一直跟同事沟通确认一些java内存使用的情况所以这里我进行了一下总结和验证
图示
问题原因的初步怀疑
自己在两年前参加达梦的培训以及之前看美团的一个堆外内存泄漏的范例时看到过Linux为了适应多核心服务器提高性能会针对每个CPU核心创建多个内存的ARENA的内存区域这样最大的好处 不同的线程申请内存的时候可以就近使用CPU的内存区域并且不要进行加锁解锁能够提高性能当然,如果是进行共享内存访问,那么必须使用非这一块区域的内存.
参数解释- MALLOC_ARENA_MAX
历史背景:glibc 2.11 之前:多线程共享单个 Arena,锁竞争激烈glibc 2.11+:引入多 Arena 机制,每个线程可拥有独立 Arenaglibc 2.34+:锁机制调整,安全性增强但性能可能下降核心作用默认值:8 × CPU 核心数目的:每个 Arena 有独立锁,减少多线程内存分配时的锁竞争代价:每个 Arena 预留约 64MB 虚拟内存(64位系统)优点第一,减少锁竞争。 每个 Arena 分配区都有独立的锁, 多线程环境下各线程可以并行进行内存分配,不会互相阻塞, 显著提升高并发场景下的分配性能。第二,提升分配效率。 线程可以绑定到特定 Arena 进行本地化分配, 减少系统调用和上下文切换开销, 特别适合 Web 服务器、数据库等多线程应用。第三,碎片隔离。 不同 Arena 独立管理各自的内存碎片, 避免单一分配区的碎片问题影响整体性能。第四,配置灵活。 通过环境变量即可调整,无需修改代码或重新编译, 便于在容器、K8s 等环境中快速调优。缺点第一,虚拟内存占用高。 每个 Arena 会预分配约 64MB 虚拟内存(64 位系统), 默认 8 倍 CPU 核心数的 Arena 数量可能导致数百 MB 甚至上 GB 的虚拟内存占用。第二,内存回收差。 线程退出后,其对应的 Arena 内存默认不会归还给操作系统, 长期运行后可能造成内存浪费。第三,容器 OOM 风险。 在 K8s 等容器环境中,虚拟内存占用过高可能触发 OOM Kill, 即使实际物理内存使用并不高。第四,碎片可能增加。 多 Arena 机制虽然隔离了碎片,但整体碎片率可能上升, 导致物理内存利用率降低。第五,glibc 版本差异。 glibc 2.34 之后锁机制有所调整, 部分场景下性能可能不升反降,需要实际测试验证。总结建议内存敏感场景如容器、Java 应用建议将值设为 1 到 2,优先控制内存占用。高并发物理机服务可适当调高至 4 到 8,平衡性能与内存。极端性能要求场景可考虑使用 jemalloc 或 tcmalloc 替代默认分配器。
pmap的验证
pmap -X 28408 |sort -k7h |awk '{sum+=$7} END {print "第七列求和结果:", sum/1024/1024}'第七列求和结果: 33.7364pmap -X 28408 |sort -k7h |awk '{sum+=$6} END {print "第六列求和结果:", sum/1024/1024}'第六列求和结果: 49.6317pmap -X 28408 |sort -k7h |grep jar |awk '{sum+=$6} END {print "jar求和结果:", sum/1024/1024}'jar求和结果: 0.134808 PID USER PR NI VIRT RES SHR COMMAND28408 root 20049.6g 33.7g 153576 javatop是可以这样对应起来的.
增加配置文件进行检查确认
export MALLOC_ARENA_MAX=8pmap -X 1098613 |awk '{print $6}' |grep -E '\b[0-9]{5}\b' |grep ^6 |wc -l 19 cat /proc/cpuinfo |grep processor |wc -l64不增加的进行查看: pmap -X 1098613 |awk '{print $6}' |grep -E '\b[0-9]{5}\b' |grep ^6 |wc -l 690cat /proc/cpuinfo |grep processor |wc -l128发现的确有效果怀疑应该不止一个地方有申请 ARENA 应该有多处进行处置. 但是的确可以减少部分内存的使用情况.