The basic idea is to reuse the runtime system mechanisms we use for lazy compilation.
When a compiled method is obsolete, we modify it's prologue so that callers get redirected to a runtime service routine (VM_DynamicBridge) that patches the callsite to target the current version of the compiled method and the resumes execution.
The actual patching mechanisms is a little touchy. On IA32, we need to lay down 5 bytes for a call instruction, which we can't do atomically. So instead we need to begin every piece of compiled code with the call to the runtime service routine we need as a pre-prologue that is normally not reachable. When the method becomes obsolete, we lay down a 2 byte backwards jump instruction to the call instruction at byte 6 (the "normal" entry point to the method). On PPC, it's simpler in that the call instruction will fit in a word (although we have fewer displacement bits, so we may have to actually do a pc-relative call to a stub, that in turn does a JTOC-based indirect call to reliably reach the runtime service routine). The dcache/icache story on PPC is touchy, but I believe ok because we don't have to ensure that every CPU sees the patch...as threads execute old versions of the patched code, they will perform the identical (or semantically equivalent) patching operation.
An alternative to this lazy approach would be to eagerly patch all callers of a compiled method as part of replacing the compiled method. I believe that the amount of meta-data one would need to keep to make this tractable (ie, not cost O(all compiled code) which is what it would take with the current machinecode maps) is prohibitive.
There's an intermediate point where we mostly rely on lazy mechanisms, but at infrequent intervals (ie, when collecting the code space), we do a scan & fixup of all pc-relative call instructions to clear out dangling references to patched & obsolete code we want to reclaim.