Open Source



When dealing with memory and buffers, you may at some point need to pass them through the Native/JVM interface efficiently by reference, avoiding copies especially as their size grows.

Knee provides a built-in solution for this problem based on java.nio direct buffers and their native counterparts defined by the knee-runtime package. You can use:

  • A direct java.nio.ByteBuffer on the JVM and on native;
  • A direct java.nio.DoubleBuffer on the JVM and on native;
  • A direct java.nio.FloatBuffer on the JVM and on native;
  • A direct java.nio.IntBuffer on the JVM and on native;
  • A direct java.nio.LongBuffer on the JVM and on native.

Whenever such buffers are used as parameters or return types for Knee functions, the runtime will convert between the two.

Memory leaks

Native buffers are just thin wrappers around a CPointer. Should you choose to allocate such buffers on the native side (see examples), you must free them after use, using


Natively allocated buffers are usable on the JVM only until the call

This requirement is lifted when buffers are allocated on the JVM side and passed down. In this case, buffers will keep a strong reference to the java.nio.ByteBuffer, so memory will be reclaimed only when all buffers (on both sides!) go out of scope and are garbage collected.


Allocate on JVM, pass down
kotlin logokotlin
// JVM val buffer = java.nio.ByteBuffer.allocateDirect(1024) fillBuffer(buffer) // Native @Knee fun fillBuffer(buffer: { check(buffer.size == 1024) val rawPointer: CArrayPointer<ByteVar> = buffer.ptr // Fill rawPointer... }
Allocate natively, pass up
kotlin logokotlin
// JVM useBuffer(1024) { buffer: java.nio.ByteBuffer -> check(buffer.capacity == 1024) // Use it... } // Native @Knee fun useBuffer(size: Int, block: ( -> Unit) { val environment = currentJavaVirtualMachine.env!! val buffer =, size) try { block(buffer) } finally { } }