某天线上突然出了报警。
JVM Thread Count > 3000 触发。
首先观察 集群的监控,发现不是个例,此应用的集群线程数都是2900~3000左右。
1.jstack 文件分析
经过业务和框架代码的问题,最终定位是 存在大量 ZkClient 对象, 每个 ZkClient 建连都会启动3个线程。
ZkClient 类 会启动 一个 名称为 ZkClient-EventThread- 的线程。
接着 调用 zkconnection.connect(watcher); 方法时,会创建 一个 ClientCnxn 的实例 。
org.apache.zookeeper.ClientCnxn#start 会启动,两个线程。localhost-startStop-sendThread 和 localhost-startStop-eventThread 。
问题原因最终定位,并修复。
同时给我产生了一定的困惑。
为什么3000+的线程却没有OOM?
理想中的计算公式
最大可用内存/Xss = 最大线程
Xss 参数配置 java8 及以上 默认为 1024K
容器内存 5.5G,Xmx 堆内存 4G,元空间 256MB
可用 内存 1280M/1M = 1280;最多1280左右个线程,机器就撑不住了。
网上查了一些资料
查看Thread 到底占用了多少内存
需要开启 JVM参数:-XX:NativeMemoryTracking=detail
然后执行 ,就可以看到 jvm 本地内存的分析
jcmd {pid} VM.native_memory
Native Memory Tracking:
Total: reserved=3439080KB, committed=2228892KB
- Java Heap (reserved=1468416KB, committed=1468416KB)
(mmap: reserved=1468416KB, committed=1468416KB)
- Class (reserved=1149482KB, committed=114346KB)
(classes #18958)
(malloc=2602KB #43119)
(mmap: reserved=1146880KB, committed=111744KB)
- Thread (reserved=238418KB, committed=238418KB)
(thread #856)
(stack: reserved=234588KB, committed=234588KB)
(malloc=2827KB #4282)
(arena=1003KB #1708)
- Code (reserved=266377KB, committed=94193KB)
(malloc=16777KB #19327)
(mmap: reserved=249600KB, committed=77416KB)
- GC (reserved=102043KB, committed=102043KB)
(malloc=14783KB #36820)
(mmap: reserved=87260KB, committed=87260KB)
- Compiler (reserved=1974KB, committed=1974KB)
(malloc=1844KB #3350)
(arena=131KB #6)
- Internal (reserved=179912KB, committed=179912KB)
(malloc=179880KB #86296)
(mmap: reserved=32KB, committed=32KB)
- Symbol (reserved=22345KB, committed=22345KB)
(malloc=19652KB #207060)
(arena=2693KB #1)
- Native Memory Tracking (reserved=6934KB, committed=6934KB)
(malloc=536KB #7399)
(tracking overhead=6398KB)
- Arena Chunk (reserved=309KB, committed=309KB)
(malloc=309KB)
- UnkNown (reserved=2868KB, committed=0KB)
(mmap: reserved=2868KB, committed=0KB)
可以看到两种类型的内存:
“Reserved:” 由操作系统承诺的可用内存大小。但尚未分配,JVM 无法访问
”Committed:“ 已被 JVM 分配,可访问
在“Thread”部分可以看到,‘reserved’ 和 ‘committed’ 大小相同,接近于“线程数 ‘ 1MB”。这时因为 JVM 一开始就尽可能地为线程分配内存。
可以发现,确实实际占用内存没那么多。
查看 每个线程栈的大小
JVM的线程在linux上底层是调用NPTL(Native Posix Thread Library)来创建的,一个JVM线程就对应linux的lwp(轻量级进程,也是进程,只不过共享了mm_struct,用来实现线程),一个thread.start就相当于do_fork了一把。 其中,我们在JVM启动时候设置了-Xss=512K(即线程栈大小),这512K中然后有8K是必须使用的,这8K是由进程的内核栈和thread_info公用的,放在两块连续的物理页框上。如下图所示:
众所周知,一个进程(包括lwp)包括内核栈和用户栈,内核栈+thread_info用了8K,那么用户态的栈可用内存就是:
1024k-8k=1016k
Linux实际物理内存映射
事实上linux对物理内存的使用非常的抠门,一开始只是分配了虚拟内存的线性区,并没有分配实际的物理内存,只有推到最后使用的时候才分配具体的物理内存,即所谓的请求调页。如下图所示:
查看smaps进程内存使用信息
执行命令
cat /proc/[pid]/smaps > smaps.txt
可以看到
|
size:用户态的大小 1016k
搜索下1016k,正好是1988个,对了1988个线程,其中RSS表示实际物理内存(含共享库)92KB,Pss表示实际物理内存(按比例共享库)92KB(由于没有共享库,所以RSS==Pss),以第一个7f06b7f02000-7f06b8000000
线性区来看,其映射了92KB的空间,第二个映射了104KB的空间。如下图所示:
线程栈并没有占用1M的实际内存 。并且可能每个线程的分支不一样,导致栈上的增长不同
大部分都是 150K左右。 2000个线程也就 300M左右,所以并没有出现OOM。
参考:
解Bug之路-记一次JVM堆外内存泄露Bug的查找 - 腾讯云开发者社区-腾讯云
Java优化知行合一(1):JVM占用内存区域和优化 - 知乎
Java 线程究竟占用多少内存_mob60475704c528的技术博客_51CTO博客
JVM中的本地内存追踪NMT(Native Memory Tracking)_阿达King的博客-CSDN博客_nativememorytracking
原文地址:https://www.jb51.cc/wenti/3280732.html
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。