NIO supports a type of ByteBuffer usually known as
a direct buffer. Direct buffers can essentially be used like any other ByteBuffer
(and are implemented as a ByteBuffer subclass), but have the property that
their underlying memory is allocated outside the Java heap. More specifically,
direct buffers have the following properties:
- once allocated, their memory address is fixed for the lifetime of the buffer;
- because their address is fixed, the kernel can safely access them directly and hence
direct buffers can be used more efficiently in I/O operations;
- in some cases, accessing them from Java can be more efficient (there's potentially
less overhead in looking up the memory address and/or other housekeeping required
before accessing a Java object)— see the example NIO buffer performance measurements made in Hotspot under Windows;
- via the Java Native Interface, you can actually set the address arbitrarily if
required (e.g. to access hardware at a particular address, or to perform the allocation
In practice, in current versions of Hotspot, the memory is allocated
via a malloc(), although this could vary in other VMs or in a future
version of Hotspot.
How to create and use a direct ByteBuffer
To create a direct buffer from Java, simply call the following:
ByteBuffer directBuf = ByteBuffer.allocateDirect(noBytes);
Then the ByteBuffer returned can be used essentially like any other
byte buffer. For example, all of the various get() and put methods
will work, as well as methods to create views of the buffer. One thing you can't
do, at least in Hotspot, is call array()— there's no Java array underlying a direct buffer,
but simply a "raw" section of memory. (Though strictly, according to the Javadoc, implementations are
actually free to implement a direct buffer with a backing array if they can find a way
to do so...)
How do you deallocate/destroy a direct buffer?
There's actually no explicit method you can call from Java to destroy or deallocate
a direct buffer. When you allocate a direct buffer, the VM effectively registers
a "cleanup" method with the garbage collector which will should be called at some point
once the ByteBuffer
object itself is no longer reachable (informally, when it is "ready to be garbage collected")1,
in order to deallocate the underlying memory that was reserved for that buffer.
Usually, this "automatic" deallocation works well enough. But it's important to
bear in mind that there's no immediate link between the last time you access a direct
buffer and the point at which the memory is actually deallocated.
Limits on direct buffers
When you allocate a direct buffer, you are generally impinging on whatever other parts
of the process might have used malloc() to allocate memory, notably memory allocated
from any native libraries used by your program.
Under some circumstances, you may wish to limit the amount of memory space that your
Java application can use to allocate direct buffers. To do so, set the
sun.nio.MaxDirectMemorySize property to the required limit in bytes when you start the VM.
Attempting to allocate over this limit will safely throw an OutOfMemoryError.
Thus, you can ensure that your Java application fails "gracefully" if it tries to allocate
too much memory for direct buffers, rather than having a knock-on effect on other native
code that may not be able to fail quite so gracefully if a malloc() fails (though
of course, any native code you write should always try to fail gracefully if
When to use a direct buffer?
In general, direct buffers are best suited to cases where:
- you're creating a relatively restricted number of buffers that will be
- performance is crucial, and you're reasonably sure that using a direct
buffer will have a performance gain (typical cases are where you use the
buffer for I/O).
1. See the sun.misc.Cleaner class for more details
of the mechanism.