0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

介绍一下使用NMT协助排查内存问题的案例

openEuler 来源:毕昇编译 作者:窦义望 2022-11-16 11:33 次阅读

从前面几篇文章,我们了解了 NMT 的基础知识以及 NMT 追踪区域分析的相关内容,本篇文章将为大家介绍一下使用 NMT 协助排查内存问题的案例。

6.使用 NMT 协助排查内存问题案例

我们在搞清楚 NMT 追踪的 JVM 各部分的内存分配之后,就可以比较轻松的协助排查定位内存问题或者调整合适的参数

可以在 JVM 运行时使用 jcmd VM.native_memory baseline 创建基线,经过一段时间的运行后再使用 jcmd VM.native_memory summary.diff/detail.diff 命令,就可以很直观地观察出这段时间 JVM 进程使用的内存一共增长了多少,各部分使用的内存分别增长了多少,可以很方便的将问题定位到某一具体的区域。

比如我们看到 MetaSpace 的内存增长异常,可以结合 MAT 等工具查看是否类加载器数量异常、是否类重复加载、reflect 的 inflation 参数设置是否合理;如果 Symbol 内存增长异常,可以查看项目 String.intern 是否使用正常;如果 Thread 使用内存过多,考虑是否可以适当调整线程堆栈大小等等。

案例一:虚高的 VIRT 内存

我们还记得前文(NMT 内存 & OS 内存概念差异性章节)中使用 top 命令查看启动的 JVM 进程,仔细观察会发现一个比较虚高的 VIRT 内存(10.7g),我们使用 NMT 追踪的 Total: reserved 才 2813709KB(2.7g),这多出来的这么多虚拟内存是从何而来呢?

top

PIDUSERPRNIVIRTRESSHRS%CPU%MEMTIME+COMMAND
27420douyiwa+20010.7g69756017596S100.00.30:18.79java

NativeMemoryTracking:

Total:reserved=2813077KB,committed=1496981KB

使用pmap -X 观察内存情况:

27420:java-Xmx1G-Xms1G-XX:+UseG1GC-XX:MaxMetaspaceSize=256M-XX:MaxDirectMemorySize=256M-XX:ReservedCodeCacheSize=256M-XX:NativeMemoryTracking=detail-jarnmtTest.jar
AddressPermOffsetDeviceInodeSizeRssPssReferencedAnonymousLazyFreeShmemPmdMappedShared_HugetlbPrivate_HugetlbSwapSwapPssLockedMapping
c0000000rw-p0000000000:00010490886372366372366372366372360000000
100080000---p0000000000:000104806400000000000
aaaaea835000r-xp00000000fd:0245613083444400000000java
aaaaea854000r--p0000f000fd:0245613083444440000000java
aaaaea855000rw-p00010000fd:0245613083444440000000java
aaab071af000rw-p0000000000:0003041081081081080000000[heap]
fffd60000000rw-p0000000000:00013244440000000
fffd60021000---p0000000000:0006540400000000000
fffd68000000rw-p0000000000:00013288880000000
fffd68021000---p0000000000:0006540400000000000
fffd6c000000rw-p0000000000:00013244440000000
fffd6c021000---p0000000000:0006540400000000000
fffd70000000rw-p0000000000:000132404040400000000
fffd70021000---p0000000000:0006540400000
......

可以发现多了很多 65404 KB 的内存块(大约 120 个),使用 /proc//smaps 观察内存地址:

......
fffd60021000-fffd64000000---p0000000000:000
Size:65404kB
KernelPageSize:4kB
MMUPageSize:4kB
Rss:0kB
Pss:0kB
Shared_Clean:0kB
Shared_Dirty:0kB
Private_Clean:0kB
Private_Dirty:0kB
Referenced:0kB
Anonymous:0kB
LazyFree:0kB
AnonHugePages:0kB
ShmemPmdMapped:0kB
Shared_Hugetlb:0kB
Private_Hugetlb:0kB
Swap:0kB
SwapPss:0kB
Locked:0kB
VmFlags:mrmwmenr
......

对照 NMT 的情况,我们发现如 fffd60021000-fffd64000000 这种 65404 KB 的内存是并没有被 NMT 追踪到的。这是因为在 JVM 进程中,除了 JVM 进程自己 mmap 的内存(如 Java Heap,和用户进程空间的 Heap 并不是一个概念)外,JVM 还直接使用了类库的函数来分配一些数据,如使用 Glibc 的 malloc/free (也是通过 brk/mmap 的方式):

6456547a-64fa-11ed-8abf-dac502259ad0.png

既然 JVM 使用了 Glibc 的 malloc/free,就不得不提及 malloc 的机制,早期版本的 malloc 只有一个 arena(分配区),每次分配时都要对分配区加锁,分配完成之后再释放,这就导致了多线程的情况下竞争比较激烈。

所以 malloc 改动了其分配机制,甚至有了 arena per-thread 的模式,即如果在一个线程中首次调用 malloc,则创建一个新的 arena,而不是去查看前面的锁是否会发生竞争,对于一定数量的线程可以避免竞争在自己的 arena 上工作。

arena 的数量限制在 32 位系统上是 2 * CPU 核心数,64 位系统上是 8 * CPU 核心数,当然我们也可以使用 MALLOC_ARENA_MAX (Linux 环境变量,详情可以查看 mallopt(3)[1])来控制。

查看发现运行 JVM 进程的环境 CPU 信息(物理 CPU 核数):Core(s) per socket: 64 。

我们给当前环境设置 MALLOC_ARENA_MAX=2,重启 JVM 进程,查看使用情况:

top

PIDUSERPRNIVIRTRESSHRS%CPU%MEMTIME+COMMAND
36319douyiwa+200310834069087217828S100.00.30:07.61java

虚高的 VIRT 内存已经降下来了,继续查看 pmap/smaps 会发现众多的 65404 KB 的内存空间也消失了(120 * 65404 KB = 7848480 KB 正好对应了 10.7g - 3108340 KB 的内存,即 VIRT 降低的内存)。

为什么我们的 JVM 进程会使用如此多的 arena 呢?因为我们在启动 JVM 进程的时候,并没有手动去设置一些进程的数目,如:CICompilerCount(编译线程数)、ConcGCThreads/ParallelGCThreads(并发 GC 线程数)、G1ConcRefinementThreads(G1 Refine 线程数)等等。

这些参数大多数根据当前机器的 CPU 核数去计算默认值,使用 jinfo -flags 查看机器参数发现:

-XX:CICompilerCount=18
-XX:ConcGCThreads=11
-XX:G1ConcRefinementThreads=43

这些线程数目都是比较大的,我们也可以不修改 MALLOC_ARENA_MAX 的数量,而通过参数减小线程的数量来减少 arena 的数量。

Glibc 的 malloc 有时会出现碎片问题,可以使用 jemalloc/tcmalloc 等替代 Glibc。

案例二:堆外内存的排查

有时候我们会发现,Java 堆、MetaSpace 等区域是比较正常的,但是 JVM 进程整体的内存却在不停的增长,此时我们就可以使用 NMT 的 baseline & diff 功能来观察究竟是哪块区域内存一直增长。

比如在一次案例中发现:

NativeMemoryTracking:
Total:reserved=8149334KB+1535794KB,committed=6999194KB+1590490KB
......
-Internal(reserved=1723321KB+1472458KB,committed=1723321KB+1472458KB)
(malloc=1723289KB+1472458KB#109094+47573)
(mmap:reserved=32KB,committed=32KB)
......
[0x00007fceb806607a]Unsafe_AllocateMemory+0x17a
[0x00007fcea1d24e68]
(malloc=1485579KBtype=Internal+1455929KB#2511+2277)
......

我们可以确认内存 1590490KB 的增长,基本上都是由 Internal 的 Unsafe_AllocateMemory 所分配的,此时可以优先考虑 NIO 中 ByteBuffer.allocateDirect / DirectByteBuffer / FileChannel.map 等使用方式是不是出现了泄漏,可以使用 MAT 查看 DirectByteBuffer 对象的数量是否异常,并可以使用 -XX:MaxDirectMemorySize 来限制 Direct 的大小。

设置 -XX:MaxDirectMemorySize 之后,进程异常的内存增长停止,但是 GC 频率变高,查看 GC 日志发现:.887+0800: 238210.127: [Full GC (System.gc()) 1175M->255M(3878),0.8370418 secs]。

FullGC 的频率大大增加,并且基本上都是由 System.gc() 显式调用引起的(HotSpot中的System.gc()为 FulGC),查看 DirectByteBuffer 相关逻辑:

#DirectByteBuffer.java

DirectByteBuffer(intcap){//package-private
......
Bits.reserveMemory(size,cap);

longbase=0;
try{
base=unsafe.allocateMemory(size);
}catch(OutOfMemoryErrorx){
Bits.unreserveMemory(size,cap);
throwx;
}
unsafe.setMemory(base,size,(byte)0);
if(pa&&(base%ps!=0)){
//Rounduptopageboundary
address=base+ps-(base&(ps-1));
}else{
address=base;
}
cleaner=Cleaner.create(this,newDeallocator(base,size,cap));
att=null;
}

#Bits.java

staticvoidreserveMemory(longsize,intcap){
......
System.gc();
......
}

DirectByteBuffer 在 unsafe.allocateMemory(size) 之前会先去做一个 Bits.reserveMemory(size, cap) 的操作,Bits.reserveMemory 会显式的调用 System.gc() 来尝试回收内存,看到这里基本可以确认为 DirectByteBuffer 的问题,排查业务代码,果然发现一处 ByteBufferStream 使用了 ByteBuffer.allocateDirect 的方式而流一直未关闭释放内存,修正后内存增长与 GC 频率皆恢复正常。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JVM
    JVM
    +关注

    关注

    0

    文章

    157

    浏览量

    12206
  • LINUX内核
    +关注

    关注

    1

    文章

    316

    浏览量

    21617
  • NMT
    NMT
    +关注

    关注

    0

    文章

    7

    浏览量

    3626

原文标题:Native Memory Tracking 详解(4):使用 NMT 协助排查内存问题案例

文章出处:【微信号:openEulercommunity,微信公众号:openEuler】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    详细介绍一下PSS+Pnoise仿真

    PSS+Pnoise仿真是很多电路要用到的仿真,今天我们详细介绍一下这个仿真。
    的头像 发表于 11-03 18:13 6820次阅读
    详细<b class='flag-5'>介绍</b><b class='flag-5'>一下</b>PSS+Pnoise仿真

    RTOS内存管理问题谁来解答一下

    定义了个buff,通过pvPortMalloc函数给buff申请了内存。但是如何去判断我申请到的BUFF的内存有没有用完呢?看来一下介绍
    发表于 06-15 04:35

    请问一下示波器可用于EMI排查吗?

    请问一下示波器可用于EMI排查吗?
    发表于 04-30 06:48

    请问一下示波器可用于电磁干扰排查嘛?

    工程师在排查EMI问题所面临的最常见的挑战是什么?如何区分出无用的骚扰行为?请问一下示波器可用于电磁干扰排查嘛?
    发表于 05-08 07:40

    分享内存泄漏定位排查技巧

    的调试工具,下面分享内存泄漏定位排查技巧。1.对malloc,free进行封装首先,我们对malloc,f
    发表于 12-17 08:13

    Native Memory Tracking 详解(4):使用 NMT 协助排查内存问题案例

    从前面几篇文章,我们了解了 NMT 的基础知识以及 NMT 追踪区域分析的相关内容,本篇文章将为大家介绍一下使用 NMT
    发表于 11-24 14:19

    简要介绍一下Python-UNO的使用方法

    OpenOffice是个免费的、开源的办公套装,集成了允许开发者用不同语言进行开发的API。Python-UNO让你可以在Python环境使用OpenOffice。本文简要介绍一下
    的头像 发表于 01-04 14:54 8497次阅读
    简要<b class='flag-5'>介绍</b><b class='flag-5'>一下</b>Python-UNO的使用方法

    电磁炉加热一下就停一下什么原因及解决办法

    电磁炉有时会出现加热故障,现象是热一下一下在热一下又停一下,基本隔
    发表于 03-18 09:02 27.4w次阅读

    电磁炉加热一下就停一下什么原因

    电磁炉加热一下就停一下什么原因。
    的头像 发表于 06-04 10:01 3.8w次阅读

    如何使用NMT和pmap来解决JVM的资源泄漏问题

    编者按:笔者使用 JDK 自带的内存跟踪工具 NMT 和 Linux 自带的 pmap 解决了个非常典型的资源泄漏问题。这个资源泄漏是由于 Java 程序员不正确地使用 Java API 导致
    的头像 发表于 09-24 16:00 3381次阅读
    如何使用<b class='flag-5'>NMT</b>和pmap来解决JVM的资源泄漏问题

    介绍NMT追踪区域的部分内存类型

    除去这上面的部分选项,我们发现 NMT 中还有个 unknown 选项,这主要是在执行 jcmd 命令时,内存类别还无法确定或虚拟类型信息还没有到达时的
    的头像 发表于 10-21 09:26 1161次阅读

    介绍一下高低温试验箱的校验项目与方法

    介绍一下高低温试验箱的校验项目与方法
    的头像 发表于 06-12 09:49 441次阅读
    <b class='flag-5'>介绍</b><b class='flag-5'>一下</b>高低温试验箱的校验项目与方法

    次Rust内存泄漏排查之旅

    在某次持续压测过程中,我们发现 GreptimeDB 的 Frontend 节点内存即使在请求量平稳的阶段也在持续上涨,直至被 OOM kill。我们判断 Frontend 应该是有内存泄漏了,于是开启了排查
    的头像 发表于 07-02 11:52 651次阅读
    记<b class='flag-5'>一</b>次Rust<b class='flag-5'>内存</b>泄漏<b class='flag-5'>排查</b>之旅

    glibc导致的堆外内存泄露的排查过程

    本文记录次glibc导致的堆外内存泄露的排查过程。
    的头像 发表于 09-01 09:43 677次阅读
    glibc导致的堆外<b class='flag-5'>内存</b>泄露的<b class='flag-5'>排查</b>过程

    java内存溢出排查方法

    过程中常见的问题之,可能导致应用程序崩溃、性能下降甚至系统崩溃。在本文中,将详细介绍如何排查和解决Java内存溢出问题。 、什么是Jav
    的头像 发表于 11-23 14:46 3105次阅读