Skip to content

allocation free AdaptiveByteBuf::setBytes(byte[])#16058

Merged
chrisvest merged 1 commit into
netty:4.2from
franz1981:4.2_put_array
Dec 18, 2025
Merged

allocation free AdaptiveByteBuf::setBytes(byte[])#16058
chrisvest merged 1 commit into
netty:4.2from
franz1981:4.2_put_array

Conversation

@franz1981
Copy link
Copy Markdown
Contributor

Motivation:
The adaptive buffer's setBytes(byte[]) method requires the underlying internal NIO buffer to be materialized, but temporary buffers (which won't get flushed to the network) are not granted to have it, causing an unwanted allocation

Modification:
Java 13 expose ad-hoc method which allow using the root parent NIO buffer, saving materializing the internal NIO buffer.

Result:
Cheaper setBytes

Motivation:
The adaptive buffer's setBytes(byte[]) method requires the underlying internal NIO buffer to be materialized, but temporary buffers (which won't get flushed to the network) are not granted to have it, causing an unwanted allocation

Modification:
Java 13 expose ad-hoc method which allow using the root parent NIO buffer, saving materializing the internal NIO buffer.

Result:
Cheaper setBytes
@franz1981 franz1981 requested a review from chrisvest December 17, 2025 07:15
@franz1981
Copy link
Copy Markdown
Contributor Author

This has popped out on some benchmark

image

And it's not the only place. More patches will arrive soon(ish).

if (PlatformDependent0.hasAbsolutePutArrayMethod()) {
return PlatformDependent0.absolutePut(dst, dstOffset, src, srcOffset, length);
} else {
ByteBuffer tmp = (ByteBuffer) dst.duplicate().clear().position(dstOffset).limit(dstOffset + length);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is not tested

@normanmaurer normanmaurer added this to the 4.2.10.Final milestone Dec 17, 2025
@normanmaurer normanmaurer requested a review from yawkat December 17, 2025 14:42
checkIndex(index, length);
ByteBuffer tmp = (ByteBuffer) internalNioBuffer().clear().position(index);
tmp.put(src, srcIndex, length);
if (tmpNioBuf == null && PlatformDependent.javaVersion() >= 13) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasAbsolutePutArrayMethod instead of javaVersion, maybe?

@chrisvest chrisvest merged commit 3cae6f6 into netty:4.2 Dec 18, 2025
19 checks passed
chrisvest pushed a commit to chrisvest/netty that referenced this pull request Dec 18, 2025
Motivation:
The adaptive buffer's setBytes(byte[]) method requires the underlying
internal NIO buffer to be materialized, but temporary buffers (which
won't get flushed to the network) are not granted to have it, causing an
unwanted allocation

Modification:
Java 13 expose ad-hoc method which allow using the root parent NIO
buffer, saving materializing the internal NIO buffer.

Result:
Cheaper setBytes

(cherry picked from commit 3cae6f6)
@chrisvest chrisvest added the needs-cherry-pick-5.0 This PR should be cherry-picked to 5.0 once merged. label Dec 18, 2025
@BaurzhanSakhariev
Copy link
Copy Markdown
Contributor

BaurzhanSakhariev commented Dec 18, 2025

Hi @franz1981, if I may ask regarding

The adaptive buffer's setBytes(byte[]) method requires the underlying internal NIO buffer to be materialized, but temporary buffers (which won't get flushed to the network) are not granted to have it, causing an unwanted allocation

Could it be that those unwanted allocations happen on heap or it's always materialized into direct buffers?

I was recently getting on-heap OOM when using direct buffers and saw setBytes in the exception trace and this PR caught my attention.

Copying only relevant for this PR part from the question

java.lang.OutOfMemoryError: Java heap space
	at java.base/java.nio.HeapByteBuffer.<init>(HeapByteBuffer.java:75)
	at java.base/java.nio.ByteBuffer.allocate(ByteBuffer.java:398)
	at io.netty.buffer.CompositeByteBuf.nioBuffer(CompositeByteBuf.java:1709)
	at io.netty.buffer.AdaptivePoolingAllocator$AdaptiveByteBuf.setBytes(AdaptivePoolingAllocator.java:1765)
	at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:1111)
	at io.netty.channel.nio.AbstractNioChannel.newDirectBuffer(AbstractNioChannel.java:519)
	at io.netty.channel.nio.AbstractNioByteChannel.filterOutboundMessage(AbstractNioByteChannel.java:280)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.write(AbstractChannel.java:739)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.write(DefaultChannelPipeline.java:1386)
	at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:823)
	at io.netty.channel.AbstractChannelHandlerContext$WriteTask.run(AbstractChannelHandlerContext.java:1136)
	at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:148)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:141)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java)

@franz1981
Copy link
Copy Markdown
Contributor Author

@BaurzhanSakhariev it is possible but I would still be surprised unless there is some leak on the source NIO heap buffer (that's why the copy happen looking at the stack trace) which keep it referenced and prevent it to be GCd.
Enable heap dump on OOM and open an issue so we can take a look 🙏

This PR is not fixing the src side but the designation side (which receive the copy) so it is possible you were already full w the heap and the additional allocation on dst side just give the final blow

@BaurzhanSakhariev
Copy link
Copy Markdown
Contributor

BaurzhanSakhariev commented Dec 18, 2025

you were already full w the heap

Right, I run test with low Xmx.
Direct memory limit is equal (or close) to max heap size if not specified and I wanted to cap direct memory with some small value.

However, that was not a good idea to cap it like that as I run tests on same JVM with my application and created many structures in test itself and had very small max heap setting.

Once I start running tests with "normal" max heap and instead directly specified XX:MaxDirectMemorySize to cap direct memory, I stopped getting on heap OOM with/without the seed.

normanmaurer pushed a commit that referenced this pull request Dec 18, 2025
Motivation:
The adaptive buffer's setBytes(byte[]) method requires the underlying
internal NIO buffer to be materialized, but temporary buffers (which
won't get flushed to the network) are not granted to have it, causing an
unwanted allocation

Modification:
Java 13 expose ad-hoc method which allow using the root parent NIO
buffer, saving materializing the internal NIO buffer.

Result:
Cheaper setBytes

(cherry picked from commit 3cae6f6)

Co-authored-by: Francesco Nigro <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-cherry-pick-5.0 This PR should be cherry-picked to 5.0 once merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants