nio

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 a HeapByteBuffer 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 a DirectByteBuffer 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 the HeapByteBuffer through it’s api. Various methods are called ranging from put(...)to duplicate(), 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 positionand limitinto a destination byte []. This is to ensure that the content of the original HeapByteBuffer are exumed and made ready for insertion into a coming DirectByteBuffer
  • 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.

Download
You can download the full source code of this example here: Java Nio HeapByteBuffer example

JJ

Jean-Jay Vester graduated from the Cape Peninsula University of Technology, Cape Town, in 2001 and has spent most of his career developing Java backend systems for small to large sized companies both sides of the equator. He has an abundance of experience and knowledge in many varied Java frameworks and has also acquired some systems knowledge along the way. Recently he has started developing his JavaScript skill set specifically targeting Angularjs and also bridged that skill to the backend with Nodejs.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button