In order to buy some performance on the common, uninstrumented, fast path, we replace repeated checks for both allocation instrumentation and allocator changes by a single function table dispatch, and templatized allocation code that can be used to generate either instrumented or uninstrumented versions of allocation routines.

When we call an allocation routine, we always indirect through a thread-local function table that either points to instrumented or uninstrumented allocation routines. The instrumented code has a kInstrumented = true template argument (or kIsInstrumented in some places), the uninstrumented code has kInstrumented = false.

The function table is thread-local. There appears to be no logical necessity for that; it just makes it easier to access from compiled Java code.

  • The function table is switched out by InstrumentQuickAllocEntryPoints[Locked], and a corresponding UninstrumentQuickAlloc... function.

  • These in turn are called by SetStatsEnabled(), SetAllocationListener(), et al, which require the mutator lock is not held.

  • With a started runtime, SetEntrypointsInstrumented() calls ScopedSupendAll() before updating the function table.

Mutual exclusion in the dispatch table is thus ensured by the fact that it is only updated while all other threads are suspended, and is only accessed with the mutator lock logically held, which inhibits suspension.

To ensure correctness, we thus must:

  1. Suspend all threads when swapping out the dispatch table, and
  2. Make sure that we hold the mutator lock when accessing it.
  3. Not trust kInstrumented once we've given up the mutator lock, since it could have changed in the interim.