Skip to main content

Documentation 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.

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-heap 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 NIO ByteBuffer 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.
Always use MemoryStack inside a try-with-resources block. The AutoCloseable.close() implementation calls pop(), which restores the previous stack frame automatically.
import org.lwjgl.system.MemoryStack;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.glfw.GLFW.*;

// Query window size without a heap allocation
try (MemoryStack stack = stackPush()) {
    IntBuffer width  = stack.mallocInt(1);
    IntBuffer height = stack.mallocInt(1);
    glfwGetWindowSize(window, width, height);
    System.out.println(width.get(0) + "x" + height.get(0));
}
// width and height are invalid after this point — the stack frame is gone

Common MemoryStack methods

MethodDescription
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
The ints()/floats() convenience variants allocate a buffer and write the provided values in one step:
try (MemoryStack stack = stackPush()) {
    // Allocate a single int pre-filled with the value 1
    IntBuffer pPresent = stack.ints(1);
    vkQueuePresentKHR(presentQueue, presentInfo.pWaitSemaphores(pPresent));
}
Never let a stack-allocated buffer escape the try block. The memory address becomes invalid as soon as the stack frame is popped. Storing it in a field or returning it from a method will cause silent data corruption or a crash.

Nesting stack frames

MemoryStack supports nested frames. Each stackPush() creates a new frame that is independent of the surrounding one:
try (MemoryStack outer = stackPush()) {
    ByteBuffer headerBuf = outer.malloc(16);

    try (MemoryStack inner = stackPush()) {
        IntBuffer temp = inner.mallocInt(4);
        // temp is released when inner closes; headerBuf is still valid
    }

    // headerBuf is still valid here
}

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.
memAlloc and its typed variants do not zero-initialize memory. Use memCalloc / memCallocInt etc. when you need zeroed buffers.
import static org.lwjgl.system.MemoryUtil.*;

ByteBuffer buffer = memAlloc(1024);
try {
    // fill and use buffer
    buffer.putInt(42).flip();
    uploadData(buffer);
} finally {
    memFree(buffer);  // always free, even if an exception is thrown
}

Typed allocation helpers

MemoryUtil provides typed variants so you do not need to manually cast byte sizes:
IntBuffer    ints   = memAllocInt(256);     // 256 ints  = 1024 bytes
FloatBuffer  floats = memAllocFloat(16);    // 16 floats = 64 bytes
LongBuffer   longs  = memAllocLong(8);      // 8 longs   = 64 bytes
DoubleBuffer dbl    = memAllocDouble(4);    // 4 doubles = 32 bytes
PointerBuffer ptrs  = memAllocPointer(32);  // 32 pointer-sized slots

// Always pair with memFree:
memFree(ints);
memFree(floats);
The zeroing counterparts are memCallocInt, memCallocFloat, memCallocLong, etc.

Resizing allocations

ByteBuffer buf = memAlloc(64);
// ... fill buf ...

// Grow the buffer; the old reference must not be used after this call
buf = memRealloc(buf, 128);

memFree(buf);

Text encoding helpers

MemoryUtil also handles string encoding without intermediate Java heap allocations:
// Encode a Java string as a null-terminated UTF-8 ByteBuffer
// The returned buffer must be freed with memFree
ByteBuffer encoded = memUTF8(myString);
try {
    nativeFunction(encoded);
} finally {
    memFree(encoded);
}

// Or encode onto the stack (no manual free needed)
try (MemoryStack stack = stackPush()) {
    ByteBuffer encoded = stack.UTF8(myString);
    nativeFunction(encoded);
}

Detecting memory leaks

Enable the debug allocator to track every allocation and report leaks at JVM exit:
java -Dorg.lwjgl.util.DebugAllocator=true -jar myapp.jar
When enabled, every 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 of Struct) can be allocated directly on a MemoryStack:
try (MemoryStack stack = stackPush()) {
    VkExtent2D extent = VkExtent2D.malloc(stack);
    extent.width(800).height(600);

    VkViewport.Buffer viewports = VkViewport.malloc(4, stack); // 4-element struct array
    for (int i = 0; i < 4; i++) {
        viewports.get(i).set(0f, 0f, 200f, 600f, 0f, 1f);
    }
}
This avoids a native malloc round-trip entirely. The struct memory lives in the same stack arena as any other allocation in the frame.

Build docs developers (and LLMs) love