LWJGL 3 calls native APIs that operate directly on memory outside the Java heap. To pass data to those APIs—window sizes, vertex arrays, uniform buffers—you need off-heapDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/LWJGL/lwjgl3/llms.txt
Use this file to discover all available pages before exploring further.
ByteBuffer instances that the JVM’s garbage collector cannot move or reclaim unpredictably. LWJGL provides two complementary systems for this: MemoryStack for short-lived, frame-scoped allocations, and MemoryUtil for longer-lived heap allocations that you manage manually.
Why off-heap memory?
Java’s normal heap objects cannot be passed to native code because the GC may relocate them at any time. Native functions expect stable memory addresses. Java NIOByteBuffer objects created with ByteBuffer.allocateDirect() are pinned outside the heap, but creating them is slow and they are managed by GC finalizers with unpredictable timing. LWJGL’s allocators give you deterministic, high-performance alternatives.
MemoryStack — frame-scoped allocations
MemoryStack is a thread-local off-heap stack with a default size of 64 KB (configurable via Configuration.STACK_SIZE). Each call to stackPush() saves the current stack pointer; when the stack frame closes, the pointer resets and all memory in that frame is effectively freed—no GC, no free() call.
Common MemoryStack methods
| Method | Description |
|---|---|
stack.mallocInt(n) | Allocate n ints, uninitialized |
stack.callocInt(n) | Allocate n ints, zeroed |
stack.mallocFloat(n) | Allocate n floats, uninitialized |
stack.ints(v1, v2, ...) | Allocate and pre-fill an int buffer |
stack.floats(v1, v2, ...) | Allocate and pre-fill a float buffer |
stack.malloc(n) | Allocate n raw bytes |
stack.calloc(n) | Allocate n raw bytes, zeroed |
ints()/floats() convenience variants allocate a buffer and write the provided values in one step:
Nesting stack frames
MemoryStack supports nested frames. Each stackPush() creates a new frame that is independent of the surrounding one:
MemoryUtil — explicit heap allocations
MemoryUtil provides malloc/free-style allocation that survives beyond a single method call. Memory is allocated from the system allocator (or jemalloc/rpmalloc if configured) and must be freed manually. Failing to call memFree() causes a native memory leak.
Typed allocation helpers
MemoryUtil provides typed variants so you do not need to manually cast byte sizes:
memCallocInt, memCallocFloat, memCallocLong, etc.
Resizing allocations
Text encoding helpers
MemoryUtil also handles string encoding without intermediate Java heap allocations:
Detecting memory leaks
Enable the debug allocator to track every allocation and report leaks at JVM exit:- System property
- Programmatic
memAlloc call records a stack trace. At JVM exit, any allocation that was not matched by a memFree is printed with its originating stack trace, making leaks easy to locate.
For a lighter-weight option that still detects leaks without the per-allocation stack trace overhead, enable Configuration.DEBUG_MEMORY_ALLOCATOR_FAST. You can toggle this on and off at runtime to focus on suspect code sections.
Choosing between MemoryStack and MemoryUtil
Use MemoryStack when...
The buffer is only needed for the duration of a single method or block. Examples: passing output pointers to GLFW queries, encoding temporary strings, building a small struct to pass to Vulkan.
Use MemoryUtil when...
The buffer must outlive the current stack frame. Examples: persistent vertex buffers, audio sample data uploaded on a background thread, any allocation whose lifetime is tied to an object rather than a call.
Struct buffers on the stack
Structs generated by LWJGL (all subclasses ofStruct) can be allocated directly on a MemoryStack:
malloc round-trip entirely. The struct memory lives in the same stack arena as any other allocation in the frame.