关于偏向锁,包括撤销和重偏向在前面已经结合源码分析过了,这里就不再赘述,主要看看延时生效的问题。另外,本文贴出的源码大多都只保留了当前关注的逻辑。
我们知道虚拟机为我们提供了参数- 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)。
本文基于博主个人理解,如有错误,感谢指出!