Java进程的内存占用[译] Part 5 - AndreiPangin

原视频链接:不存在的网站,翻译此视频并发布已获得原作者同意。

Java进程的内存占用[译] Part 5 - AndreiPangin

堆外内存-Buffers

在这里插入图片描述
好了, 接下来看NMT报告中的下一个部分:Internal,这个不是很重要但挺有意思,因为它可以增长到极其巨大。这是一个堆外的内存,换句话说Direct Memory,不管你是通过直接缓冲(Direct ByteBuffers),还是Unsafe.allocateMemory方式分配的,都会在报告中的这部分显示。

但是这个名称看起来是不是很怪,直接缓冲和内部这两个词有关系吗?这个问题在JDK11中已修复:把ByteBuffers占用的内存分类,从Internal改成了Other,不过在我看来也不是很清晰。
在这里插入图片描述
接下来我们看下直接缓冲和内存占用的关系。有两种直接缓冲:

  1. 通过ByteBuffer.allocateDirect分配的,可以通过-XX:MaxDirectMemorySize限制,挺奇怪的,默认值竟然和Xmx一样,因为堆和非堆好像没啥关系。不过如果你的应用使用了很多直接内存,这也是一种限制方式。
  2. mmap分配方式要更糟,因为这种情况你完全无法限制大小,并且NMT报告不包含这部分内存。那么会被记入内存占用(RSS)吗?
    在这里插入图片描述
    当然是可以的,回忆下,你可以使用pmap命令,查看进程的内存映射,包含了ByteBuffers方式和mmap方式分配的内存。所以对于数据库来说,这些内存总量将会非常大。
    在这里插入图片描述
    你可以通过JMX、JConsole、Java Mission Controll或者其他工具监控Buffer,找到ByteBuffer MBean,有两个MBean,一个是mapped buffer,一个是direct buffer。
    在这里插入图片描述
    管理ByteBuffer消耗的内存并不容易,因为并没有一个释放ByteBuffer的标准方式,JVM没有对ByteBuffer的引用时将会自动释放,所以如果你有一个大的Java堆,而GC很少,那你DirectMemory会更早用尽,然后GC才会触发。在HotSpot JVM中,有一个有趣的处理方式,如果获取不到Direct Memory,JVM会执行System.gc(),等一段时间,希望能回收掉一些内存,给新的ByteBuffer腾出一些空间,然后以此方式循环。

这导致了一些有意思的后果,如果你因为一些原因,禁用了显式GC(-XX:+DisableExplicitGC),这个算法将不工作,那么最终就是OutofMemory。这就是为什么,在我们的系统中,从不使用-XX:+DisableExplicitGC选项,而是使用-XX:+ExplicitGCInvokesConcurrent,所以显式GC不一定就是不好的。
在这里插入图片描述
好的,如果Direct ByteBuffer不好用,那么Heap ByteBuffer怎么样?直接点,Heap ByteBuffer不是解决方案,因为在Java的Heap ByteBuffer不能进行I/O,不管什么时候你尝试读或写Heap ByteBuffer,Java将分配一块临时的Direct ByteBuffer,复制数据到上面,再释放掉。这种分配、释放会导致更多的内存占用,这就是为什么会有内部的缓存用于临时的Direct ByteBuffer。
在这里插入图片描述
如上图,你可能在堆Dump分析中,注意到过这个sun.nio.ch.Util$BufferCache
在这里插入图片描述
BufferCache有几个特点:

  1. BufferCache的一个优点是它从不收缩,所以大的Buffer可以替换小的,但是他们不会被从Cache中丢弃。
  2. 另外,BufferCache是ThreadLocal的,所以如果你有许多线程都用到了Heap ByteBuffer,那么你麻烦大了,
  3. 但是好消息是,BufferCache是可以限制的,使用 -Djdk.nio.maxCachedBufferSize,所以如果要运行一个密集使用Direct ByteBuffer的,比如Elasticsearch或其他类似的,就能较好的进行限制最大CacheBuffer大小。
    在这里插入图片描述
    但以上并不是Direct ByteBuffer带来的所有麻烦,看下上面的代码,没什么问题是吧,这里我们分配ByteBuffer,也没有什么引用,所以他们肯定会被立刻释放掉,真的是这样吗?我测试了下,堆内存上限1G,Direct内存上限2G,然后监控实际的内存消耗。注意看下面的RES大小。
    在这里插入图片描述
    内存继续增长…
    在这里插入图片描述
    增长…
    在这里插入图片描述
    已经比限制的Direct内存加上堆内存都要大了,不过我们已经学过NMT了,来看看NMT报告。
    在这里插入图片描述
    额,NMT说整个JVM提交的内存是小于3G的,那么,有人在说谎!
    在这里插入图片描述
    好的,虚拟内存映射表再次拯救了场面,如果你再看一下内存映射,你会发现RSS是7G多,比较奇怪的是,这里有一些匿名的内存分区,简单粗暴的占用了64M内存,这是个神奇的数字,我们先假设它和标准的内存分配器有关。
    在这里插入图片描述
    malloc是一个C语言标准库,用途是在底层分配内存和Direct ByteBuffer,那么malloc以大块的形式从操作系统中保留内存,恰好64M大小,但是这只是保留不是分配。
    在这里插入图片描述
    malloc使用另外的系统调用mprotect提交小块的内存,
    在这里插入图片描述
    然后内存块就在这个被保护的区域中分配。
    在这里插入图片描述
    有些内存块可以被释放或者重用,但是本地内存块是永远不会动的。
    在这里插入图片描述
    在本例中,当我们以不同的随机的大小分配Direct内存时,这显然会导致碎片化的产生,我们可能会遇到这样的情况:这个 malloc arenas 中有很多空闲内存,但是没有足够的连续块来分配。
    在这里插入图片描述
    所以这基本上是系统标准内存分配器所导致的内存碎片问题,Java也没办法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值