Regarding that bit of code... it does and doesn't help. To my mind I'm using the API in the correct way: GLFWImage.Buffer implements Autoclosable, therefore, it must be usable in a try-with-resources block. If this is not the case, it must not implement Autoclosable. Even if this is explicitly stated in the Javadoc, it still fails because code itself can't read Javadocs and does what it is supposed to do when confronted by interfaces with a specific contract. The API as it stands (and anywhere else this may be occurring) is simply asking for big trouble, and looks like it's found it too.
Even so - there should be actual runtime exception checks that are always on for management of this sort of thing in LWJGL I think. Correctness IMO always trumps performance. If I were worried about pure performance, I'd be using C.
I totally get your point of view and the concerns you raise have been carefully considered... years ago.
The chosen solution for correctness was the DebugAllocator. The exception you were seeing ("The memory address specified is not being tracked") was saying that someone tried to free a pointer that was never allocated by an explicit allocation API. It also triggers on a double-free. It is not enabled by default because of the performance impact. The current implementation uses a global ConcurrentHashMap from Long to Allocation, where Allocation holds the allocation size, thread ID and stacktrace
where the allocation happened. Even with the new stack walking API in Java 9, this is very expensive. Best we could do is make the DebugAllocator implementation configurable and have an alternative implementation (per-thread state, primitive collections, no call stack tracking) that might be more reasonable to have always enabled during development (e.g. enabled-by-default in Debug mode, without a separate switch).
One might think that doing some kind of tracking or detection per buffer/struct would be ideal. First of all, buffers come from the JDK and there's no way to add extra state, so immediately we don't have a general solution, we're left with structs only. Most importantly though, this is virtually an impossible problem to solve: The backing buffer might be sliced/duplicated. A Struct might have come from a StructBuffer. The StructBuffer itself might be sliced/duplicated. We'd have to recurse through the chain, doing instanceof checks and then we'd still need to examine private NIO buffer data. And we'd still not be 100% sure about ownership, we'd have to consider the API used: We just got a pointer from an API, are we responsible for freeing it or will the API take care of it? It becomes too much. And it's not like C does anything better about this, tracking pointer ownership is the developer's responsibility. Rust is the only language with robust ownership tracking, but we can't expect LWJGL to be comparable to Rust, can we?
Anyway, I think getting used to the API more will help. Quick reminder of how the "what to use for allocation" algorithm goes:
1. Is it a short-lived, small-sized allocation? Use the MemoryStack.
* Anything allocated via the MemoryStack must NOT be explicitly freed.
* You manipulate the stack instead, a pop will free anything allocated after the last push. This can be done either automatically in a try-with-resources block, or by calling push/pop explicitly.
* Yes, the MemoryStack is AutoCloseable too and close() delegates to pop().
2. Is it a long-lived and/or big allocation and it has a clearly defined life-cycle? Use the explicit allocation/deallocation API.
* If it's called malloc/calloc/realloc, it's an explicit allocation that must be freed manually.
* This is where you'd use try-with-resources to do the free safely/automatically.
3. Weird life-cycle or too much hassle to track liveness? Use BufferUtils.
* If it starts with "create", it uses ByteBuffer.allocateDirect under the hood. It must NOT be explicitly freed.
* Same semantics as every allocation in an LWJGL 2-based application, the allocation will be freed by the GC.