深入OpenJDK源码-偏向锁的延时生效如何实现的

关于偏向锁,包括撤销和重偏向在前面已经结合源码分析过了,这里就不再赘述,主要看看延时生效的问题。另外,本文贴出的源码大多都只保留了当前关注的逻辑。

  我们知道虚拟机为我们提供了参数- XX:+UseBiasedLocking以开启或者关闭偏向锁优化(默认开启),但是偏向锁的启用有个默认的延时时间,可以通过参数- XX:BiasedLockingStartupDelay设置,默认为4秒,可以在globals.hpp中找到默认值设置:

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

  本文就到源码层面看看,这个延时生效是如何实现的,废话不多说,我们直奔主题~

源码分析

  进入biasedLocking.cpp中看看偏向锁的初始化逻辑:

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);
    }
  }
}

  首先UseBiasedLocking为true,表示开启了偏向锁,如果BiasedLockingStartupDelay大于0,则表示需要延时生效,这里使用的是一个EnableBiasedLockingTask,来看看这个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);
  }
};

  可以看到EnableBiasedLockingTask 是PeriodicTask 的派生类,使用interval_time参数调用了父类的带参构造函数,PeriodicTask定义在task.hpp中,构造函数实现在task.cpp中:

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

  将interval_time赋值给了_interval属性,_interval是一个私有常量,以毫秒为单位,同时_counter设置为0:

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

  到现在已经创建了一个EnableBiasedLockingTask(PeriodicTask的派生类),创建好之后就调用了task->enroll()方法,该方法在父类PeriodicTask中定义:

void enroll();

  在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();
  }
}

  可以看到最终还是使用了一个WatcherThread对象,WatcherThread是Thread对象的派生类,其定义在thread.hpp中,这里就不贴代码了,不然就跑题了~ 我们直接进入WatcherThread::run()方法看看,只贴两行代码:

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

  那么接下来就看看PeriodicTask::real_time_tick(time_waited)方法的实现,其中核心代码是:

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

  我们之前调用PeriodicTask::enroll()方法将EnableBiasedLockingTask注册到了_tasks数组中,这里会从_tasks中遍历任务,然后分别调用execute_if_pending方法,该方法定义在父类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;
    }
  }

  当判断达到_interval时间,那么就会调用task()方法,task()方法定义在EnableBiasedLockingTask方法中,所以我们绕一圈又回到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);
  }
};

  task方法的逻辑比较简单,首先生成了一个VM_EnableBiasedLocking对象。它是VM_Operation类的派生类,实现了doit方法的逻辑,通过VMThread::execute去执行这个操作(如果配置不延时生效,这个逻辑会在偏向锁初始化的时候就执行),最终会调用doit()方法:

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

  doit方法主要调用了SystemDictionary::classes_do方法,我们等会儿再回来看enale_biased_locking是什么,现在先看SystemDictionary::classes_do方法到底干了什么事:

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

  这里就很清晰了,classes_do接收的是一个函数指针,dictionary()函数会返回已经被加载的类,这些被加载的类会以Dictionary的形式存储在SystemDictionary的静态成员之中:

static Dictionary*            _dictionary;

  这个Dictionary是TwoOopHashtable的派生类:

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

  这里我们不用太过关心这个数据结构,只需要知道dictionary()函数会返回已经加载的类,然后对这些类调用传入的函数,可以看看classes_do函数的实现,该函数实现在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);
      }
    }
  }
}

  就是遍历所有加载的类,然后调用传入的函数,并且将遍历到的每个Klass当做参数传入其中。到这里我们就可以回到VM_EnableBiasedLocking的doit()方法了,也就是看看这行代码:

SystemDictionary::classes_do(enable_biased_locking);

  经过前面的分析,我们知道SystemDictionary::classes_do会接收一个函数指针,然后遍历所有已加载的类,然后以类(Klass)为入参调用该函数。那么这个enable_biased_locking应该是一个函数指针才对,的确,我们回到biaseLocking.cpp中找到该函数的定义:

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

  跟踪到这里,所有就明了了。在Klass中有一个markOop类型的_prototype_header属性,可以用于表示对象偏向锁的启用/禁用,具体定义在Klass.hpp中:

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

  至于set_prototype_header()方法是一个内联方法,具体实现在klass.inline.hpp中:

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

  可以看到,就是把_prototype_header属性设置为指定的markOop,那么这个markOop现在具体是什么呢?不知道是不是有的小伙伴已经有熟悉的感觉了~ 再次回到biaseLocking.cpp的enable_biased_locking方法中:

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

  我们来看看markOopDesc::biased_locking_prototype()返回的具体是个什么markOop。进入markOop.h中找到biased_locking_prototype方法:

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

  看看biased_lock_pattern的定义:

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

  这个看起熟悉吧?如果不熟悉,我加个备注再看看:

 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)
  };

  没错,这个定义的就是对象头Mark Word中的锁状态标识。

总结

在这里插入图片描述
  那么为什么虚拟机会对偏向锁增加一个默认延时生效的控制呢?虚拟机在启动的过程中也会启动一些线程,有部分逻辑在内部也进行了并发控制,如果直接开启偏向锁的话,那么通常就会导致偏向撤销,JVM在启用偏向撤销的时候使用了大量的安全点,一开始先不启用偏向锁更有助于提高JVM启动速度(感觉应该是这样o(╯□╰)o)。

本文基于博主个人理解,如有错误,感谢指出!

猜你喜欢

转载自blog.csdn.net/huangzhilin2015/article/details/115314096