In-depth OpenJDK source code-how to implement the delayed effect of the bias lock

Regarding the bias lock, including undo and re-bias, we have already analyzed the source code before, so I won't go into details here, and mainly look at the issue of delayed entry into force. In addition, most of the source code posted in this article only retains the logic of current concern.

  We know that the virtual machine provides us with the parameter-XX:+UseBiasedLocking to turn on or off the bias lock optimization (default is on), but the activation of the bias lock has a default delay time, which can be set by the parameter-XX:BiasedLockingStartupDelay, the default is 4 seconds, you can find the default setting in globals.hpp:

  product(intx, BiasedLockingStartupDelay, 4000,"Number of milliseconds to wait before enabling biased locking")

  This article will go to the source code level to see how this delay is implemented. Not much nonsense, let's go straight to the topic~

Source code analysis

  Enter biasedLocking.cpp to see the initialization logic of the biased lock:

void BiasedLocking::init() {
    
    
  if (UseBiasedLocking) {
    
    
  	//启用偏向锁
    if (BiasedLockingStartupDelay > 0) {
    
    
	  //需要延时生效,默认4000ms
      EnableBiasedLockingTask* task = new EnableBiasedLockingTask(BiasedLockingStartupDelay);
      //调用enroll注册任务
      task->enroll();
    } else {
    
    
      //否则立即调用VM_EnableBiasedLocking 开启偏向锁
      VM_EnableBiasedLocking op(false);
      VMThread::execute(&op);
    }
  }
}

  First, UseBiasedLocking is true, indicating that the biased lock is turned on. If BiasedLockingStartupDelay is greater than 0, it means that a delay is required to take effect. Here is an EnableBiasedLockingTask, let’s take a look at the definition of this task:

class EnableBiasedLockingTask : public PeriodicTask {
    
    
 public:
  EnableBiasedLockingTask(size_t interval_time) : PeriodicTask(interval_time) {
    
    }
  //这个方法这时先不管,后面调用的时候再说
  virtual void task() {
    
    
    VM_EnableBiasedLocking *op = new VM_EnableBiasedLocking(true);
    VMThread::execute(op);
  }
};

  It can be seen that EnableBiasedLockingTask is a derived class of PeriodicTask. The parameter constructor of the parent class is called with the interval_time parameter. PeriodicTask is defined in task.hpp, and the constructor is implemented in task.cpp:

PeriodicTask::PeriodicTask(size_t interval_time) :
  _counter(0), _interval((int) interval_time) {
    
    
}

  The interval_time is assigned to the _interval attribute, _interval is a private constant, in milliseconds, and _counter is set to 0:

class PeriodicTask: public CHeapObj<mtInternal> {
    
    
private:
  int _counter;
  const int _interval;
}

  Up to now, an EnableBiasedLockingTask (a derived class of PeriodicTask) has been created. After it is created, the task->enroll() method is called, which is defined in the parent class PeriodicTask:

void enroll();

  Find the implementation of this method in task.cpp:

void PeriodicTask::enroll() {
    
    
  MutexLockerEx ml(PeriodicTask_lock->owned_by_self() ?
                     NULL : PeriodicTask_lock);
  _tasks[_num_tasks++] = this;
  WatcherThread* thread = WatcherThread::watcher_thread();
  if (thread) {
    
    
    thread->unpark();
  } else {
    
    
    WatcherThread::start();
  }
}

  It can be seen that a WatcherThread object is finally used. WatcherThread is a derived class of Thread object. It is defined in thread.hpp. No code will be posted here, otherwise it will digress~ Let’s go directly to the WatcherThread::run() method. Look, just post two lines of code:

void WatcherThread::run() {
    
    
	......
	int time_waited = sleep();
	......
	PeriodicTask::real_time_tick(time_waited);
	......
}

  Then let's take a look at the implementation of PeriodicTask::real_time_tick(time_waited) method, where the core code is:

for(int index = 0; index < _num_tasks; index++) {
    
    
	 _tasks[index]->execute_if_pending(delay_time);
}

  We previously called the PeriodicTask::enroll() method to register the EnableBiasedLockingTask in the _tasks array, here we will traverse the tasks from _tasks, and then respectively call the execute_if_pending method, which is defined in the parent class PeriodicTask:

 void execute_if_pending(int delay_time) {
    
    
    // make sure we don't overflow
    jlong tmp = (jlong) _counter + (jlong) delay_time;
    if (tmp >= (jlong) _interval) {
    
    
      _counter = 0;
      task();
    } else {
    
    
      _counter += delay_time;
    }
  }

  When the judgment reaches the _interval time, the task() method will be called. The task() method is defined in the EnableBiasedLockingTask method, so we go around and return to EnableBiasedLockingTask:

class EnableBiasedLockingTask : public PeriodicTask {
    
    
 public:
  EnableBiasedLockingTask(size_t interval_time) : PeriodicTask(interval_time) {
    
    }

  virtual void task() {
    
    
    //实际上是一个VM_Operation
    VM_EnableBiasedLocking *op = new VM_EnableBiasedLocking(true);
    VMThread::execute(op);
  }
};

  The logic of the task method is relatively simple. First, a VM_EnableBiasedLocking object is generated. It is a derived class of the VM_Operation class, which implements the logic of the doit method, and executes this operation through VMThread::execute (if the configuration does not take effect with a delay, this logic will be executed when the bias lock is initialized), and finally doit( )method:

class VM_EnableBiasedLocking: public VM_Operation {
    
    
 public:
  void doit() {
    
    
    SystemDictionary::classes_do(enable_biased_locking);
    _biased_locking_enabled = true;
  }
};

  The doit method mainly calls the SystemDictionary::classes_do method. We will come back later to see what enale_biased_locking is. Now let’s look at what the SystemDictionary::classes_do method does:

void SystemDictionary::classes_do(void f(Klass*)) {
    
    
  dictionary()->classes_do(f);
}

  It is very clear here, classes_do receives a function pointer, the dictionary() function will return the loaded classes, and these loaded classes will be stored in the static members of SystemDictionary in the form of Dictionary:

static Dictionary*            _dictionary;

  This Dictionary is a derived class of TwoOopHashtable :

class Dictionary : public TwoOopHashtable<Klass*, mtClass> {
    
    
}

  Here we don’t need to care too much about this data structure, we just need to know that the dictionary() function will return the loaded classes, and then call the passed functions on these classes. You can look at the implementation of the classes_do function, which is implemented in dictionary.cpp :

void Dictionary::classes_do(void f(Klass*)) {
    
    
  for (int index = 0; index < table_size(); index++) {
    
    
    for (DictionaryEntry* probe = bucket(index);
                          probe != NULL;
                          probe = probe->next()) {
    
    
      Klass* k = probe->klass();
      if (probe->loader_data() == InstanceKlass::cast(k)->class_loader_data()) {
    
    
        f(k);
      }
    }
  }
}

  It is to traverse all loaded classes, then call the passed function, and pass each Klass traversed as a parameter. At this point we can go back to the doit() method of VM_EnableBiasedLocking, that is, take a look at this line of code:

SystemDictionary::classes_do(enable_biased_locking);

  After the previous analysis, we know that SystemDictionary::classes_do will receive a function pointer, then traverse all the loaded classes, and then call the function with the class (Klass) as the input parameter. Then this enable_biased_locking should be a function pointer. Indeed, we go back to biaseLocking.cpp to find the definition of this function:

static void enable_biased_locking(Klass* k) {
    
    
  k->set_prototype_header(markOopDesc::biased_locking_prototype());
}

  Trace here, everything will be clear. There is a markOop type _prototype_header property in Klass, which can be used to indicate the enable/disable of the object bias lock, which is defined in Klass.hpp:

class Klass : public Metadata {
    
    
 protected:
 	markOop  _prototype_header;
}

  As for the set_prototype_header() method is an inline method, the specific implementation is in klass.inline.hpp:

inline void Klass::set_prototype_header(markOop header) {
    
    
  _prototype_header = header;
}

  As you can see, the _prototype_header property is set to the specified markOop, so what exactly is this markOop now? I don’t know if some friends already have a familiar feeling~ Go back to the enable_biased_locking method of biaseLocking.cpp again:

static void enable_biased_locking(Klass* k) {
    
    
  k->set_prototype_header(markOopDesc::biased_locking_prototype());
}

  Let's take a look at the specific markOop returned by markOopDesc::biased_locking_prototype(). Go to markOop.h and find the biased_locking_prototype method:

static markOop biased_locking_prototype() {
    
    
    return markOop( biased_lock_pattern );
  }

  Look at the definition of biased_lock_pattern:

  enum {
    
     locked_value             = 0,
         unlocked_value           = 1,
         monitor_value            = 2,
         marked_value             = 3,
         biased_lock_pattern      = 5
  };

  Does this look familiar? If you are not familiar with it, let me add a note and look at it again:

 enum {
    
      locked_value             = 0,//00 轻量级锁
         unlocked_value           = 1,//01 无锁
         monitor_value            = 2,//10 重量级锁
         marked_value             = 3,//11 GC标记
         biased_lock_pattern      = 5 //101 偏向锁,1位偏向标记和2位状态标记(01)
  };

  That's right, this definition is the lock status identifier in the Mark Word of the object header.

to sum up

Insert picture description here
  So why does the virtual machine add a default delay effective control to the bias lock? The virtual machine will also start some threads during the startup process, and some of the logic is also controlled internally. If the bias lock is directly turned on, it will usually lead to the bias cancellation. The JVM uses a lot of To be safe, not enabling the bias lock at the beginning is more helpful to improve the JVM startup speed (it feels like this should be o(╯□╰)o).

This article is based on the blogger’s personal understanding. If there are any errors, thank you for pointing them out!

Guess you like

Origin blog.csdn.net/huangzhilin2015/article/details/115314096