The "serialization using ByteBuffers" should be clarified that it is "serialization using direct ByteBuffers" -- as in they exist in the native OS memory space and not inside the JVM's heap.
Direct ByteBuffers are excellent when you can make use of a long-lived, fixed-size buffer that you are using to communicate with a native resource (e.g. socket, file, etc.)
My own experience with using direct ByteBuffers is allocating read/write buffers to a running Redis process that I use to write commends to the server and read the results back. The difference in performance using a direct buffer instead of raw byte[] (basically a standard ByteBuffer) were astounding.
I have seen people argue against the use of direct buffers pointing out that at some point your calls and payload have to cross the JVM-native barrier and using a direct ByteBuffer simply moves the point of entry/exit which won't change the performance of the entire round-trip.
I can't argue with that, but I would point out that in my own work with Redis, having a native process input and output data to and from a native OS buffer that I can then pull into the JVM gave me at least an order of magnitude improvement in speed than sticking with raw byte[] in and out over a socket.
ASIDE: I attribute my success here to the fact that I was able to queue up out-bound commands as fast as possible inside the JVM, pushing them out into the native buffer space which streamed them into Redis; reading back the replies as quickly as possible in a separate thread. My understanding is that by moving the "blood-brain-barrier" to this point, I am allowing Redis to consume and produce as fast as possible as long as I keep the input buffer full and output buffer relatively empty. In other words Redis wasn't being blocked (for the most part) by waiting on me to push and pull data in and out of my running JVM on every single read/write.
ADDENDUM: Just had a fun impl thought for anyone that read this and thought it was interesting... a custom InputStream and OutputStream impl along the lines of the JDK's Buffered streams, but the input and output streams are actually backed by direct ByteBuffers.
The use-cases for the stream would need to be very specific and the underlying approach clearly spelled out in the Javadoc, but it would provide a nice bridge between standard JDK stream-based I/O and the NIO work without burdening the caller with knowing about how to use the NIO APIs.
For anyone interesting, I'll likely add a first-pass impl of this to the Universal Binary JSON Java libs[1] later today to compliment the re-usable ByteArray stream impls that are there already.
Be careful. The JVM will never unmap the underlying regions it uses to support direct ByteBuffers, so if you end up creating a lot of them (such as for reading/writing files) or even just resizing them (which creates an entirely new one), you can run out of virtual memory.
This is a myth. Or more precisely, a mmaped region no longer in use will be unmapped whenever the JVM feels like it (which can, of course, be never). Try running the loop:
long size = 0;
while (size >= 0) {
size += Files.map(veryBigFile).capacity();
}
You will see that VSIZE grows to be very big, but will decrease in size whenever the JVM runs the finalizers for the mmaped regions. Adding System.gc() to the loop makes the VSIZE stay constantly low on my machine, proving that at least my JVM (build 1.6.0_29-b11-402-11D50b on a mac) will unmap the underlying regions (JVM is allowed to ignore System.gc()).
That's not exactly what Netty does. It does allocate large direct buffers and slices up pieces, but the pieces are not pooled because they are never returned to Netty. For this reason they also don't need to implement malloc in Java. All the slices reference the "parent" buffer, and once they are all collected, the parent can be collected as well. When there is no more room in the buffer, another one is allocated. (I just read that code last week because I wanted to know what was going on precisely because there was no way of returning a sliced buffer to the pool).
It is very easy (and efficient) to implement java.io input and output streams backed by a buffer (as long as the buffer doesn't have to grow).
Direct ByteBuffers are excellent when you can make use of a long-lived, fixed-size buffer that you are using to communicate with a native resource (e.g. socket, file, etc.)
My own experience with using direct ByteBuffers is allocating read/write buffers to a running Redis process that I use to write commends to the server and read the results back. The difference in performance using a direct buffer instead of raw byte[] (basically a standard ByteBuffer) were astounding.
I have seen people argue against the use of direct buffers pointing out that at some point your calls and payload have to cross the JVM-native barrier and using a direct ByteBuffer simply moves the point of entry/exit which won't change the performance of the entire round-trip.
I can't argue with that, but I would point out that in my own work with Redis, having a native process input and output data to and from a native OS buffer that I can then pull into the JVM gave me at least an order of magnitude improvement in speed than sticking with raw byte[] in and out over a socket.
ASIDE: I attribute my success here to the fact that I was able to queue up out-bound commands as fast as possible inside the JVM, pushing them out into the native buffer space which streamed them into Redis; reading back the replies as quickly as possible in a separate thread. My understanding is that by moving the "blood-brain-barrier" to this point, I am allowing Redis to consume and produce as fast as possible as long as I keep the input buffer full and output buffer relatively empty. In other words Redis wasn't being blocked (for the most part) by waiting on me to push and pull data in and out of my running JVM on every single read/write.
ADDENDUM: Just had a fun impl thought for anyone that read this and thought it was interesting... a custom InputStream and OutputStream impl along the lines of the JDK's Buffered streams, but the input and output streams are actually backed by direct ByteBuffers.
The use-cases for the stream would need to be very specific and the underlying approach clearly spelled out in the Javadoc, but it would provide a nice bridge between standard JDK stream-based I/O and the NIO work without burdening the caller with knowing about how to use the NIO APIs.
For anyone interesting, I'll likely add a first-pass impl of this to the Universal Binary JSON Java libs[1] later today to compliment the re-usable ByteArray stream impls that are there already.
[1] https://github.com/thebuzzmedia/universal-binary-json-java