一个jvm线程占用多少操作系统内存
找到关键点
在看到12452个等待在CachedBnsClient.run的业务的一瞬间笔者就意识到,肯定是这边的线程导致对外内存泄露了。下面就是根据线程大小计算其泄露内存量是不是确实能够引起OOM了。
发现内存计算对不上
由于我们这边设置的Xss是512K,即一个线程栈大小是512K,而由于线程共享其它MM单元(线程本地内存是是现在线程栈上的),所以实际线程堆外内存占用数量也是512K。进行如下计算:
12563 * 512K = 6331M = 6.3G
整个环境一共4G,加上JVM堆内存1.8G(1792M),已经明显的超过了4G。
(6.3G + 1.8G)=8.1G > 4G
如果按照此计算,应用应用早就被OOM了。
怎么回事呢?
为了解决这个问题,笔者又思考了好久。如下所示:
Java线程底层实现
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,那么用户态的栈可用内存就是:
512K-8K=504K
如下图所示:
Linux实际物理内存映射
事实上linux对物理内存的使用非常的抠门,一开始只是分配了虚拟内存的线性区,并没有分配实际的物理内存,只有推到最后使用的时候才分配具体的物理内存,即所谓的请求调页。如下图所示:
查看smaps进程内存使用信息
使用如下命令,查看
cat /proc/[pid]/smaps > smaps.txt
实际物理内存使用信息,如下所示:
7fa69a6d1000-7fa69a74f000 rwxp 00000000 00:00 0 Size: 504 kB
Rss: 92 kB
Pss: 92 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 92 kB
Referenced: 92 kB
Anonymous: 92 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
7fa69a7d3000-7fa69a851000 rwxp 00000000 00:00 0
Size: 504 kB
Rss: 152 kB
Pss: 152 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 152 kB
Referenced: 152 kB
Anonymous: 152 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
搜索下504KB,正好是12563个,对了12563个线程,其中Rss表示实际物理内存(含共享库)92KB,Pss表示实际物理内存(按比例共享库)92KB(由于没有共享库,所以Rss==Pss),以第一个7fa69a6d1000-7fa69a74f000线性区来看,其映射了92KB的空间,第二个映射了152KB的空间。如下图所示:
挑出符合条件(即size是504K)的几十组看了下,基本都在92K-152K之间,再加上内核栈8K
(92+152)/2+8K=130K,由于是估算,取整为128K,即反映此应用平均线程栈大小。
注意,实际内存有波动的原因是由于环境不同,从而走了不同的分支,导致栈上的增长不同。
重新进行内存计算
JVM一开始申请了
-Xmx1792m -Xms1792m
即1.8G的堆内内存,这里是即时分配,一开始就用物理页框填充。
12563个线程,每个线程栈平均大小128K,即:
128K * 12563=1570M=1.5G的对外内存
取个整数128K,就能反映出平均水平。再拿这个128K * 12563 =1570M = 1.5G,加上JVM的1.8G,就已经达到了3.3G,再加上kernel和日志传输进程等使用的内存数量,确实已经接近了4G,这样内存就对应上了!(注:用于定量内存计算的环境是一台内存用量将近4G,但还没OOM的机器)
为什么在物理机上没有应用Down机
笔者登录了原来物理机,应用还在跑,发现其同样有堆外内存泄露的现象,其物理内存使用已经达到了5个多G!幸好物理机内存很大,而且此应用发布还比较频繁,所以没有被OOM。
Dump了物理机上应用的线程,
一共有28737个线程,其中28626个线程等待在CachedBnsClient上。
同样用smaps查看进程实际内存信息,其平均大小依旧为
128K,因为是同一应用的原因
继续进行物理内存计算
1.8+(28737 * 128k)/1024K =(3.6+1.8)=5.4G
进一步验证了我们的推理。
这么多线程应用为什么没有卡顿
因为基本所有的线程都睡眠在
Thread.sleep(60 * 1000);//一次睡眠60s
上。所以仅仅占用了内存,实际占用的CPU时间很少。
总结
查找Bug的时候,现场信息越多越好,同时定位Bug必须要有实质性的证据。例如内存泄露就要用你推测出的模型进行定量分析。在定量和实际对不上的时候,深挖下去,你会发现不一样的风景!
以上是 一个jvm线程占用多少操作系统内存 的全部内容, 来源链接: utcz.com/z/511401.html