Investigating Off‑Heap Memory Leak in Dble Using Java NIO and BTrace

This article walks through a real‑world case of off‑heap memory leakage in the Dble proxy, detailing symptom observation, log analysis, monitoring, BTrace instrumentation, code review, and the eventual fix that delays buffer allocation until needed.

Aikesheng Open Source Community
Aikesheng Open Source Community
Aikesheng Open Source Community
Investigating Off‑Heap Memory Leak in Dble Using Java NIO and BTrace

When using Java NIO with Dble, a client experienced a complete timeout of MySQL heartbeat checks, which was temporarily resolved by restarting the service. The root cause was identified as an off‑heap memory leak.

Phenomenon

All backend MySQL instances reported heartbeat timeouts, and the logs contained repeated warnings about exceeding the DirectByteBufferPool size.

Analysis Process

Log inspection revealed messages such as //心跳超时 and

You may need to turn up page size. The maximum size of the DirectByteBufferPool that can be allocated at one time is 2097152, and the size that you would like to allocate is 4194304

. These indicated possible long GC pauses due to insufficient off‑heap memory.

Verification

Monitoring graphs showed a short‑term spike in memory usage and high CPU load on the Dble host, while the free‑buffer metric demonstrated a gradual decline of off‑heap memory after startup, confirming that the leak exhausted the off‑heap pool and forced Dble to allocate on‑heap buffers for network packets.

Off‑Heap Memory Leak Analysis

BTrace was employed to trace allocation and release of off‑heap buffers. The following BTrace script records the addresses of allocated and recycled DirectByteBuffers:

package com.actiontech.dble.btrace.script;

import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.*;
import sun.nio.ch.DirectBuffer;
import java.nio.ByteBuffer;

@BTrace(unsafe = true)
public class BTraceDirectByteBuffer {
    @OnMethod(clazz = "com.actiontech.dble.buffer.DirectByteBufferPool", method = "recycle", location = @Location(Kind.RETURN))
    public static void recycle(@ProbeClassName String pcn, @ProbeMethodName String pmn, ByteBuffer buf) {
        String threadName = BTraceUtils.currentThread().getName();
        if (!threadName.contains("writeTo")) {
            String js = BTraceUtils.jstackStr(15);
            if (!js.contains("heartbeat") && !js.contains("XAAnalysisHandler")) {
                BTraceUtils.println(threadName);
                if (buf.isDirect()) {
                    BTraceUtils.println("r:" + ((DirectBuffer) buf).address());
                }
                BTraceUtils.println(js);
            }
        }
    }

    @OnMethod(clazz = "com.actiontech.dble.buffer.DirectByteBufferPool", method = "allocate", location = @Location(Kind.RETURN))
    public static void allocate(@Return ByteBuffer buf) {
        String threadName = BTraceUtils.currentThread().getName();
        if (!threadName.contains("writeTo")) {
            String js = BTraceUtils.jstackStr(15);
            if (!js.contains("heartbeat") && !js.contains("XAAnalysisHandler")) {
                BTraceUtils.println(threadName);
                if (buf.isDirect()) {
                    BTraceUtils.println("a:" + ((DirectBuffer) buf).address());
                }
                BTraceUtils.println(js);
            }
        }
    }
}

Running the script and filtering the logs:

$ btrace -o /path/to/log -u 11735 /path/to/BTraceDirectByteBuffer.java
$ grep '^a:' /tmp/142-20-dble-btrace.log > allocat.txt
$ sed 's/..//' allocat.txt > allocat_addr.txt
$ grep '^r:' /tmp/142-20-dble-btrace.log > release.txt
$ sed 's/..//' release.txt > release_addr.txt
$ sort allocat_addr.txt release_addr.txt | uniq -u > res.txt

The resulting res.txt lists addresses that were allocated but never released, pointing to the leak.

Code Review

Stack traces from the leaked addresses highlighted the allocation path:

com.actiontech.dble.buffer.DirectByteBufferPool.allocate(DirectByteBufferPool.java:82)
com.actiontech.dble.net.connection.AbstractConnection.allocate(AbstractConnection.java:395)
com.actiontech.dble.backend.mysql.nio.handler.query.impl.OutputHandler.<init>(OutputHandler.java:51)
...

Reviewing the relevant Dble source revealed that OutputHandler creates a DirectByteBuffer during construction:

public OutputHandler(long id, NonBlockingSession session) {
    super(id, session);
    session.setOutputHandler(this);
    this.lock = new ReentrantLock();
    this.packetId = (byte) session.getPacketId().get();
    this.isBinary = session.isPrepared();
    // allocate off‑heap memory
    this.buffer = session.getSource().allocate();
}

The allocation occurs even when the complex query execution chain is later abandoned (e.g., when routeSingleNode != null causes an early return), leaving the buffer unreleased.

Fix

The resolution is to defer buffer allocation until it is actually needed, removing the eager allocation in the OutputHandler constructor.

Conclusion

By combining log inspection, monitoring, BTrace instrumentation, and targeted code review, the off‑heap memory leak in Dble was traced to premature buffer allocation in OutputHandler, and the fix prevents unnecessary off‑heap consumption.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

debuggingmemory leakOff-Heap MemoryJava NIOBTrace
Aikesheng Open Source Community
Written by

Aikesheng Open Source Community

The Aikesheng Open Source Community provides stable, enterprise‑grade MySQL open‑source tools and services, releases a premium open‑source component each year (1024), and continuously operates and maintains them.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.