Java Nio HeapByteBuffer Example
This example demonstrates the usage of the Java Nio HeapByteBuffer
. The Java Nio HeapByteBuffer
is an odd class, one you will never reference directly and for good reason, it’s package private. Although it’s use is almost guaranteed when working with ByteBuffers unless you opt for a DirectByteBuffer
(off heap). By virtue of extending ByteBuffer, it also happens to extend Buffer and implement Comparable.
1. Introduction
A Java Nio HeapByteBuffer
is created via calling the following methods on the ByteBuffer class:
allocate(int)
wrap(byte[], int, int)
wrap(byte[])
All of the get(...)
and put(...)
methods defined on the super abstract class ByteBuffer return the current implementation owing to the fluent API design. If the actual implementation have been a HeapByteBuffer
then this will surely be returned.
HeapByteBuffer
further specializes into HeapByteBufferR
which is a read only implementation for a HeapByteBuffer
disallowing mutations and ensuring that “views” of this type of ByteBuffer only return an instance of HeapByteBufferR
and not the superclass, thus protecting the invariant. A HeapByteBufferR
instance is created by calling asReadOnlyBuffer()
on an instance of HeapByteBuffer
.
2. Technologies used
The example code in this article was built and run using:
- Java 1.8.101 (1.8.x will do fine)
- Maven 3.3.9 (3.3.x will do fine)
- Spring source tool suite 4.6.3 (Any Java IDE would work)
- Ubuntu 16.04 (Windows, Mac or Linux will do fine)
3. Overview
This article builds upon a previous article on ByteBuffers in general and I would recommend to read that article first before continuing with this one. HeapByteBuffers
are simply the default implementation provided when creating a ByteBuffer via the 3 methods listed in the introduction.
One way to ensure you are working with a a HeapByteBuffer
implementation is to call the isDirect()
method which will return false
for HeapByteBuffer
and HeapByteBufferR
implementations.
HeapByteBuffer
instances are, barring escape analysis, always allocated on the Java heap and thus are wonderfully managed by the GC. DirectByteBuffers
are not and thus are not easily quantifiable in terms of space.
The reasons for the differentiation stem from the fact that the Operating system can optimize IO operations on DirectByteBuffers
because the bytes are guaranteed to be physically contiguous, whereas heap memory is at the whim of the GC and thus means that HeapByteBuffer
bytes may not necessarily be contiguous.
4. Test cases
Testing a HeapByteBuffer identity
public class HeapByteBufferIdentityTest { @Test public void isHeapBuffer() { final ByteBuffer heapBuffer = ByteBuffer.allocate(5 * 10000); final ByteBuffer directBuffer = ByteBuffer.allocateDirect(5 * 10000); assertTrue("Must be direct", directBuffer.isDirect()); assertTrue("Must not be direct", !heapBuffer.isDirect()); } @Test public void persistenIdentityChecker() { final ByteBuffer buffer = ByteBuffer.allocate(5 * 10000); check(buffer, (a) -> !a.duplicate().isDirect()); check(buffer, (a) -> !a.slice().isDirect()); check(buffer, (a) -> !a.put("I am going to trick this buffer".getBytes()).isDirect()); check(buffer, (a) -> !a.asIntBuffer().isDirect()); check(buffer, (a) -> !a.asCharBuffer().isDirect()); check(buffer, (a) -> !a.asFloatBuffer().isDirect()); } private void check(final ByteBuffer buffer, final Predicate<? super ByteBuffer> action) { assertTrue(action.test(buffer)); } }
- line 24-26: we accept a predicate which asserts the fact that the
HeapByteBuffer
stays aHeapByteBuffer
regardless of the operation we performed on it.
Testing a HeapByteBuffer memory vs DirectByteBuffer
public class HeapByteBufferMemoryTest { private static final Runtime RUNTIME = Runtime.getRuntime(); private ByteBuffer heapBuffer; private ByteBuffer directBuffer; private long startFreeMemory; @Before public void setUp() { this.startFreeMemory = RUNTIME.freeMemory(); } @Test public void memoryUsage() { this.heapBuffer = ByteBuffer.allocate(5 * 100000); long afterHeapAllocation = RUNTIME.freeMemory(); assertTrue("Heap memory did not increase", afterHeapAllocation > this.startFreeMemory); this.directBuffer = ByteBuffer.allocateDirect(5 * 100000); assertTrue("Heap memory must not increase", RUNTIME.freeMemory() >= afterHeapAllocation); } }
- In this test case we attempt to prove that allocating a
HeapByteBuffer
will have an impact on heap memory usage whereas the allocation of aDirectByteBuffer
will not.
Testing the read only properties of a HeapByteBuffer
public class HeapByteBufferReadOnlyTest { private ByteBuffer buffer; @Before public void setUp() { this.buffer = ByteBuffer.allocate(5 * 10000); } @Test(expected = ReadOnlyBufferException.class) public void readOnly() { this.buffer = this.buffer.asReadOnlyBuffer(); assertTrue("Must be readOnly", this.buffer.isReadOnly()); this.buffer.putChar('b'); } @Test(expected = ReadOnlyBufferException.class) public void readOnlyWithManners() { this.buffer = this.buffer.asReadOnlyBuffer(); assertTrue("Must be readOnly", this.buffer.isReadOnly()); this.buffer.put("Please put this in the buffer".getBytes()); } @Test public void persistenReadOnlyChecker() { this.buffer = buffer.asReadOnlyBuffer(); check(this.buffer, (a) -> a.duplicate().put("I am going to trick this buffer".getBytes())); check(this.buffer, (a) -> a.slice().put("I am going to trick this buffer".getBytes())); check(this.buffer, (a) -> a.put("I am going to trick this buffer".getBytes())); check(this.buffer, (a) -> a.asIntBuffer().put(1)); check(this.buffer, (a) -> a.asCharBuffer().put('c')); check(this.buffer, (a) -> a.asFloatBuffer().put(1f)); } private void check(final ByteBuffer buffer, final Consumer>? super ByteBuffer< action) { try { action.accept(buffer); fail("Must throw exception"); } catch (ReadOnlyBufferException e) { } } }
- lines 35-41: we invoke a function that should throw a
ReadOnlyBufferException
proving the property of read only despite creating a different view of theHeapByteBuffer
through it’s api. Various methods are called ranging fromput(...)
toduplicate()
,sice()
etc. to try and break this property
Cloning a HeapByteBuffer into a DirectByteBuffer
public class HeapByteBufferIdentityCrisisTest { @Test public void shapeShift() { final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes()); assertTrue("Not a heap buffer", !buffer.isDirect()); final ByteBuffer shapeShifter = ByteBuffer.allocateDirect(buffer.capacity()).put(getBytes(buffer)); assertTrue("Not a direct buffer", shapeShifter.isDirect()); shapeShifter.flip(); byte [] contents = new byte[shapeShifter.capacity()]; shapeShifter.get(contents); assertEquals("Invalid text", "Hello world!", new String(contents).trim()); } private byte [] getBytes(final ByteBuffer buffer) { byte [] dest = new byte[buffer.remaining()]; buffer.get(dest); return dest; } }
- lines 18-22 we copy the bytes (remaining) between
position
andlimit
into a destinationbyte []
. This is to ensure that the content of the originalHeapByteBuffer
are exumed and made ready for insertion into a comingDirectByteBuffer
- I did not concern myself with encoding and decoding explicitness in this test. I refer you to a previous article in this series which does those test cases explicit in the Charset usage during encoding and decoding.
5. Summary
This example demonstrated the creation and usage of a HeapByteBuffer
implementation of a ByteBuffer. It proved properties of the API and attempted to demonstrate the core difference in implementation between this and a DirectByteBuffer
in terms of memory allocation.
6. Download the source code
This was a Java Nio HeapByteBuffer example.
You can download the full source code of this example here: Java Nio HeapByteBuffer example