Java SDK direct memory leak (missing ByteBuf release ?)

Hi All,

We are using the last version of the Java Couchbase SDK (2.2.5 a the time of writing) and it seems that we are facing a memory leak with on direct memory allocated by Netty.

As mentionned in the documentation Env config doc we have disabled buffer pooling with the system property com.couchbase.bufferPoolingEnabled=false because we suspected the SDK to be the origin of the memory leak.

And it worked. The memory leak was gone. The documentation says to open a ticket, if this his happenning. I create this topic instead, to be sure I am using the SDK as I should.

Here is how we create the DefaultCouchbaseEnvironment :

DefaultCouchbaseEnvironment.Builder envBuilder = DefaultCouchbaseEnvironment
                .reconnectDelay(Delay.linear(TimeUnit.SECONDS, TimeUnit.HOURS.toSeconds(12), 10, 10))
                    .defaultMetricsLoggingConsumer(true, CouchbaseLogLevel.INFO);
DefaultCouchbaseEnvironment environment =;

Here is how we open buckets :

 List<Transcoder<? extends Document, ?>> transcoders = new ArrayList<>(2);

LegacyTranscoder legacyTranscoder = new LegacyTranscoder(compressionMinSize);
BinaryTranscoder binaryTranscoder = new BinaryTranscoder();


Bucket bucket = cluster.openBucket(bucketName, bucketPassword, transcoders);

Here is how we are accessing the bucket :

// reading
 return bucket.async()
                .get(id, LegacyDocument.class)
                .timeout(readTimeoutInMs, TimeUnit.MILLISECONDS)
                .onErrorResumeNext(throwable -> {
                    Throwable cause = throwable.getCause();
                    boolean exceptionForReplica = throwable instanceof TimeoutException || throwable instanceof TemporaryFailureException || cause instanceof TimeoutException || cause instanceof TemporaryFailureException;
                    if (exceptionForReplica) {
                        return bucket
                                .getFromReplica(id, ReplicaMode.FIRST, LegacyDocument.class)
                                .timeout(readReplicaTimeoutInMs, TimeUnit.MILLISECONDS);

                    return Observable.error(throwable);

// upsert
LegacyDocument document = LegacyDocument.create(

return bucket.async()
                .timeout(writeTimeoutInMs, TimeUnit.MILLISECONDS)
                .map((Func1<LegacyDocument, Void>) legacyDocument -> null);

// delete
return bucket.async()
                .remove(key, LegacyDocument.class)
                .map((Func1<LegacyDocument, Void>) document -> null);

Thanks for your help

1 Like

Are these Transcoders custom ones? (you named one instance “patchedBinaryTranscoder”)


Thanks for your quick reply. No this is the BinaryTranscoder included in the SDK. The variable name is false, because we were using a custom one before using the BinaryTranscoder included in the SDK.
I will edit my first post to make this clear

ok. No need for using the overload with a list of Transcoder when opening the Bucket then.

Can you try to come up with a simple main class that shows the leak, maybe using Netty’s leak detector at paranoid level before opening the cluster (use ResourceLeakDetector.setLevel(PARANOID) for that)?

Could be a problem with the LegacyDocument, unless you’re actually using BinaryDocument somewhere else (you haven’t shown code that does that).

If you’re using BinaryDocument, make sure you follow the advice from the document basics documentation: working with BinaryDocument and correctly managing buffers


We are not using the BinaryDocument because we needed to be backward compatible with the SDK version 1.

What do you mean when you’re saying [quote=“simonbasle, post:4, topic:7186”]
No need for using the overload with a list of Transcoder when opening the Bucket then.
[/quote] ?

We set the ResourceLeakDetector and it show the following trace
(sorry for the long stack trace) :

03-01 15:05:29.365 [cb-io-1-8] ERROR util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See for more information.
Recent access records: 4

Created at:$Decoder.decode($$$

what I meant by not using the overload for opening the bucket is that you can simply avoid creating a List<Transcoder> and instead just open the bucket like so:

Bucket bucket = cluster.openBucket(bucketName, bucketPassword);

Unfortunately the leak trace doesn’t help us much (as I feared), but confirm that it happens while handling a memcached/couchbase message. Reproducing in a simple test case would help tremendously.

How can I use these transcoders if i don’t give them to the openBucket method ?

Reproducing this will be difficult. We did load tests on this code and the direct memory did not exceeded 200 MB.
If i have enough free time I will try to create a minimal test to reproduce the memory leak.

The openBucket method only needs custom transcoders. it already knows about all the ones that are provided by the SDK.

Ok thanks for the explanation. It was not clear that this parameter was only for custom transcoders.

It’s hard to reproduct in a unit test.
But i made a simple application who read key value using LegacyDocument and i have the same issue.
The direct buffer size increases over the time.

Maybe it’s the LegacyDocumentTranscoder.

Is there another document type which is not legacy but supports compression and binary data ?

getting back at this, what I initially missed is you provide a custom compression threshold to the LegacyTranscoder. So indeed you need to pass that one to the openBucket method and it’ll replace the default one (that has probably a higher threshold).

Can you say what kind of data you store during the write? (String? Bytes? Long? Serializable?..)

Can you give an example of such a data item that causes the leak detector to trigger? What’s the compression threshold like?

Can you share the code that does the actual call to the read and write snippets you’ve shared? (they don’t include the methods signatures, return an Observable<LegacyDocument> but never shows where it’s subscribed to…)

@cnguyen would it be possible to share the small demonstration app that you wrote (eg. on github, or if it’s just a few files then in a gist)?

We customize only the compression threshold. By default, it is at 16ko but we have small object, about 2,5kB before compression.
With compression, their sizes are about 300 bytes.

We set the threshold to 100 bytes because we have very small objects and we don’t want to compress them.

Here is an example where i have the memory leak.

does it get through the onErrorResumeNext?

can you isolate a single document that triggers the leak and share its content, or similar “redacted” content that would trigger the leak as well (see if the leak is in the decode & decompress path)?

but that’s assuming the leak comes from transcoding of the LegacyDocument…

hi @cnguyen,
any good news about this? i have the same errors about LEAK:

2017-02-12 02:49:15 ERROR ResourceLeakDetector:171 - LEAK: ByteBuf.release() was not called before it’s garbage-collected. See for more information.
Recent access records: 4
Created at:$Decoder.decode(

Hi @yiman,

I have just disable the buffer with the following jvm arguments :


I use LegacyDocument because i store my data as binary with gzip compression.

@cnguyen instead of disabling buffer pooling you should release the buffers you are not using anymore?

@daschl when should i release it ? After each read ? The SDK can’t manage it for me ? :smiley:

@cnguyen you need to release the bytebuffer on read once you are done using it, so it can be returned to the pool. This is what the SDK does when decoding it to json, string and so forth, but since the BinaryDocument gives you direct access to the buffer the SDK has no idea when you are done using it. If you don’t release it, it gets never put back into the pool and leaked.

If you don’t care about an additional memory copy just get the value on the heap and be done with it:

byte[] data = new byte[content.readableBytes()];