记一次JVM堆外内存泄露Bug的查找

发表于:2018-1-23 11:18

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:无毁的湖光-Al    来源:开源中国


  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
  进一步验证了我们的推理。
  这么多线程应用为什么没有卡顿
  因为基本所有的线程都睡眠在上。所以仅仅占用了内存,实际占用的CPU时间很少。
  Thread.sleep(60 * 1000);//一次睡眠60s
  总结
  查找Bug的时候,现场信息越多越好,同时定位Bug必须要有实质性的证据。例如内存泄露就要用你推测出的模型进行定量分析。在定量和实际对不上的时候,深挖下去,你会发现不一样的风景!

上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号