Mastering Zero-Copy in Java: Boost Performance with NIO, Netty, and Sendfile

This article explains the concept of zero-copy I/O, covering buffer types, virtual memory, mmap+write and sendfile techniques in Java, and demonstrates how Java NIO, Netty, and other frameworks like RocketMQ and Kafka leverage zero-copy to reduce data copying and improve performance.

Programmer DD
Programmer DD
Programmer DD
Mastering Zero-Copy in Java: Boost Performance with NIO, Netty, and Sendfile

Preface

Zero‑copy means data does not need to be copied back and forth, greatly improving system performance. The term appears frequently in frameworks such as Java NIO, Netty, Kafka, and RocketMQ as a key performance highlight. Below we start with basic I/O concepts and then analyse zero‑copy.

I/O Concepts

1. Buffer

A buffer is the foundation of all I/O. I/O essentially moves data into or out of a buffer. When a process performs an I/O operation, it requests the OS to either drain the kernel buffer (write) or fill it (read). The following diagram shows a typical Java read request flow.

After a read request, the kernel first checks if the required data already resides in kernel space; if so it copies the data to the process buffer. Otherwise the kernel issues a DMA command to the disk controller, which writes data directly into the kernel read buffer, after which the kernel copies the data to the process buffer. For a write request, data is copied from the user buffer to the kernel socket buffer and then DMA‑transferred to the NIC. This double copy motivates zero‑copy, which provides two approaches: mmap+write and sendfile.

2. Virtual Memory

Modern operating systems use virtual memory, mapping virtual addresses to physical ones. This allows multiple virtual addresses to refer to the same physical memory and lets the virtual address space exceed physical memory. By mapping kernel and user virtual addresses to the same physical page, DMA can fill a buffer visible to both kernel and user space, as illustrated below.

This eliminates copies between kernel and user space; Java leverages this OS feature to improve performance. The following sections focus on Java’s zero‑copy support.

3. mmap+write

The mmap+write method replaces the traditional read+write flow. mmap maps a file (or other object) into a process’s address space, establishing a one‑to‑one correspondence between a file’s disk address and a range of virtual addresses. This removes the need for the kernel to copy data from its read buffer to the user buffer, though a copy from the kernel read buffer to the kernel socket buffer may still occur. Diagram:

4. sendfile

The sendfile system call, introduced in kernel 2.1, simplifies data transfer between two channels over the network. It reduces both data copying and context switches. Diagram:

Data transfer occurs entirely in kernel space, eliminating one context switch. Linux 2.4 further optimised this by recording the data’s memory address and offset in the socket buffer, removing the remaining kernel‑to‑user copy.

Java Zero‑Copy

1. MappedByteBuffer

Java NIO’s FileChannel provides a map() method that creates a virtual memory mapping between an opened file and a MappedByteBuffer. MappedByteBuffer extends ByteBuffer, but its data resides in the file on disk. get() reads from the file, while put() updates the file and is visible to other readers. Example:

public class MappedByteBufferTest {
    public static void main(String[] args) throws Exception {
        File file = new File("D://db.txt");
        long len = file.length();
        byte[] ds = new byte[(int) len];
        MappedByteBuffer mappedByteBuffer = new FileInputStream(file).getChannel()
            .map(FileChannel.MapMode.READ_ONLY, 0, len);
        for (int offset = 0; offset < len; offset++) {
            byte b = mappedByteBuffer.get();
            ds[offset] = b;
        }
        Scanner scan = new Scanner(new ByteArrayInputStream(ds)).useDelimiter(" ");
        while (scan.hasNext()) {
            System.out.print(scan.next() + " ");
        }
    }
}

The map() signature:

public abstract MappedByteBuffer map(MapMode mode, long position, long size) throws IOException;
MapMode

can be READ_ONLY, READ_WRITE, or PRIVATE. PRIVATE creates a copy‑on‑write mapping that is not persisted to the underlying file.

2. DirectByteBuffer

DirectByteBuffer

extends MappedByteBuffer and allocates memory outside the JVM heap, avoiding GC overhead. It can be obtained via FileChannel.map() or manually:

ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(100);

3. Channel‑to‑Channel Transfer

FileChannel’s transferTo() method transfers data directly between channels without an intermediate user‑space buffer. Example:

public class ChannelTransfer {
    public static void main(String[] argv) throws Exception {
        String[] files = new String[1];
        files[0] = "D://db.txt";
        catFiles(Channels.newChannel(System.out), files);
    }
    private static void catFiles(WritableByteChannel target, String[] files) throws Exception {
        for (int i = 0; i < files.length; i++) {
            FileInputStream fis = new FileInputStream(files[i]);
            FileChannel channel = fis.getChannel();
            channel.transferTo(0, channel.size(), target);
            channel.close();
            fis.close();
        }
    }
}

Signature:

public abstract long transferTo(long position, long count, WritableByteChannel target) throws IOException;

This allows two kernel buffers to exchange data without copying to user space.

Netty Zero‑Copy

Netty provides zero‑copy buffers using CompositeChannelBuffer (for aggregation) and Slice (for splitting). The composite buffer holds references to multiple received buffers, avoiding new memory allocation and data copying. Example snippet:

public class CompositeChannelBuffer extends AbstractChannelBuffer {
    private final ByteOrder order;
    private ChannelBuffer[] components;
    private int[] indices;
    private int lastAccessedComponentId;
    private final boolean gathering;
    public byte getByte(int index) {
        int componentId = componentId(index);
        return components[componentId].getByte(index - indices[componentId]);
    }
}

The buffer stores references to the original channel buffers, achieving zero‑copy.

Other Zero‑Copy Uses

RocketMQ writes messages sequentially to a commit log and uses a consume‑queue index; it employs mmap+write zero‑copy to serve consumers. Kafka also uses the sendfile zero‑copy method for persisting network data and transmitting files.

Conclusion

Zero‑copy in Java can be understood as operating on object references rather than copying object data; each reference points to a single underlying object, ensuring only one copy exists throughout the processing pipeline.

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.

performancemmapsendfileZero Copy
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.