设为首页 加入收藏

TOP

jdk17下netty导致堆内存疯涨原因排查(二)
2023-09-09 10:25:52 】 浏览:73
Tags:jdk17 netty
ner(capacity) : ByteBuffer.allocateDirect(capacity);
  • 三) USE_DIRECT_BUFFER_NO_CLEANER 参数逻辑配置在PlatformDependent 类的static{}里面。

    关键逻辑:maxDirectMemory==0和!hasUnsafe()在jdk17下没有特殊配置都是不满足条件的,关键是PlatformDependent0.hasDirectBufferNoCleanerConstructor的判断逻辑

if (maxDirectMemory == 0 || !hasUnsafe() || !PlatformDependent0.hasDirectBufferNoCleanerConstructor()) {  
    USE_DIRECT_BUFFER_NO_CLEANER = false;  
} else {  
    USE_DIRECT_BUFFER_NO_CLEANER = true;  
  


  • 四) PlatformDependent0.hasDirectBufferNoCleanerConstructor()的判断是看PlatformDependent0的DIRECT_BUFFER_CONSTRUCTOR是否NULL,回到了刚开的debug日志,我们是可以看到在默认情况下DIRECT_BUFFER_CONSTRUCTOR该构造器是unavailable的(unavailable则为NULL)。以下代码具体的逻辑判断及其伪代码。

1.开启条件一:jdk9及其以上必须要开启jvm参数 -io.netty.tryReflectionSetAccessible参数

2.开启条件二:能反射获取到一个 private DirectByteBuffer构造器,该构造器是通过内存地址和大小来构造DirectByteBuffer.(备注:如果在jdk9以上对java.nio有模块权限限制,需要加上jvm启动参数--add-opens=java.base/java.nio=ALL-UNNAMED ,否则会报Unable to make private java.nio.DirectByteBuffer(long,int) accessible: module java.base does not "opens java.nio" to unnamed module)

所以这里我们默认是没有开启这两个jvm参数的,那么DIRECT_BUFFER_CONSTRUCTOR为空值,对应第二部PlatformDependent.useDirectBufferNoCleaner()为false。

  
    // 伪代码,实际与这不一致  
 ByteBuffer direct = ByteBuffer.allocateDirect(1);  
  
    if(SystemPropertyUtil.getBoolean("io.netty.tryReflectionSetAccessible",  
        javaVersion() < 9 || RUNNING_IN_NATIVE_IMAGE)) {  
         DIRECT_BUFFER_CONSTRUCTOR =  
         direct.getClass().getDeclaredConstructor(long.class, int.class)  
        }  


  • 五) 现在回到第2步骤,发现PlatformDependent.useDirectBufferNoCleaner()在jdk高版本下默认值是false。那么每次申请直接内存都是通过ByteBuffer.allocateDirect来创建。那么到这个时候就已经定位到相关根因了,通过ByteBuffer.allocateDirect来申请直接内存,如果内存不足的时候会强制系统System.Gc(),并且会同步等待DirectByteBuffer通过Cleaner的虚引用回收内存。下面是ByteBuffer.allocateDirect预占内存(reserveMemory)的关键代码。大概逻辑是 触达申请的最大的直接内存->判断是否有相关的对象在gc回收->没有在回收则主动触发System.gc()来触发回收->在同步循环最多等待MAX_SLEEPS次数看是否有足够的直接内存。整个同步等待逻辑在亲测在jdk17版本最多能1秒以上。

所以最根本原因:如果这个时候我们的netty的消费者EventLoop处理消费因为申请直接内存在达到最大内存的场景,那么就会导致有大量的任务消费都会同步去等待申请直接内存上。并且如果没有足够的的直接内存,那么就会成为大面积的消费阻塞。

  
static void reserveMemory(long size, long cap) {  
  
    if (!MEMORY_LIMIT_SET && VM.initLevel() >= 1) {  
        MAX_MEMORY = VM.maxDirectMemory();  
        MEMORY_LIMIT_SET = true;  
    }  
  
    // optimist!  
    if (tryReserveMemory(size, cap)) {  
        return;  
    }  
  
    final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();  
    boolean interrupted = false;  
    try {  
  
        do {  
            try {  
                refprocActive = jlra.waitForReferenceProcessing();  
            } catch (InterruptedException e) {  
                // Defer interrupts and keep trying.  
                interrupted = true;  
                refprocActive = true;  
            }  
            if (tryReserveMemory(size, cap)) {  
                return;  
            }  
        } while (refprocActive);  
  
        // trigger VM's Reference processing  
        System.gc();  
  
        int sleeps = 0;  
        while (true) {  
            if (tryReserveMemory(size, cap)) {  
                return;  
            }  
            if (sleeps >= MAX_SLEEPS) {  
                break;  
            }  
            try {  
                if (!jlra.waitForReferenceProcessing()) {  
                    Thread.sleep(sleepTime);  
                    sleepTime <<= 1;  
                    sleeps++;  
                }  
            } catch (InterruptedException e) {  
                interrupted = true;  
            }  
        }  
  
        // no luck  
        throw new OutOfMemoryError  
            ("Cannot reserve "  
             + size + " bytes of direct buffer memory (allocated: "  
             + RESERVED_MEMORY.get() + ", limit: " + MAX_MEMORY +")");  
  
    } finally {  
        if (interru
首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇1、SpringMVC简介 下一篇springboot~InvocationHandler中..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目