目录

1、定义

2、init

3、init方法补充说明

4、revoke_bias

5、bulk_revoke_or_rebias_at_safepoint

6、revoke_and_rebias

7、VM_RevokeBias / VM_BulkRevokeBias

8、revoke_at_safepoint / revoke

9、preserve_marks / restore_marks

10、MacroAssembler::biased_locking_enter / biased_locking_exit


之前的博客中已经讲解了synchronized关键字的底层实现,其中涉及偏向锁的逻辑都封装在BiasedLocking中,本篇博客就详细探讨该类的实现。

1、定义

BiasedLocking的定义位于hotspot\src\share\vm\runtime\biasedLocking.hpp中,该类定义的属性和方法都是静态的,其中属性只有一个,如下图:

BiasedLockingCounters的定义在同一个文件中,就是一个保存各项跟偏向锁有关的统计数据的数据结构,如下图:

这些统计数据主要是为了打印日志使用,如果PrintBiasedLockingStatistics为true就会打印该数据结构中的数据,默认为false。参考获取_total_entry_count内存地址的total_entry_count_addr方法的调用链,如下:

其中MacroAssembler::fast_lock和对应的MacroAssembler::fast_unlock是C2即时编译器使用的加锁和解锁的方法。

除此之外,还有一个比较关键的枚举Condition,用来描述当前偏向锁的状态,其定义如下:

NOT_BIASED表示该对象没有持有偏向锁,BIAS_REVOKED表示该对象的偏向锁已经被撤销了,即其对象头已经恢复成默认的不开启偏向锁时的状态,BIAS_REVOKED_AND_REBIASED表示当前线程重新获取了该偏向锁。

2、init

init方法用于初始化BiasedLocking,该方法是在JVM启动时调用的,其调用链如下:

其实现如下:

void BiasedLocking::init() {if (UseBiasedLocking) {//UseBiasedLocking默认为true,表示是否使用偏向锁if (BiasedLockingStartupDelay > 0) {//BiasedLockingStartupDelay表示BiasedLocking初始化时的延时,默认为4000,避免在JVM启动时初始化,可加快JVM的启动EnableBiasedLockingTask* task = new EnableBiasedLockingTask(BiasedLockingStartupDelay);task->enroll();} else {VM_EnableBiasedLocking op(false);VMThread::execute(&op);}}
}

EnableBiasedLockingTask中执行任务的实现task方法跟else分支基本一样,如下:

就构造函数的参数不同,一个是false,一个是true,该参数决定了VM_EnableBiasedLocking的执行模式,其实现如下:

class VM_EnableBiasedLocking: public VM_Operation {private:bool _is_cheap_allocated;public:VM_EnableBiasedLocking(bool is_cheap_allocated) { _is_cheap_allocated = is_cheap_allocated; }VMOp_Type type() const          { return VMOp_EnableBiasedLocking; }Mode evaluation_mode() const    { return _is_cheap_allocated ? _async_safepoint : _safepoint; }bool is_cheap_allocated() const { return _is_cheap_allocated; }void doit() {//将所有通过SystemDictionary加载过的类的对象头标记成其支持偏向SystemDictionary::classes_do(enable_biased_locking);//_biased_locking_enabled是一个静态static变量_biased_locking_enabled = true;if (TraceBiasedLocking) {tty->print_cr("Biased locking enabled");}}bool allow_nested_vm_operations() const        { return false; }
};static void enable_biased_locking(Klass* k) {//prototype_header表示该Klass对应oop的初始对象头k->set_prototype_header(markOopDesc::biased_locking_prototype());
}static markOop biased_locking_prototype() {//biased_lock_pattern表示该对象支持偏向锁return markOop( biased_lock_pattern );}void SystemDictionary::classes_do(void f(Klass*)) {dictionary()->classes_do(f);
}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);}}}
}

3、init方法补充说明

理解init方法的逻辑需要知道以下几点:

1)、Klass的prototype_header的用途,其调用链如下:

其中must_be开头的三个方法用于判断该对象头是否需要保存下来,oopDesc::init_mark用于初始化对象头,将其设置为对应Klass的prototype_header,prototype_header_offset方法返回prototype_header的偏移量,主要给解释器和编译器生成Stub使用,同样用于获取Klass的prototype_header将其作为一个新创建对象的对象头,其调用链如下:

2) 枚举biased_lock_pattern的含义

biased_lock_pattern仅仅只是表示JVM已经开启了偏向锁,当前对象支持偏向锁,具体是否持有偏向锁需要通过biased_locker方法来判断,其实现如下:

即判断用来存储持有当前对象偏向锁的线程指针是否为空,为空则表示该对象的偏向锁未被其他线程占有,不为空则表示已经被其他线程占用了。

与biased_locking_prototype方法相对的就是prototype方法,该方法返回默认的即不开启偏向锁时的对象头,其实现如下:

no_hash_in_place和no_lock_in_place都是枚举值,其定义如下:

no_hash_in_place表示未初始化hash码,unlocked_value表示当前对象没有持有任何锁,也不支持偏向锁,此时对象头的状态就是neutral,参考 is_neutral方法的实现,如下:

3) Dictionary::classes_do

Dictionary::classes_do用于遍历所有已加载的Klass,那这个Klass是哪来的了?参考与之对应的add_klass方法实现,如下:

void Dictionary::add_klass(Symbol* class_name, ClassLoaderData* loader_data,KlassHandle obj) {assert_locked_or_safepoint(SystemDictionary_lock);assert(obj() != NULL, "adding NULL obj");assert(obj()->name() == class_name, "sanity check on name");assert(loader_data != NULL, "Must be non-NULL");//计算hash值,Dictionary用于保存DictionaryEntry的数据结构跟Java中的HashMap是一样的unsigned int hash = compute_hash(class_name, loader_data);//计算保存该Klass的数组索引int index = hash_to_index(hash);//创建一个新的key-value键值对对象DictionaryEntry* entry = new_entry(hash, obj(), loader_data);//将其加入到数组中add_entry(index, entry);
}

该方法的调用链如下:

其最上层的调用方法resolve_or_null和resolve_array_class_or_null都是用来解析常量池中的Class类型的符号引用,当某个类执行某个方法时,会判断该方法字节码所用到的符号引用是否解析完成,如果未解析则调用上述resolve方法完成解析,即会加载该类,创建对应的Klass,创建完成后就会调用add_klass方法将创建的Klass加入到Dictionary中管理。可参考《Hotspot 类加载、链接和初始化 C++源码解析》。

4)静态属性_biased_locking_enabled

_biased_locking_enabled默认为false,如果UseBiasedLocking为true,则在init方法中会将其置为true,其调用链如下:

enable方法直接返回该属性,如下:

update_dictionary方法中的调用如下:

在调用add_class方法前会先执行上述逻辑,跟上面的enable_biased_locking方法一样,即将_biased_locking_enabled置为true以后会保证后面新加载的Klass的prototype_header都是biased_locking_prototype,而在此之前已加载的Klass已经通过init方法做了同样处理,如此就可以保证后续新创建的锁对象oop都可以支持偏向锁,但是之前已经创建的锁对象oop则不支持偏向锁。

4、revoke_bias

revoke_bias的返回值只有两种NOT_BIASED和BIAS_REVOKED,前者表示目标对象没有偏向锁,后者表示已经成功撤销了该对象的偏向锁,即该方法就是用于撤销偏向锁。该方法的参数is_bulk无实际意义,只用于判断是否打印日志;参数allow_rebias用于判断是否彻底撤销偏向锁,如果为true将只是清除偏向锁中的线程指针,将其变成一个匿名的即没有被其他线程占用的偏向锁;如果为false则将其恢复成无锁状态。在撤销偏向锁时,如果是匿名偏向锁或者持有该锁的线程已经退出了或者持有该锁的线程中没有对应的BasicObjectLock(即占用该锁的方法或者代码块已经执行完了)则按照参数allow_rebias撤销偏向锁;如果有某个线程正在持有该偏向锁,无论是否当前线程,都需要将其持有的偏向锁膨胀成轻量级锁,且需要考虑锁嵌套的情形。

static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread) {markOop mark = obj->mark();if (!mark->has_bias_pattern()) {//如果未加锁if (TraceBiasedLocking) {ResourceMark rm;tty->print_cr("  (Skipping revocation of object of type %s because it's no longer biased)",obj->klass()->external_name());}return BiasedLocking::NOT_BIASED;}uint age = mark->age();markOop   biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);if (TraceBiasedLocking && (Verbose || !is_bulk)) {ResourceMark rm;tty->print_cr("Revoking bias of object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s , prototype header " INTPTR_FORMAT " , allow rebias %d , requesting thread " INTPTR_FORMAT,p2i((void *)obj), (intptr_t) mark, obj->klass()->external_name(), (intptr_t) obj->klass()->prototype_header(), (allow_rebias ? 1 : 0), (intptr_t) requesting_thread);}JavaThread* biased_thread = mark->biased_locker();if (biased_thread == NULL) {//如果是匿名偏向锁if (!allow_rebias) {//将其变成无锁状态obj->set_mark(unbiased_prototype);}if (TraceBiasedLocking && (Verbose || !is_bulk)) {tty->print_cr("  Revoked bias of anonymously-biased object");}return BiasedLocking::BIAS_REVOKED;}//biased_thread不为空bool thread_is_alive = false;if (requesting_thread == biased_thread) {//如果当前线程就是占有偏向锁的线程thread_is_alive = true;} else {for (JavaThread* cur_thread = Threads::first(); cur_thread != NULL; cur_thread = cur_thread->next()) {if (cur_thread == biased_thread) {//遍历所有的线程,如果找到了占用偏向锁的线程thread_is_alive = true;break;}}}if (!thread_is_alive) {//如果占用偏向锁的线程退出了if (allow_rebias) {//如果允许使用偏向锁,则设置为匿名偏向锁,注意此时的对象头中线程指针没了obj->set_mark(biased_prototype);} else {//如果不允许使用偏向锁,置为无锁状态obj->set_mark(unbiased_prototype);}if (TraceBiasedLocking && (Verbose || !is_bulk)) {tty->print_cr("  Revoked bias of object biased toward dead thread");}//返回偏向锁被撤销了return BiasedLocking::BIAS_REVOKED;}//如果拥有偏向锁的线程还是存活的//获取该线程的所有MonitorInfo,MonitorInfo是对BasicObjectLock的一层包装而已//返回的MonitorInfo数组中最近分配的在前面GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);BasicLock* highest_lock = NULL;for (int i = 0; i < cached_monitor_info->length(); i++) {MonitorInfo* mon_info = cached_monitor_info->at(i);if (mon_info->owner() == obj) {if (TraceBiasedLocking && Verbose) {tty->print_cr("   mon_info->owner (" PTR_FORMAT ") == obj (" PTR_FORMAT ")",p2i((void *) mon_info->owner()),p2i((void *) obj));}//找到关联的BasicObjectLock,注意此处找到了跟目标obj关联的BasicObjectLock后并不会终止遍历而是继续遍历//因为存在锁嵌套的情形,这里继续遍历找到最外层的BasicObjectLockmarkOop mark = markOopDesc::encode((BasicLock*) NULL);highest_lock = mon_info->lock();//修改displaced_header属性,实际就是将其置为NULLhighest_lock->set_displaced_header(mark);} else {if (TraceBiasedLocking && Verbose) {tty->print_cr("   mon_info->owner (" PTR_FORMAT ") != obj (" PTR_FORMAT ")",p2i((void *) mon_info->owner()),p2i((void *) obj));}}}if (highest_lock != NULL) {//修改displaced_header,撤销原对象头中包含的偏向锁highest_lock->set_displaced_header(unbiased_prototype);//修改对象头让其指向highest_lock,实际就是将其膨胀成轻量级锁obj->release_set_mark(markOopDesc::encode(highest_lock));assert(!obj->mark()->has_bias_pattern(), "illegal mark state: stack lock used bias bit");if (TraceBiasedLocking && (Verbose || !is_bulk)) {tty->print_cr("  Revoked bias of currently-locked object");}} else {//没有关联的BasicObjectLock,说明该线程实际已经释放了偏向锁if (TraceBiasedLocking && (Verbose || !is_bulk)) {tty->print_cr("  Revoked bias of currently-unlocked object");}if (allow_rebias) {//设置成匿名偏向锁obj->set_mark(biased_prototype);} else {//设置成无锁状态obj->set_mark(unbiased_prototype);}}return BiasedLocking::BIAS_REVOKED;
}static GrowableArray<MonitorInfo*>* get_or_compute_monitor_info(JavaThread* thread) {//获取目标线程的cached_monitor_info属性GrowableArray<MonitorInfo*>* info = thread->cached_monitor_info();if (info != NULL) {//如果不为空说明之前已经调用过get_or_compute_monitor_info方法,直接返回return info;}//info为null,创建一个新的info = new GrowableArray<MonitorInfo*>();if (thread->has_last_Java_frame()) {//如果包含Java栈帧RegisterMap rm(thread);//遍历所有的Java栈帧for (javaVFrame* vf = thread->last_java_vframe(&rm); vf != NULL; vf = vf->java_sender()) {//monitors方法中返回的MonitorInfo的顺序是最先分配的BasicObjectLock在前面GrowableArray<MonitorInfo*> *monitors = vf->monitors();if (monitors != NULL) {int len = monitors->length();//遍历Java栈帧中的MonitorInfo,注意此处是倒序遍历的,即最先遍历最近分配的BasicObjectLockfor (int i = len - 1; i >= 0; i--) {MonitorInfo* mon_info = monitors->at(i);//如果该MonitorInfo已经被淘汰if (mon_info->eliminated()) continue;oop owner = mon_info->owner();if (owner != NULL) {//owner不为空,即是非空闲的BasicObjectLock,则加入到info中info->append(mon_info);}}}}}//info中最近分配的BasicObjectLock在数组前面thread->set_cached_monitor_info(info);return info;
}static markOop encode(BasicLock* lock) {return (markOop) lock;}inline void oopDesc::release_set_mark(markOop m) {OrderAccess::release_store_ptr(&_mark, m);
}

上述代码中涉及的MonitorInfo是一个用来保存BasicObjectLock重要属性的数据结构,其定义如下:

其构造函数的调用链如下:

以interpretedVFrame::monitors的实现为例说明,如下:

该方法会遍历当前调用栈帧中包含的所有BasicObjectLock,利用该对象构造一个MonitorInfo实例然后放入数组中,注意其是从高地址往低地址遍历,即先遍历最早分配的BasicObjectLock,参考previous_monitor_in_interpreter_frame方法的实现,如下:

5、bulk_revoke_or_rebias_at_safepoint

该方法用于批量的撤销或者重新偏向,批量是指会对某个oop对应的Klass的所有作为锁对象的位于调用栈帧上的oop,撤销就是将该Klass的所有作为锁对象oop的对象头都置为无锁状态,同时把prototype_header也置为无锁状态,即该Klass新创建的作为锁对象的oop不再支持偏向锁;重新偏向时,如果prototype_header支持偏向锁,则将prototype_header中的epoch加1,并且同样的将该Klass的所有作为锁对象的oop的epoch加1,注意此时除目标对象o以外的其他锁对象的锁状态不变,只是增加epoch而已;此时如果attempt_rebias_of_object为true,则目标对象的偏向锁会先被置为匿名偏向锁,再置为偏向请求线程的偏向锁,如果为false则置为无锁状态。注意该方法只能在安全点下执行,其返回值只有BIAS_REVOKED和BIAS_REVOKED_AND_REBIASED两种。

static BiasedLocking::Condition bulk_revoke_or_rebias_at_safepoint(oop o,bool bulk_rebias,bool attempt_rebias_of_object,JavaThread* requesting_thread) {assert(SafepointSynchronize::is_at_safepoint(), "must be done at safepoint");if (TraceBiasedLocking) {tty->print_cr("* Beginning bulk revocation (kind == %s) because of object "INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",(bulk_rebias ? "rebias" : "revoke"),p2i((void *) o), (intptr_t) o->mark(), o->klass()->external_name());}jlong cur_time = os::javaTimeMillis();//更新批量撤销的时间o->klass()->set_last_biased_lock_bulk_revocation_time(cur_time);Klass* k_o = o->klass();Klass* klass = k_o;if (bulk_rebias) {//如果批量重新偏向if (klass->prototype_header()->has_bias_pattern()) {int prev_epoch = klass->prototype_header()->bias_epoch();//增加prototype_header中的bias_epochklass->set_prototype_header(klass->prototype_header()->incr_bias_epoch());int cur_epoch = klass->prototype_header()->bias_epoch();// 遍历所有的Java线程for (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {//获取该线程的所有调用栈帧包含的BasicObjectLockGrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(thr);for (int i = 0; i < cached_monitor_info->length(); i++) {MonitorInfo* mon_info = cached_monitor_info->at(i);oop owner = mon_info->owner();markOop mark = owner->mark();//如果BasicObjectLock关联的obj的kass跟目标对象的klass一致if ((owner->klass() == k_o) && mark->has_bias_pattern()) {//修改关联obj的epoch,确保跟prototype_header保持一致,注意此时偏向锁并未撤销assert(mark->bias_epoch() == prev_epoch || mark->bias_epoch() == cur_epoch, "error in bias epoch adjustment");owner->set_mark(mark->set_bias_epoch(cur_epoch));}}}}//撤销目标对象的偏向锁revoke_bias(o, attempt_rebias_of_object && klass->prototype_header()->has_bias_pattern(), true, requesting_thread);} else {//如果批量撤销if (TraceBiasedLocking) {ResourceMark rm;tty->print_cr("* Disabling biased locking for type %s", klass->external_name());}//将prototype_header恢复成默认的无锁状态klass->set_prototype_header(markOopDesc::prototype());//遍历所有Java线程持有的所有BasicObjectLockfor (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(thr);for (int i = 0; i < cached_monitor_info->length(); i++) {MonitorInfo* mon_info = cached_monitor_info->at(i);oop owner = mon_info->owner();markOop mark = owner->mark();//如果关联obj的klass和目标obj的klass一致if ((owner->klass() == k_o) && mark->has_bias_pattern()) {//撤销偏向锁,将其膨胀成轻量级锁,因为这些偏向锁都是正在使用中的revoke_bias(owner, false, true, requesting_thread);}}}//撤销偏向锁,上面的遍历其实就会完成对应o的偏向锁撤销revoke_bias(o, false, true, requesting_thread);}if (TraceBiasedLocking) {tty->print_cr("* Ending bulk revocation");}BiasedLocking::Condition status_code = BiasedLocking::BIAS_REVOKED;if (attempt_rebias_of_object &&   //只有attempt_rebias_of_object和bulk_rebias都为true时后面两个条件才可能成立o->mark()->has_bias_pattern() &&klass->prototype_header()->has_bias_pattern()) {//重置偏向锁  markOop new_mark = markOopDesc::encode(requesting_thread, o->mark()->age(),klass->prototype_header()->bias_epoch());o->set_mark(new_mark);status_code = BiasedLocking::BIAS_REVOKED_AND_REBIASED;if (TraceBiasedLocking) {tty->print_cr("  Rebiased object toward thread " INTPTR_FORMAT, (intptr_t) requesting_thread);}}//最后的结果要么o撤销了偏向锁,要么o重新设置了偏向锁assert(!o->mark()->has_bias_pattern() ||(attempt_rebias_of_object && (o->mark()->biased_locker() == requesting_thread)),"bug in bulk bias revocation");return status_code;
}markOop incr_bias_epoch() {return set_bias_epoch((1 + bias_epoch()) & epoch_mask);}

6、revoke_and_rebias

此方法的逻辑不是很复杂,但是有很多的条件判断,要想知道其背后的用意,需要清楚revoke_and_rebias的调用场景,其调用链如下:

其中fast_enter用于实现synchronized关键字,其调用时attempt_rebias为true;FastHashCode用于获取对象头中的hash码,其调用时attempt_rebias为false;jni开头的三个调用方法用于实现JNI和Unsafe类中的监视器锁获取和释放,其调用时attempt_rebias为false;wait,nofity,notifyall三个方法用于实现Object的本地方法,其调用时attempt_rebias为false,其他的几处调用attempt_rebias也都是为false。

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");//非fast_enter的调用可能会走if或者else if分支,此时attempt_rebias都是false,即这种情形下都是撤销偏向锁markOop mark = obj->mark();if (mark->is_biased_anonymously() && !attempt_rebias) {//如果obj的偏向锁未被占有且不需要获取偏向锁,则尝试将其恢复成默认的markOop biased_value       = mark;//将对象的分代年龄写入初始对象头中markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());//原子的修改对象头,将其恢复成无锁状态markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);if (res_mark == biased_value) {//修改成功return BIAS_REVOKED;}} else if (mark->has_bias_pattern()) {//如果obj的偏向锁已经被占用了或者attempt_rebias为trueKlass* k = obj->klass();//prototype_header变更只能在安全点下,变更时会将位于栈上的该klass的所有锁对象oop都做同样的变更//如果某个锁对象oop不在当时的调用栈帧中,则可能出现不一致markOop prototype_header = k->prototype_header();if (!prototype_header->has_bias_pattern()) {//如果prototype_header中没有偏向锁标识markOop biased_value       = mark;//将当前对象的对象头原子的修改成prototype_header,将其恢复成无锁状态markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");return BIAS_REVOKED;} else if (prototype_header->bias_epoch() != mark->bias_epoch()) {//如果两个对象头的epoch的值不等,说明更新prototype_header的epoch值时该对象不在任何Java线程栈帧中,即该对象的偏向锁实际已经释放了if (attempt_rebias) {//如果需要重新偏向assert(THREAD->is_Java_thread(), "");markOop biased_value       = mark;//生成一个新的偏向锁对象头,让当前线程占用该偏向锁markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);if (res_mark == biased_value) {//如果修改成功return BIAS_REVOKED_AND_REBIASED;}} else {//attempt_rebias为false,则将对象头恢复成无锁状态markOop biased_value       = mark;markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);if (res_mark == biased_value) {//修改成功return BIAS_REVOKED;}}}}//进入此逻辑的有以下几种情形:目标对象处于无锁状态;目标对象有偏向锁,其Klass的prototype_header也有偏向锁,且两者epoch一致。//如果是并发抢占偏向锁,则抢占失败的线程会进入此逻辑,通过VM_RevokeBias将偏向锁膨胀成轻量级锁,HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);if (heuristics == HR_NOT_BIASED) {//返回没有偏向锁return NOT_BIASED;} else if (heuristics == HR_SINGLE_REVOKE) {Klass *k = obj->klass();markOop prototype_header = k->prototype_header();if (mark->biased_locker() == THREAD &&prototype_header->bias_epoch() == mark->bias_epoch()) {//如果目标对象的偏向锁被当前线程占有ResourceMark rm;if (TraceBiasedLocking) {tty->print_cr("Revoking bias by walking my own stack:");}/将偏向锁膨胀成轻量级锁或者撤销BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD);//将cached_monitor_info置为NULL,revoke_bias方法会设置该属性((JavaThread*) THREAD)->set_cached_monitor_info(NULL);assert(cond == BIAS_REVOKED, "why not?");return cond;} else {//如果占用偏向锁的不是当前线程,底层也是调用revoke_bias,撤销偏向锁或者将其膨胀成轻量级锁//因为是修改其他线程占用的obj,为了安全需要在安全点下执行,借此暂停持有该偏向锁的线程,注意这个任务并不是立即执行,而是有一个短暂的延时VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);VMThread::execute(&revoke);return revoke.status_code();}}assert((heuristics == HR_BULK_REVOKE) ||(heuristics == HR_BULK_REBIAS), "?");//执行批量撤销或者重偏向,底层调用bulk_revoke_or_rebias_at_safepoint//同一个Klass的多个锁对象oop累计调用update_heuristics超过一定次数就会返回HR_BULK_REBIAS,通过VM_BulkRevokeBias将Klass和对应的多个位于调用栈帧中的锁对象oop的epoch值增加,这样没有为调用栈帧中也是该Klass的锁对象oop就会被用于重新获取偏向锁,减少update_heuristics的调用//如果调用更加频繁,则返回HR_BULK_REVOKE,将该Klass的prototype_header和对应的多个位于调用栈帧中的锁对象oop恢复成无锁状态,因为频繁的执行此逻辑说明当前的并发调用场景已经不适用于偏向锁了       VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,(heuristics == HR_BULK_REBIAS),attempt_rebias);VMThread::execute(&bulk_revoke);return bulk_revoke.status_code();
}//目前对象的偏向锁是否被占有了
bool is_biased_anonymously() const {return (has_bias_pattern() && (biased_locker() == NULL));}//获取epoch值
int bias_epoch() const {assert(has_bias_pattern(), "should not call this otherwise");return (mask_bits(value(), epoch_mask_in_place) >> epoch_shift);}//重新生成一个偏向锁对象头
static markOop encode(JavaThread* thread, uint age, int bias_epoch) {intptr_t tmp = (intptr_t) thread;assert(UseBiasedLocking && ((tmp & (epoch_mask_in_place | age_mask_in_place | biased_lock_mask_in_place)) == 0), "misaligned JavaThread pointer");assert(age <= max_age, "age too large");assert(bias_epoch <= max_bias_epoch, "bias epoch too large");//全部用或运算return (markOop) (tmp | (bias_epoch << epoch_shift) | (age << age_shift) | biased_lock_pattern);}//HeuristicsResult是一个枚举
static HeuristicsResult update_heuristics(oop o, bool allow_rebias) {markOop mark = o->mark();if (!mark->has_bias_pattern()) {//返回没有偏向锁return HR_NOT_BIASED;}Klass* k = o->klass();jlong cur_time = os::javaTimeMillis();//获取上一次偏向锁被批量撤销的时间jlong last_bulk_revocation_time = k->last_biased_lock_bulk_revocation_time();//获取偏向锁被撤销的次数int revocation_count = k->biased_lock_revocation_count();//BiasedLockingBulkRebiasThreshold的默认值是20//BiasedLockingBulkRevokeThreshold的默认值是40//BiasedLockingDecayTime的默认值是25000,单位毫秒if ((revocation_count >= BiasedLockingBulkRebiasThreshold) &&(revocation_count <  BiasedLockingBulkRevokeThreshold) &&(last_bulk_revocation_time != 0) &&(cur_time - last_bulk_revocation_time >= BiasedLockingDecayTime)) {//将其重置为0,即在BiasedLockingDecayTime内,如果//无论如何都是先触发HR_BULK_REBIAS,触发完了以后如果频繁的调用update_heuristics会导致revocation_count达到BiasedLockingBulkRevokeThreshold,触发HR_BULK_REVOKE//如果调用不频繁,则不会触发HR_BULK_REVOKEk->set_biased_lock_revocation_count(0);revocation_count = 0;}if (revocation_count <= BiasedLockingBulkRevokeThreshold) {//增加计数器revocation_count = k->atomic_incr_biased_lock_revocation_count();}if (revocation_count == BiasedLockingBulkRevokeThreshold) {return HR_BULK_REVOKE;}if (revocation_count == BiasedLockingBulkRebiasThreshold) {return HR_BULK_REBIAS;}return HR_SINGLE_REVOKE;
}

7、VM_RevokeBias / VM_BulkRevokeBias

VM_RevokeBias用于撤销偏向锁,支持单个对象或者一组对象,VM_BulkRevokeBias继承自VM_RevokeBias,只能用于单个对象,其底层都是revoke_bias和bulk_revoke_or_rebias_at_safepoint方法,因为这两方法需要遍历线程栈帧中包含的BasicObjectLock,为了保证遍历的过程中栈帧包含的BasicObjectLock不变,所以必须在安全点下执行。其实现如下:

class VM_RevokeBias : public VM_Operation {
protected:Handle* _obj; //被撤销偏向锁的单个对象GrowableArray<Handle>* _objs; //被撤销偏向锁的对象数组JavaThread* _requesting_thread; //请求线程BiasedLocking::Condition _status_code; //执行结果public://两种构造函数,对应两种用法VM_RevokeBias(Handle* obj, JavaThread* requesting_thread): _obj(obj), _objs(NULL), _requesting_thread(requesting_thread), _status_code(BiasedLocking::NOT_BIASED) {}VM_RevokeBias(GrowableArray<Handle>* objs, JavaThread* requesting_thread): _obj(NULL), _objs(objs), _requesting_thread(requesting_thread), _status_code(BiasedLocking::NOT_BIASED) {}virtual VMOp_Type type() const { return VMOp_RevokeBias; }virtual bool doit_prologue() {// 检查是否包含带有偏向锁的对象,如果有返回true,如果没有则返回false,避免触发安全点同步if (_obj != NULL) {markOop mark = (*_obj)()->mark();if (mark->has_bias_pattern()) {return true;}} else {//遍历数组中所有对象for ( int i = 0 ; i < _objs->length(); i++ ) {markOop mark = (_objs->at(i))()->mark();if (mark->has_bias_pattern()) {return true;}}}return false;}virtual void doit() {if (_obj != NULL) {if (TraceBiasedLocking) {tty->print_cr("Revoking bias with potentially per-thread safepoint:");}//调用revoke_bias撤销偏向锁_status_code = revoke_bias((*_obj)(), false, false, _requesting_thread);//清除所有线程的cached_monitor_infoclean_up_cached_monitor_info();return;} else {if (TraceBiasedLocking) {tty->print_cr("Revoking bias with global safepoint:");}//批量撤销,底层调用bulk_revoke_or_rebias_at_safepointBiasedLocking::revoke_at_safepoint(_objs);}}BiasedLocking::Condition status_code() const {return _status_code;}
};bool has_bias_pattern() const {return (mask_bits(value(), biased_lock_mask_in_place) == biased_lock_pattern);
}static void clean_up_cached_monitor_info() {//将所有线程的cached_monitor_info置为NULLfor (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {thr->set_cached_monitor_info(NULL);}
}class VM_BulkRevokeBias : public VM_RevokeBias {
private:bool _bulk_rebias;bool _attempt_rebias_of_object;public:VM_BulkRevokeBias(Handle* obj, JavaThread* requesting_thread,bool bulk_rebias,bool attempt_rebias_of_object): VM_RevokeBias(obj, requesting_thread), _bulk_rebias(bulk_rebias), _attempt_rebias_of_object(attempt_rebias_of_object) {}virtual VMOp_Type type() const { return VMOp_BulkRevokeBias; }virtual bool doit_prologue()   { return true; }virtual void doit() {_status_code = bulk_revoke_or_rebias_at_safepoint((*_obj)(), _bulk_rebias, _attempt_rebias_of_object, _requesting_thread);clean_up_cached_monitor_info();}
};

8、revoke_at_safepoint / revoke

该方法有个重载方法,一个处理单个对象,一个处理一组对象,其处理逻辑一样,前者适用于单个对象,后者适用于一组对象

void BiasedLocking::revoke_at_safepoint(Handle h_obj) {assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");oop obj = h_obj();HeuristicsResult heuristics = update_heuristics(obj, false);if (heuristics == HR_SINGLE_REVOKE) {revoke_bias(obj, false, false, NULL);} else if ((heuristics == HR_BULK_REBIAS) ||(heuristics == HR_BULK_REVOKE)) {bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);}clean_up_cached_monitor_info();
}void BiasedLocking::revoke_at_safepoint(GrowableArray<Handle>* objs) {assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");int len = objs->length();for (int i = 0; i < len; i++) {oop obj = (objs->at(i))();HeuristicsResult heuristics = update_heuristics(obj, false);if (heuristics == HR_SINGLE_REVOKE) {revoke_bias(obj, false, false, NULL);} else if ((heuristics == HR_BULK_REBIAS) ||(heuristics == HR_BULK_REVOKE)) {bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);}}clean_up_cached_monitor_info();
}void BiasedLocking::revoke(GrowableArray<Handle>* objs) {assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");if (objs->length() == 0) {return;}//最终也是调用revoke_at_safepoint方法VM_RevokeBias revoke(objs, JavaThread::current());VMThread::execute(&revoke);
}

9、preserve_marks / restore_marks

preserve_marks方法在GC开始前将所有持有偏向锁的对象和对象头放入对应Stack中,然后在GC结束后恢复其对象头。

void BiasedLocking::preserve_marks() {if (!UseBiasedLocking)return;//必须在安全点调用assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");assert(_preserved_oop_stack  == NULL, "double initialization");assert(_preserved_mark_stack == NULL, "double initialization");_preserved_mark_stack = new (ResourceObj::C_HEAP, mtInternal) GrowableArray<markOop>(10, true);_preserved_oop_stack = new (ResourceObj::C_HEAP, mtInternal) GrowableArray<Handle>(10, true);ResourceMark rm;Thread* cur = Thread::current();//遍历所有的JavaThreadfor (JavaThread* thread = Threads::first(); thread != NULL; thread = thread->next()) {if (thread->has_last_Java_frame()) {RegisterMap rm(thread);//遍历所有的java栈帧for (javaVFrame* vf = thread->last_java_vframe(&rm); vf != NULL; vf = vf->java_sender()) {//获取当前栈帧中包含的所有MonitorInfoGrowableArray<MonitorInfo*> *monitors = vf->monitors();if (monitors != NULL) {int len = monitors->length();//遍历所有的MonitorInfofor (int i = len - 1; i >= 0; i--) {MonitorInfo* mon_info = monitors->at(i);if (mon_info->owner_is_scalar_replaced()) continue;oop owner = mon_info->owner();if (owner != NULL) {markOop mark = owner->mark();//如果其持有偏向锁,将对象和对象头分别保存到对应的Stack中if (mark->has_bias_pattern()) {_preserved_oop_stack->push(Handle(cur, owner));_preserved_mark_stack->push(mark);}}}}}}}
}void BiasedLocking::restore_marks() {if (!UseBiasedLocking)return;assert(_preserved_oop_stack  != NULL, "double free");assert(_preserved_mark_stack != NULL, "double free");//恢复Stack中保存的对象的对象头int len = _preserved_oop_stack->length();for (int i = 0; i < len; i++) {Handle owner = _preserved_oop_stack->at(i);markOop mark = _preserved_mark_stack->at(i);owner->set_mark(mark);}//释放掉Stack占用的内存delete _preserved_oop_stack;_preserved_oop_stack = NULL;delete _preserved_mark_stack;_preserved_mark_stack = NULL;
}

10、MacroAssembler::biased_locking_enter / biased_locking_exit

biased_locking_enter用于获取偏向锁,如果该对象的锁已经膨胀成轻量级锁或者重量级锁,则直接返回;如果还是当前线程持有该对象的偏向锁且该对象的对象头中的epoch和该对象的klass的prototype_header中的epoch一致,则跳转到加锁完成标签,即该对象的加锁完成;如果该对象的klass的prototype_header已经没有偏向锁标识了,则需要将目标对象的对象头恢复成默认的无锁状态,然后跳转到slow_path;如果该对象的klass的prototype_header中的epoch发生改变了,则重新设置目标对象的偏向锁对象头,跳转到slow_path标签;如果klass的prototype_header没有改变,是一个新的线程请求该对象的偏向锁,则将当前线程的线程指针写入目标对象的偏向锁对象头中,跳转到slow_path标签。

biased_locking_exit只是校验目标对象的偏向锁标识是否存在,如果存在说明未向上膨胀,则直接跳转到解锁完成标签,即该对象的解锁完成,如果不存在则直接返回。

void MacroAssembler::biased_locking_exit(Register obj_reg, Register temp_reg, Label& done) {assert(UseBiasedLocking, "why call this otherwise?");//获取obj的对象头movptr(temp_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));//将其进行and运算,biased_lock_mask_in_place就是7,二进制111andptr(temp_reg, markOopDesc::biased_lock_mask_in_place);//判断and后的结果是否是5即101cmpptr(temp_reg, markOopDesc::biased_lock_pattern);//如果是则跳转到done,如果是表明未发生锁膨胀jcc(Assembler::equal, done);
}int MacroAssembler::biased_locking_enter(Register lock_reg,Register obj_reg,Register swap_reg,Register tmp_reg,bool swap_reg_contains_mark,Label& done,Label* slow_case,BiasedLockingCounters* counters) {assert(UseBiasedLocking, "why call this otherwise?");assert(swap_reg == rax, "swap_reg must be rax for cmpxchgq");LP64_ONLY( assert(tmp_reg != noreg, "tmp_reg must be supplied"); )bool need_tmp_reg = false;if (tmp_reg == noreg) {//如果tmp_reg不存在,临时使用lock_regneed_tmp_reg = true;tmp_reg = lock_reg;assert_different_registers(lock_reg, obj_reg, swap_reg);} else {assert_different_registers(lock_reg, obj_reg, swap_reg, tmp_reg);}assert(markOopDesc::age_shift == markOopDesc::lock_bits + markOopDesc::biased_lock_bits, "biased locking makes assumptions about bit layout");//obj的对象头地址Address mark_addr      (obj_reg, oopDesc::mark_offset_in_bytes());//BasicLock中保存对象头的地址Address saved_mark_addr(lock_reg, 0);if (PrintBiasedLockingStatistics && counters == NULL) {counters = BiasedLocking::counters();}Label cas_label;int null_check_offset = -1;if (!swap_reg_contains_mark) {null_check_offset = offset();//将对象头放入swap_reg即rax中movptr(swap_reg, mark_addr);}if (need_tmp_reg) {//将lock_reg中保存的BasicLock地址压入栈帧,从而可以利用lock_reg执行其他的运算push(tmp_reg);}//将swap_reg中的对象头拷贝到tmp_regmovptr(tmp_reg, swap_reg);//执行and运算,biased_lock_mask_in_place就是111andptr(tmp_reg, markOopDesc::biased_lock_mask_in_place);//判断结果是否是biased_lock_patterncmpptr(tmp_reg, markOopDesc::biased_lock_pattern);if (need_tmp_reg) {//将栈顶的BasicLock地址pop出来放到lock_reg中pop(tmp_reg);}//如果不等于,说明该对象的锁已经变成轻量级锁或者重量级锁,则跳转到cas_labeljcc(Assembler::notEqual, cas_label);//如果等于,说明该对象的锁还是偏向锁,判断持有偏向锁的线程是否是当前线程if (need_tmp_reg) {//重新将tmp_reg中的BasicLock地址压入栈顶push(tmp_reg);}if (swap_reg_contains_mark) {null_check_offset = offset();}//获取obj对应klass的prototype_header,将其拷贝到tmp_reg中load_prototype_header(tmp_reg, obj_reg);//将r15_thread中保存的当前thread指针和prototype_header进行or运算,结果保存在tmp_regorptr(tmp_reg, r15_thread);//与obj的对象头做异或运算,将结果保存到tmp_reg,如果两者的thead指针和epoch一样则这些位置为0,prototype_header中没有age,这4位是1xorptr(tmp_reg, swap_reg);Register header_reg = tmp_reg;//进行and运算,相当于将header_reg中表示age的4位置为0andptr(header_reg, ~((int) markOopDesc::age_mask_in_place));if (need_tmp_reg) {pop(tmp_reg);}if (counters != NULL) {cond_inc32(Assembler::zero,ExternalAddress((address) counters->biased_lock_entry_count_addr()));}//将swap_reg即rax与header_reg比较  //判断header_reg的结果是为0,如果是则还是当前线程持有的偏向锁jcc(Assembler::equal, done);//如果不等于,说明有一个新的线程请求获取偏向锁或者prototype_header发生变更了Label try_revoke_bias;Label try_rebias;testptr(header_reg, markOopDesc::biased_lock_mask_in_place);//如果prototype header的最后三位中有一位是1,说明prototype_header中已经没有偏向锁了,//则跳转到try_revoke_bias,撤销偏向锁,恢复成默认的对象头jccb(Assembler::notZero, try_revoke_bias);//最后三位还是0,说明obj还是可以用于偏向锁testptr(header_reg, markOopDesc::epoch_mask_in_place);//如果epoch位上不是0,说明prototype_header中的epoch和obj的对象头中包含的epoch不一致,prototype_header中的epoch发生改变了//则跳转到try_rebiasjccb(Assembler::notZero, try_rebias);//epoch相同,说明就是当前线程与obj对象头中包含的线程指针不一样或者obj对象头中的线程指针为空//将对象头进行and运算,即去掉obj对象中包含的线程指针andptr(swap_reg,markOopDesc::biased_lock_mask_in_place | markOopDesc::age_mask_in_place | markOopDesc::epoch_mask_in_place);if (need_tmp_reg) {push(tmp_reg);}//将其拷贝到tmp_reg,然后跟当前线程指针做or运算movptr(tmp_reg, swap_reg);//将线程指针写入tmp_regorptr(tmp_reg, r15_thread);if (os::is_MP()) {lock();}//将obj对象头与swap_reg中已经去掉线程指针的对象头比较,如果相等则把tmp_reg写入对象头,占用偏向锁成功,否则把新的对象头写入swap_reg中cmpxchgptr(tmp_reg, mark_addr); // compare tmp_reg and swap_regif (need_tmp_reg) {pop(tmp_reg);}if (counters != NULL) {//增加计数器cond_inc32(Assembler::zero,ExternalAddress((address) counters->anonymously_biased_lock_entry_count_addr()));}if (slow_case != NULL) {//如果不等则跳转到slow_casejcc(Assembler::notZero, *slow_case);}jmp(done);bind(try_rebias);if (need_tmp_reg) {push(tmp_reg);}//重新加载prototype_headerload_prototype_header(tmp_reg, obj_reg);//跟当前线程指针做or运算,确保新的对象头跟prototype_header中的epoch值一致orptr(tmp_reg, r15_thread);if (os::is_MP()) {lock(); //lock指令让cmpxchgptr变成一个原子操作}//将obj对象头同swap_reg中的对象头比较,如果相等将运算结果写入对象头,即占用偏向锁成功,如果不等将新的对象头放入swap_reg中cmpxchgptr(tmp_reg, mark_addr); // compare tmp_reg and swap_regif (need_tmp_reg) {pop(tmp_reg);}if (counters != NULL) {//更新计数器cond_inc32(Assembler::zero,ExternalAddress((address) counters->rebiased_lock_entry_count_addr()));}if (slow_case != NULL) {//如果不等于,说明抢占偏向锁失败,则跳转到slow_case,尝试锁膨胀jcc(Assembler::notZero, *slow_case);}jmp(done);bind(try_revoke_bias);if (need_tmp_reg) {push(tmp_reg);}//重新加载prototype_headerload_prototype_header(tmp_reg, obj_reg);if (os::is_MP()) {lock();//lock指令前缀让cmpxchgptr变成一个原子操作}//将obj对象头同swap_reg中的对象头比较,如果相等将prototype_header写入对象头,如果不等将新的对象头写入swap_reg中,多线程线下只有一个线程//判断相等,其他的都是不等//此时对象头已经恢复成无锁状态,如果是多线程请求会则触发锁膨胀cmpxchgptr(tmp_reg, mark_addr); if (need_tmp_reg) {pop(tmp_reg);}if (counters != NULL) {//增加计数器cond_inc32(Assembler::zero,ExternalAddress((address) counters->revoked_lock_entry_count_addr()));}bind(cas_label);return null_check_offset;
}void MacroAssembler::load_prototype_header(Register dst, Register src) {load_klass(dst, src);movptr(dst, Address(dst, Klass::prototype_header_offset()));
}

Hotspot 偏向锁BiasedLocking 源码解析相关推荐

  1. 源码解析-偏向锁撤销流程解读

    一.单个偏向锁的撤销 源码链接:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/runtim ...

  2. Hotspot 重量级锁ObjectMonitor(一) 源码解析

    目录 1.定义 2.TrySpin_VaryDuration 3.ObjectWaiter 4.EnterI 5.JavaThreadBlockedOnMonitorEnterState / OSTh ...

  3. Hotspot 重量级锁ObjectMonitor(二) 源码解析

    目录 1.AddWaiter / DequeueWaiter /DequeueSpecificWaiter 2.wait 3.notify 4.notifyAll 5.exit 6.try_enter ...

  4. Hotspot 垃圾回收之GenCollectedHeap 源码解析

    目录 1.定义 2.构造方法 / initialize / post_initialize 3.do_collection 4.do_full_collection 5.collect 6.VM_Ge ...

  5. Hotspot 垃圾回收之VM_Operation 源码解析

    目录 一.VM_Operation ​二.VMThread 1.定义 2.create / destroy 3.run / wait_for_vm_thread_exit 4.loop 5.VMThr ...

  6. Hotspot 对象引用Reference和Finalizer 源码解析

    目录 一.Reference 1.SoftReference / WeakReference / PhantomReference 2.定义 3.ReferenceHandler 4.Cleaner ...

  7. Redis进阶- Redisson分布式锁实现原理及源码解析

    文章目录 Pre 用法 Redisson分布式锁实现原理 Redisson分布式锁源码分析 redisson.getLock(lockKey) 的逻辑 redissonLock.lock()的逻辑 r ...

  8. Hotspot 垃圾回收之ReferenceProcessor(二) 源码解析

    目录 1.process_discovered_reflist 2.process_phaseJNI 3.process_discovered_references 4.preclean_discov ...

  9. Hotspot 垃圾回收之oop_iterate(二) 源码解析

    目录 1.java.lang.Class 1.1.Class实例中oop_size.klass等属性是哪来的? 1.2._offset_of_static_fields 1.3 为什么从_offset ...

  10. Android图案密码,手势锁源码解析

    Android图案密码解锁源码解析 Android Lock Pattern 源码解析  1. 介绍   1.1 关于 Android 的图案密码解锁,通过手势连接 3 * 3 的点矩阵绘制图案表示解 ...

最新文章

  1. R语言常用包分类总结
  2. matlab矩阵乘法与打印
  3. How is JerryMaster.view.xml being loaded in WebIDE local test environment
  4. matlab中的级数怎默算_matlab绘图小技巧-图像光滑数据取点
  5. python3写文件_python3 写文件问题
  6. matcaffe训练与测试
  7. 探码科技Baklib荣登36氪“2020影响未来的产品Great100”未来企服榜单
  8. 基于matlab的电池管理系统开发,基于MATLAB的锂电池组均衡仿真系统设计
  9. C++Primer第五版 第六章 课后习题答案
  10. 消费评价网 | 线上保险消费调查报告 虚假宣传多 捆绑销售坑人
  11. netty4.0源码分析之PooledByteBufAllocator
  12. 张博增是谁?为什么说他开启石墨烯的2.0时代!
  13. macOS Big Sur 11.7.1 (20G918) 正式版 ISO、PKG、DMG、IPSW 下载
  14. 如何提升数据思维能力?
  15. 机器学习中Batch Size、Iteration和Epoch的概念
  16. Linux4.19-获取IDT地址
  17. 图像去噪,深度学习去噪,普通方法
  18. 【洛谷4735】 最大异或和(可持久化01Trie)
  19. 在一起计时器_设计作品|最佳倒数计时器设计分析「附原型实例」
  20. 国家二级计算机考试大纲,计算机国家二级考试大纲.doc

热门文章

  1. 光敏传感器c语言,光敏传感器的工作原理及其应用
  2. 算法学习笔记 全源最短路径Johnson算法(用于稀疏图和有负边的图)
  3. 雷赛控制卡总线方式的坑
  4. c libxml2库的编译和使用
  5. shell(30) : 批量修改文件后缀
  6. XRD测试常见问题及解答(一)
  7. MDK Keil 使用STLink仿真,LOAD按钮为灰色解决方法
  8. Ognl表达式(根据Apache-Ognl文档直译)
  9. 【源码】高精度31波段音频均衡器
  10. 前后端分离微服务管理系统项目实战SaaS-HRM项目(一)——系统概述与环境搭建