Search code examples
javajvmjvm-hotspot

What are the slow and fast path in GCLocker?


  1. What are the slow and fast path in GCLocker for JNI critical regions in HotSpot JVM?

  2. What's the difference between these two concepts?

I find code comment from class GCLocker.

  // JNI critical regions are the only participants in this scheme
  // because they are, by spec, well bounded while in a critical region.
  //
  // Each of the following two method is split into a fast path and a
  // slow path. JNICritical_lock is only grabbed in the slow path.
  // _needs_gc is initially false and every java thread will go
  // through the fast path, which simply increments or decrements the
  // current thread's critical count.  When GC happens at a safepoint,
  // GCLocker::is_active() is checked. Since there is no safepoint in
  // the fast path of lock_critical() and unlock_critical(), there is
  // no race condition between the fast path and GC. After _needs_gc
  // is set at a safepoint, every thread will go through the slow path
  // after the safepoint.  Since after a safepoint, each of the
  // following two methods is either entered from the method entry and
  // falls into the slow path, or is resumed from the safepoints in
  // the method, which only exist in the slow path. So when _needs_gc
  // is set, the slow path is always taken, till _needs_gc is cleared.
  static void lock_critical(JavaThread* thread);
  static void unlock_critical(JavaThread* thread);

Solution

  • The answer is in the quote you've cited, so I'm not sure what else you are looking for.

    • The fast path, when _needs_gc == false, simply increments/decrements the counter - Thread::_jni_active_critical.
    • The slow path, when _needs_gc == true, goes through the global lock (the mutex). The mutex is needed to make sure the GC is called once after the last thread leaves the critical region.

    Seems you already have HotSpot sources in front of you, so just take a look at the implementation in gcLocker.inline.hpp:

    inline void GC_locker::lock_critical(JavaThread* thread) {
      if (!thread->in_critical()) {
        if (needs_gc()) {
          // jni_lock call calls enter_critical under the lock so that the
          // global lock count and per thread count are in agreement.
          jni_lock(thread);   <-- SLOW PATH
          return;
        }
        increment_debug_jni_lock_count();
      }
      thread->enter_critical();  <-- FAST PATH
    }
    

    The idea behind splitting into fast/slow paths is to make entering/leaving JNI critical region as fast as possible when GC is not requested. JNI methods will endure the overhead of a critical section only when GC is required.