非数组对象在内存中的布局:

  • markword 8bytes
  • class pointer 4bytes(开启压缩后)
  • 实例数据
  • padding(长度需是8的倍数)

数组对象还多一个数组长度(4bytes)

image.png
对象头包括markword和classpointer,其中markword记录了synchronized锁的信息。

  1. // Bit-format of an object header (most significant first, big endian layout below):
  2. //
  3. // 32 bits:
  4. // --------
  5. // hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
  6. // JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
  7. // size:32 ------------------------------------------>| (CMS free block)
  8. // PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
  9. //
  10. // 64 bits:
  11. // --------
  12. // unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
  13. // JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
  14. // PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
  15. // size:64 ----------------------------------------------------->| (CMS free block)
  16. //
  17. // unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
  18. // JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
  19. // narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
  20. // unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

Synchronized 的实现

  • 源码级别:synchronized(o)
  • 字节码级别:monitorenter、moniterexit
  • JVM级别(Hotpot)

InterpreterRuntime:: monitorenter方法

  1. IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
  2. #ifdef ASSERT
  3. thread->last_frame().interpreter_frame_verify_monitor(elem);
  4. #endif
  5. if (PrintBiasedLockingStatistics) {
  6. Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
  7. }
  8. Handle h_obj(thread, elem->obj());
  9. assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
  10. "must be NULL or an object");
  11. if (UseBiasedLocking) {
  12. // Retry fast entry if bias is revoked to avoid unnecessary inflation
  13. ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  14. } else {
  15. ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
  16. }
  17. assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
  18. "must be NULL or an object");
  19. #ifdef ASSERT
  20. thread->last_frame().interpreter_frame_verify_monitor(elem);
  21. #endif
  22. IRT_END
  1. void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
  2. if (UseBiasedLocking) {
  3. if (!SafepointSynchronize::is_at_safepoint()) {
  4. BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
  5. if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
  6. return;
  7. }
  8. } else {
  9. assert(!attempt_rebias, "can not rebias toward VM thread");
  10. BiasedLocking::revoke_at_safepoint(obj);
  11. }
  12. assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
  13. }
  14. slow_enter (obj, lock, THREAD) ;
  15. }
  16. void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  17. markOop mark = obj->mark();
  18. assert(!mark->has_bias_pattern(), "should not see bias pattern here");
  19. if (mark->is_neutral()) {
  20. // Anticipate successful CAS -- the ST of the displaced mark must
  21. // be visible <= the ST performed by the CAS.
  22. lock->set_displaced_header(mark);
  23. if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
  24. TEVENT (slow_enter: release stacklock) ;
  25. return ;
  26. }
  27. // Fall through to inflate() ...
  28. } else
  29. if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
  30. assert(lock != mark->locker(), "must not re-lock the same lock");
  31. assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
  32. lock->set_displaced_header(NULL);
  33. return;
  34. }
  35. #if 0
  36. // The following optimization isn't particularly useful.
  37. if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
  38. lock->set_displaced_header (NULL) ;
  39. return ;
  40. }
  41. #endif
  42. // The object header will never be displaced to this lock,
  43. // so it does not matter what the value is, except that it
  44. // must be non-zero to avoid looking like a re-entrant lock,
  45. // and must not look locked either.
  46. lock->set_displaced_header(markOopDesc::unused_mark());
  47. ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
  48. }

锁升级过程

在jdk早起,synchronized称为重量级锁,因为获取锁需要通过向kernel申请(ox80调用),用户态调用内核态指令。通过升级,synchronized需要经过锁升级过程才会变为重量级锁。
image.png
image.png

普通对象——>轻量级锁—>重量级锁

public class Code01_Normal {
    public static void main(String[] args) {
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable(o));

        synchronized (o){
            System.out.println(ClassLayout.parseInstance(o).toPrintable(o));
        }
    }
}

image.png
0x0000000000000001
—>0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 -》无锁状态
0x0000006a178ff468
—>0000 0000 0000 0000 0000 0000 0110 1010 0001 0111 1000 1111 1111 0100 0110 1000 -》 轻量级锁状态

为什么有自旋锁还需要重量级锁?自旋锁什么时候升级为重量级锁?
线程自旋等待是需要消耗CPU资源的,如果等待锁的时间长或者自选线程很多,那么CPU会被大量消耗。
重量级锁是有等待队列的,拿不到锁的线程进入等待队列,不需要消耗CPU资源。因此还是需要重量级锁。

等待线程自旋次数超过10次或者等待线程数量大于一半CPU核数,那么轻量级锁会升级为重量级锁。

偏向锁是否一定比自旋锁效率高?
不一定。在明确知道有多线程竞争时,偏向锁可能会涉及锁撤销过程,消耗CPU资源;如果直接使用自旋锁,效率会比使用偏向锁效率高。所以JVM启动过程,会有很多线程竞争(明确),所以默认情况启动时不打开偏向锁,过一段儿时间再打开。(默认延迟4s)

匿名偏向——>偏向锁——>轻量级锁——>重量级锁

可通过设置-XX:BiasedLockingStartupDelay=0,即JVM启动时开始偏向锁,普通对象——》轻量级锁
image.png
(1)打开偏向锁,new出来的对象,默认就是一个可偏向匿名对象101
0x0000000000000005 -》
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0101

(2)若有线程上锁,把markword的线程ID改为自己线程ID的过程
0x000002609e79e805 ——》
0000 0000 0000 0000 0000 0010 0110 0000 1001 1110 0111 1001 1110 1000 0000 0101

(3)若有线程竞争,撤销偏向锁,升级为自旋锁(轻量级锁)
线程在自己的线程栈生成LockRecord ,用CAS操作将markword设置为指向自己这个线程的LR的指针,设置成功者得到锁。

(4)若竞争加剧,自旋锁升级为重量级锁
竞争加剧:有线程超过10次自旋, -XX:PreBlockSpin, 或者自旋线程数超过CPU核数的一半, 1.6之后,加入自适应自旋 Adapative Self Spinning , JVM自己控制

升级重量级锁:-> 向操作系统申请资源,linux mutex , CPU从3级-0级系统调用,线程挂起,进入等待队列,等待操作系统的调度,然后再映射回用户空间
image.png
注:(1)如果计算过对象的hashCode,则对象无法进入偏向状态。
(2)轻量级锁重量级锁的hashCode存在线程栈中:轻量级锁的LR(LockRecord )中,或是代表重量级锁的ObjectMonitor的成员中

锁重入

sychronized是可重入锁,重入次数必须记录,因为要解锁几次必须得对应
偏向锁 自旋锁 -> 线程栈 -> LR + 1
重量级锁 -> ? ObjectMonitor字段上

锁消除 lock eliminate

public void add(String str1,String str2){
    StringBuffer sb = new StringBuffer();
    sb.append(str1).append(str2);
 }

我们都知道 StringBuffer 是线程安全的,因为它的关键方法都是被 synchronized 修饰过的,但我们看上面这段代码,我们会发现,sb 这个引用只会在 add 方法中使用,不可能被其它线程引用(因为是局部变量,栈私有),因此 sb 是不可能共享的资源,JVM 会自动消除 StringBuffer 对象内部的锁。

锁粗化 lock coarsening

public String test(String str){

    int i = 0;
    StringBuffer sb = new StringBuffer():
    while(i < 100){
        sb.append(str);
        i++;
    }
    return sb.toString():
 }

JVM 会检测到这样一连串的操作都对同一个对象加锁(while 循环内 100 次执行 append,没有锁粗化的就要进行 100 次加锁/解锁),此时 JVM 就会将加锁的范围粗化到这一连串的操作的外部(比如 while 虚幻体外),使得这一连串操作只需要加一次锁即可。