When an nmethod is loaded, we call the nmethod::post_compiled_method_load_event() function. If we are running with events on, we calculate some jmethod_id, and take the safepoint checking JmethodIdCreation_lock in the process. After that, we may wake up N safepoints later, and the nmethod* may point at arbitrary freed memory in the code cache. Then we store the jmethod ID to what we think is a live nmethod, but it could be toast by now. Then we poke at its lock counter and enqueue some deferred event to the service thread. But again, the nmethod could be toast by now. This could cause random memory corruption of the code cache (as newly compiled nmethods could be placed over the memory where the JVMTI code is poking around).
The solution seems to be to just make that lock not check for safepoints.