Tuesday, December 20, 2016

Day 20, Java Holiday Calendar 2016, Breakout of the Java Heap

Day 20, Java Holiday Calendar 2016, Breakout of the Java Heap




Today's tip is about storing things off heap. As we all know, Java will occasionally clean up the heap (i.e. invoke its Garbage Collector or GC for short) and remove objects that are no longer used. As the heap grows, so will the time it takes to clean it up. Eventually, the GC will take seconds or minutes and we have hit "the GC wall". Historically, this was a problem for heaps above some 10 GB of data but nowadays we can have larger heaps. How big depends on a vast number of factors.

One way of reducing GC impact is to store data off heap where the GC is not even looking. This way, we can grow to any data size without caring about the GC. The drawback is that we have to manage our memory manually and also provide a means of converting data back and forth between the two memory regions. In the general case, this is a bit tricky but if we limit ourselves to the primitive types like int, long, double and the likes, it is fairly easy.

Consider the following OffHeapIntArray:

public final class OffHeapIntArray implements Iterable<Integer> {

    private final IntBuffer buffer;
    private final int length;

    public OffHeapIntArray(int length) {
        if (length < 0) {
            throw new IllegalArgumentException();
        }
        this.length = length;
        /* Allocates memory off heap */
        this.buffer = ByteBuffer.allocateDirect(length * Integer.BYTES)
            .asIntBuffer();
    }

    public int get(int index) {
        return buffer.get(index);
    }

    public void set(int index, int value) {
        buffer.put(index, value);
    }

    public int length() {
        return length;
    }

    @Override
    public PrimitiveIterator.OfInt iterator() {
        return new Iter();
    }

    @Override
    public void forEach(Consumer<? super Integer> action) {
        for (int i = 0; i < length; i++) {
            action.accept(i);
        }
    }

    @Override
    public Spliterator.OfInt spliterator() {
        return Spliterators.spliterator(iterator(), length,
            Spliterator.SIZED
            | Spliterator.SUBSIZED
            | Spliterator.NONNULL
            | Spliterator.CONCURRENT
        );
    }

    public IntStream stream() {
        return StreamSupport.intStream(spliterator(), false);
    }

    private final class Iter implements PrimitiveIterator.OfInt {

        private int currentIndex;

        public Iter() {
            currentIndex = 0;
        }

        @Override
        public int nextInt() {
            if (hasNext()) {
                return get(currentIndex++);
            }
            throw new NoSuchElementException();
        }

        @Override
        public void forEachRemaining(IntConsumer action) {
            while (currentIndex < length) {
                action.accept(get(currentIndex++));
            }
        }

        @Override
        public boolean hasNext() {
            return currentIndex < length;
        }

        @Override
        public Integer next() {
            return nextInt();
        }

    }

}

As can be seen, it stores all the int elements off heap and allows us to do a number of things like this:

    public static final void main(String... args) {
        final OffHeapIntArray array = new OffHeapIntArray(10);
        array.set(0, 100);
        array.set(1, 101);
        array.set(9, 109);

        System.out.println("** Iterate **");
        for (int val : array) {
            System.out.println(val);
        }

        System.out.println("** Stream **");
        array.stream()
            .filter(i -> i > 0)
            .forEach(System.out::println);

    }

This will produce the following output:

** Iterate **
100
101
0
0
0
0
0
0
0
109
** Stream **
100
101
109

It is possible to create more advanced off heap classes like OffHeapMap, OffHeapArrayList etc. that store all data off heap in a similar way. Speedment Enterprise is using a more advanced version of this technology to be able to store large databases as in-JVM-memory objects. In fact, it is possible to store more data than the available RAM since byte buffers can be mapped onto files. This opens up the path to mammoth JVMs with terabytes of data available at ultra-low latency.


Follow the Java Holiday Calendar 2016 with small tips and tricks all the way through the winter holiday season. I am contributing to open-source Speedment, a stream based ORM tool and runtime. Please check it out on GitHub.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.