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
HeapByteBufferstays aHeapByteBufferregardless 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
HeapByteBufferwill have an impact on heap memory usage whereas the allocation of aDirectByteBufferwill 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
ReadOnlyBufferExceptionproving the property of read only despite creating a different view of theHeapByteBufferthrough 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
positionandlimitinto a destinationbyte []. This is to ensure that the content of the originalHeapByteBufferare 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
