正文
中间件中多个不同的ClassLoader加载了多个netty的io.netty.buffer.PooledByteBufAllocator,每一个都有1G的内存配额,所以存在实际使用的堆外内存超出1G限制的问题。
通过Arthas可以看到存在这个类的7个不同的实例:
而其中rocketmq-client的这一个,已经基本用完1G的内存(其它几个使用量大多在100多M的样子):
中间件中多个不同的ClassLoader加载了多个netty的io.netty.buffer.PooledByteBufAllocator,每个Allocator都用自己的计数器在限制堆外内存的使用量,这个限制值大多数情况下取值至MaxDirectMemorySize,所以会存在无法限制堆外内存使用量在1G以内的问题。(这个设计是否合理,还请中间件的同学帮忙补充了)
这个应用是饿了么弹内的应用,io.netty.buffer.PooledByteBufAllocator,有7个ClassLoader加载了它,分别是:
sentinel's ModuleClassLoader、rocketmq-client's ModuleClassLoader、tair-plugin's ModuleClassLoader、hsf's ModuleClassLoader、
XbootModuleClassLoader
、pandora-qos-service's ModuleClassLoader、
ele-enhancer's ModuleClassLoader
。
相比弹内应用的4个(数据来自淘天集团的核心应用ump2,如下图),多了3个。
在Java8,以及Java11中(JVM参数设置了-Dio.netty.tryReflectionSetAccessible=true过后),netty会直接使用unsafe的方法申请堆外内存,不通过Java的DirectMemory分配API,所以通过监控看不到堆外内存的占用量,也不受JVM MaxDirectMemorySize的管控。
查看DirectByteBuffer实现代码可以发现,它限制MaxDirectMemorySize的方法是在Java层(代码标记处1),实际上在JVM底层是没有任何限制的,netty是直接用了这里代码标记处2的API分配内存。