4 ms·
One of the little known cool and new things here is the manual memory management APIs they've added. They give you a lot of upgrades over malloc/free, but with
by nvm0n2 3y ago
One of the little known cool and new things here is the manual memory management APIs they've added. They give you a lot of upgrades over malloc/free, but with good performance:
- Bounds checks, we know about those. The JITC can move them around to reduce their costs.
- Temporal safety is new. It means you can't have crashes or read bad data due to use-after-free bugs, even though deallocation/unmapping is explicit and not GC controlled. It's efficient (see below).
- Thread safety. You can allocate memory that can't be shared between threads (confined segments).
- You can create customized memory allocators (arenas)
- You can allocate memory and give it to another piece of code, without that code being able to free it. This lets you communicate memory lifetimes via the type system.
- You can wrap memory allocated by external libraries to give these allocations these same abilities.
This isn't a set of compile-time guarantees like what Rust gives you, it's still Java, but it does provide the usual guarantees Java developers expect, like buggy code can't crash the VM.
It's also cheaper than you'd expect at runtime. The JIT compiler understands how to eliminate redundant bounds/free checks, so code that looks like it'd do a lot of bounds/free checking will only really do it once.
You could wonder how that works in a multithreaded language. What stops you allocating some memory, starting to work with it, the thread checks the memory isn't closed once and then does a lot of operations, but whilst it's doing that, another thread closes it? There's a TOCTOU problem. Their solution is to use the thread-local handshake mechanism built into the JVM, which lets threads send "messages" to each other even if the user's code isn't polling anything for a message. This is built on the safepoint mechanism, which is itself a highly optimized and efficient per-thread check. If a multi-threaded segment is closed then each thread that is accessing it is brought to a safepoint and then de-optimized (forced back to the interpreter from compiled code), at which point the flag will be checked on the next access and throw an exception.
In this way bounds and freed-flag checks can appear as if they are done on every single access, whilst still having high performance, and use-after-free bugs will always yield a safe exception that can be logged, reporting, doesn't corrupt memory etc.