Making Unsafe safer
OverviewIf you use Unsafe directly, you risk crashing the JVM. This happens when you access a page of memory which hasn't been mapped and the result on Unix is a SIGSEG (if you access page 0) or SIGBUS (if you access another page which is not mapped)
Using MethodHandlesWrapping Unsafe method with a MethodHandle is a possible solution. You can add code to Method Handles to check for a zero page access. e.g. unsigned_ptr < 4096. The reason you would add this to MethodHandle is it makes it easier to optimise away this check.
Downside of this is that
- You have to use MethodHandles which complicates the syntax and obscures what you are really doing.
- It doesn't work if you don't
- It doesn't cover bus errors, nor could it as the mapping for the whole application is complex, and can change in any thread at any time.
- Optimising away the bounds check requires some work to the optimiser which is yet to be proven.
If only there was some way to do this in the hardware already, and there is. The CPU already checks if a page you attempt to access is valid and it throws an interrupt if the page is not in cache. This interrupt is turned into a signal if the OS cannot find/create a mapping for the this cache miss.
If only there was a signal handler in the JVM already, and there is, this is what produces the crash report.
If only there was some way for an interrupt handler to trigger an error or exception back to the code which triggered it. Like Thread.currentThread().stop(e); (You get the idea)
- No additional work is required to perform the checking as it is already done by the CPU.
- Minimal changes to the optimiser (if any)
- Potentially work for signals produced from a range of source.
- Using signals are a mature/old tech way of trapping runtime errors which pre-date Java.
- Single processing is likely to be a stop-the-world operation (no way to benchmark this in Java currently)
- Even if it is not, it is likely to be much more expensive when an error is triggered.
- You would have to change the signal handler which traditionally hasn't been changed. i.e. there is much more experience of changing the optimiser.
Possible exceptions thrown.
New exceptions could be thrown, however I suggest re-using existing exceptions.
Access to page 0 - NullPointerException
Accesses to page 0 (not just access of a NULL pointer) trigger a SIGSEG. NPE is named after the access of a NULL pointer from C and it is perhaps more obvious to have a NPE for an access to a NULL pointer than a reference. i.e. it could have been called NullReferenceException since Java doesn't have pointers.
Invalid Access - IndexOutOfBoundsException
Other candidates include BufferUnderflowException (if you a a page short of a mapped region), BufferOverflowException (if you are a page long of a mapped region)
Something these all have in common is they are RuntimeException(s). If a custom, more descriptive exception is raised, a RuntimeException might be consistent with existing throwables thrown.
A common trick to maximise performance is; don't write in Java something your system is doing for you already. In Chronicle we use the OS to do the asynchronous persistence to disk and it is more efficient and reliable than writing the same again in Java. Similarly, trapping and handling invalid memory access would be more efficient and more robust if the facilities provided by the CPU and OS were re-used.
Generally speaking you would re-write OS features when each OS does things differently to support cross platform compatibility, but only a minimum required to do this. This is why Java doesn't have a thread scheduler and in relativity has little control over how threads are run.
Virtual memory handling is so old and standard, that major platforms all work basically the same way.