java源码剖析: 对象内存布局JVM锁以及优化

Posted 只会一点java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java源码剖析: 对象内存布局JVM锁以及优化相关的知识,希望对你有一定的参考价值。

 很多人一提到锁,自然第一个想到了synchronized,但一直不懂源码实现,现特地追踪到C++层来剥开synchronized的面纱。

网上的很多描述大都不全,让人看了不够爽,看完本章,你将彻底了解synchronized的核心原理。

一、启蒙知识预热

开启本文之前先介绍2个概念

1.1.cas操作

为了提高性能,JVM很多操作都依赖CAS实现,一种乐观锁的实现。本文锁优化中大量用到了CAS,故有必要先分析一下CAS的实现。

CAS:Compare and Swap。

JNI来完成CPU指令的操作:

unsafe.compareAndSwapInt(this, valueOffset, expect, update);

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。如果A=V,那么把B赋值给V,返回V;如果A!=V,直接返回V。

打开源码:openjdk\\hotspot\\src\\oscpu\\windowsx86\\vm\\ atomicwindowsx86.inline.hpp,如下图:0

os::is_MP()  这个是runtime/os.hpp,实际就是返回是否多处理器,源码如下:

 

如上面源代码所示(看第一个int参数即可),LOCK_IF_MP:会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。

 

1.2.对象头

HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

HotSpot虚拟机的对象头(Object Header)包括两部分信息:

第一部分"Mark Word":用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等.

第二部分"Klass Pointer":对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。(数组,对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。 )

32位的HotSpot虚拟机对象头存储结构:(下图摘自网络)

                                                              图1 32位的HotSpot虚拟机对象头Mark Word组成

为了证实上图的正确性,这里我们看openJDK--》hotspot源码markOop.hpp,虚拟机对象头存储结构:

                                             图2 HotSpot源码markOop.hpp中注释

单词解释:

hash: 保存对象的哈希码
age: 保存对象的分代年龄
biased_lock: 偏向锁标识位
lock: 锁状态标识位
JavaThread*: 保存持有偏向锁的线程ID
epoch: 保存偏向时间戳

上图中有源码中对锁标志位这样枚举

1 enum {   locked_value             = 0,//00 轻量级锁
2          unlocked_value           = 1,//01 无锁
3          monitor_value            = 2,//10 监视器锁,也叫膨胀锁,也叫重量级锁
4          marked_value             = 3,//11 GC标记
5          biased_lock_pattern      = 5 //101 偏向锁
6   };

下面是源码注释:

                                        图3 HotSpot源码markOop.hpp中锁标志位注释

看图3,不管是32/64位JVM,都是1bit偏向锁+2bit锁标志位。上面红框是偏向锁(第一行是指向线程的显示偏向锁,第二行是匿名偏向锁)对应枚举biased_lock_pattern,下面红框是轻量级锁、无锁、监视器锁、GC标记,分别对应上面的前4种枚举。我们甚至能看见锁标志11时,是GC的markSweep(标记清除算法)使用的。(这里就不再拓展了)

对象头中的Mark Word,synchronized源码实现就用了Mark Word来标识对象加锁状态。

二、JVM中synchronized锁实现原理(优化)

大家都知道java中锁synchronized性能较差,线程会阻塞。本节将以图文形式来描述JVM的synchronized锁优化。

在jdk1.6中对锁的实现引入了大量的优化来减少锁操作的开销:

锁粗化(Lock Coarsening):将多个连续的锁扩展成一个范围更大的锁,用以减少频繁互斥同步导致的性能损耗。

锁消除(Lock Elimination):JVM及时编译器在运行时,通过逃逸分析,如果判断一段代码中,堆上的所有数据不会逃逸出去从来被其他线程访问到,就可以去除这些锁。

轻量级锁(Lightweight Locking):JDK1.6引入。在没有多线程竞争的情况下避免重量级互斥锁,只需要依靠一条CAS原子指令就可以完成锁的获取及释放。

偏向锁(Biased Locking):JDK1.6引入。目的是消除数据再无竞争情况下的同步原语。使用CAS记录获取它的线程。下一次同一个线程进入则偏向该线程,无需任何同步操作。

适应性自旋(Adaptive Spinning):为了避免线程频繁挂起、恢复的状态切换消耗。产生了忙循环(循环时间固定),即自旋。JDK1.6引入了自适应自旋。自旋时间根据之前锁自旋时间和线程状态,动态变化,用以期望能减少阻塞的时间。

 锁升级:偏向锁--》轻量级锁--》重量级锁

2.1.偏向锁

  按照之前的HotSpot设计,每次加锁/解锁都会涉及到一些CAS操作(比如对等待队列的CAS操作),CAS操作会延迟本地调用,因此偏向锁的想法是一旦线程第一次获得了监视对象,之后让监视对象“偏向”这个线程,之后的多次调用则可以避免CAS操作。
  简单的讲,就是在锁对象的对象头(开篇讲的对象头数据存储结构)中有个ThreaddId字段,这个字段如果是空的,第一次获取锁的时候,就将自身的ThreadId写入到锁的ThreadId字段内,将锁头内的是否偏向锁的状态位置1.这样下次获取锁的时候,直接检查ThreadId是否和自身线程Id一致,如果一致,则认为当前线程已经获取了锁,因此不需再次获取锁,略过了轻量级锁和重量级锁的加锁阶段。提高了效率。
注意:当锁有竞争关系的时候,需要解除偏向锁,进入轻量级锁。

每一个线程在准备获取共享资源时:

第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁”.跳过轻量级锁直接执行同步体。

获得偏向锁如下图:

 

2.2.轻量级锁和重量级锁

如上图所示:

第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,之前线程将Markword的内容置为空。

第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord.

第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋.

第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于轻量级锁的状态,如果自旋失败 第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己.

注意点:JVM加锁流程

偏向锁--》轻量级锁--》重量级锁

从左往右可以升级,从右往左不能降级

三、从C++源码看synchronized

前两节讲了synchronized锁实现原理,这一节我们从C++源码来剖析synchronized。

3.1 同步和互斥

同步:多个线程并发访问共享资源时,保证同一时刻只有一个(信号量可以多个)线程使用。

实现同步的方法有很多,常见四种如下:

1)临界区(CriticalSection,又叫关键段):通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。进程内可用。

2)互斥量:互斥量用于线程的互斥。只能为0/1一个互斥量只能用于一个资源的互斥访问,可跨进程使用。

3)信号量:信号线用于线程的同步。可以为非负整数可实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。可跨进程使用。

4)事件:用来通知线程有一些事件已发生,从而启动后继任务的开始,可跨进程使用。

synchronized的底层实现就用到了临界区和互斥锁(重量级锁的情况下)这两个概念。

3.2 synchronized  C++源码

重点来了,之前在第一节中的图1,看过了对象头Mark Word。现在我们从C++源码来剖析具体的数据结构和获取释放锁的过程。

2.2.1 C++中的监视器锁数据结构

oopDesc--继承-->markOopDesc--方法monitor()-->ObjectMonitor-->enter、exit 获取、释放锁

1.oopDesc

openjdk\\hotspot\\src\\share\\vm\\oops\\oop.hpp下oopDesc类是JVM对象的顶级基类,故每个object都包含markOop。如下图所示:

 1 class oopDesc {
 2   friend class VMStructs;
 3  private:
 4   volatile markOop  _mark;//markOop:Mark Word标记字段
 5   union _metadata {
 6     Klass*      _klass;//对象类型元数据的指针
 7     narrowKlass _compressed_klass;
 8   } _metadata;
 9 
10   // Fast access to barrier set.  Must be initialized.
11   static BarrierSet* _bs;
12 
13  public:
14   markOop  mark() const         { return _mark; }
15   markOop* mark_addr() const    { return (markOop*) &_mark; }
16 
17   void set_mark(volatile markOop m)      { _mark = m;   }
18 
19   void    release_set_mark(markOop m);
20   markOop cas_set_mark(markOop new_mark, markOop old_mark);
21 
22   // Used only to re-initialize the mark word (e.g., of promoted
23   // objects during a GC) -- requires a valid klass pointer
24   void init_mark();
25 
26   Klass* klass() const;
27   Klass* klass_or_null() const volatile;
28   Klass** klass_addr();
29   narrowKlass* compressed_klass_addr();
....省略...
}

2.markOopDesc类

openjdk\\hotspot\\src\\share\\vm\\oops\\markOop.hpp下markOopDesc继承自oopDesc,并拓展了自己的方法monitor(),如下图

1 ObjectMonitor* monitor() const {
2     assert(has_monitor(), "check");
3     // Use xor instead of &~ to provide one extra tag-bit check.
4     return (ObjectMonitor*) (value() ^ monitor_value);
5   }

该方法返回一个ObjectMonitor*对象指针。

其中value()这样定义:

 1 uintptr_t value() const { return (uintptr_t) this; }

可知:将this转换成一个指针宽度的整数(uintptr_t),然后进行"异或"位操作。

monitor_value是常量
1 enum {   locked_value             = 0,//00轻量级锁
2          unlocked_value           = 1,//01无锁
3          monitor_value            = 2,//10监视器锁,又叫重量级锁
4          marked_value             = 3,//11GC标记
5          biased_lock_pattern      = 5 //101偏向锁
6   };
指针低2位00,异或10,结果还是10.(拿一个模板为00的数,异或一个二位数=数本身。因为异或:“相同为0,不同为1”.操作)

3.ObjectMonitor类

在HotSpot虚拟机中,最终采用ObjectMonitor类实现monitor。

openjdk\\hotspot\\src\\share\\vm\\runtime\\objectMonitor.hpp源码如下:

 1 ObjectMonitor() {
 2     _header       = NULL;//markOop对象头
 3     _count        = 0;
 4     _waiters      = 0,//等待线程数
 5     _recursions   = 0;//重入次数
 6     _object       = NULL;//监视器锁寄生的对象。锁不是平白出现的,而是寄托存储于对象中。
 7     _owner        = NULL;//指向获得ObjectMonitor对象的线程或基础锁
 8     _WaitSet      = NULL;//处于wait状态的线程,会被加入到wait set;
 9     _WaitSetLock  = 0 ;
10     _Responsible  = NULL ;
11     _succ         = NULL ;
12     _cxq          = NULL ;
13     FreeNext      = NULL ;
14     _EntryList    = NULL ;//处于等待锁block状态的线程,会被加入到entry set;
15     _SpinFreq     = 0 ;
16     _SpinClock    = 0 ;
17     OwnerIsThread = 0 ;// _owner is (Thread *) vs SP/BasicLock
18     _previous_owner_tid = 0;// 监视器前一个拥有者线程的ID
19   }

每个线程都有两个ObjectMonitor对象列表,分别为free和used列表,如果当前free列表为空,线程将向全局global list请求分配ObjectMonitor。

ObjectMonitor对象中有两个队列:_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表;

2.获取锁流程

 synchronized关键字修饰的代码段,在JVM被编译为monitorenter、monitorexit指令来获取和释放互斥锁.。

解释器执行monitorenter时会进入到InterpreterRuntime.cppInterpreterRuntime::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、JavaThread thread指向java中的当前线程;
2、BasicObjectLock基础对象锁:包含一个BasicLock和一个指向Object对象的指针oop。

openjdk\\hotspot\\src\\share\\vm\\runtime\\basicLock.hpp中BasicObjectLock类源码如下:
 1 class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
 2   friend class VMStructs;
 3  private:
 4   BasicLock _lock;                                    // the lock, must be double word aligned
 5   oop       _obj;                                     // object holds the lock;
 6 
 7  public:
 8   // Manipulation
 9   oop      obj() const                                { return _obj;  }
10   void set_obj(oop obj)                               { _obj = obj; }
11   BasicLock* lock()                                   { return &_lock; }
12 
13   // Note: Use frame::interpreter_frame_monitor_size() for the size of BasicObjectLocks
14   //       in interpreter activation frames since it includes machine-specific padding.
15   static int size()                                   { return sizeof(BasicObjectLock)/wordSize; }
16 
17   // GC support
18   void oops_do(OopClosure* f) { f->do_oop(&_obj); }
19 
20   static int obj_offset_in_bytes()                    { return offset_of(BasicObjectLock, _obj);  }
21   static int lock_offset_in_bytes()                   { return offset_of(BasicObjectLock, _lock); }
22 };

3、BasicLock类型_lock对象主要用来保存:指向Object对象的对象头数据;

basicLock.hpp中BasicLock源码如下:
 1 class BasicLock VALUE_OBJ_CLASS_SPEC {
 2   friend class VMStructs;
 3  private:
 4   volatile markOop _displaced_header;//markOop是不是很熟悉?1.2节中讲解对象头时就是分析的markOop源码
 5  public:
 6   markOop      displaced_header() const               { return _displaced_header; }
 7   void         set_displaced_header(markOop header)   { _displaced_header = header; }
 8 
 9   void print_on(outputStream* st) const;
10 
11   // move a basic lock (used during deoptimization
12   void move_to(oop obj, BasicLock* dest);
13 
14   static int displaced_header_offset_in_bytes()       { return offset_of(BasicLock, _displaced_header); }
15 };

偏向锁的获取ObjectSynchronizer::fast_enter

在HotSpot中,偏向锁的入口位于openjdk\\hotspot\\src\\share\\vm\\runtime\\synchronizer.cpp文件的ObjectSynchronizer::fast_enter函数:

 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  //轻量级锁
15  slow_enter (obj, lock, THREAD) ;
16 }
偏向锁的获取由BiasedLocking::revoke_and_rebias方法实现,由于实现比较长,就不贴代码了,实现逻辑如下:
1、通过markOop mark = obj->mark()获取对象的markOop数据mark,即对象头的Mark Word;
2、判断mark是否为可偏向状态,即mark的偏向锁标志位为 1,锁标志位为 01
3、判断mark中JavaThread的状态:如果为空,则进入步骤(4);如果指向当前线程,则执行同步代码块;如果指向其它线程,进入步骤(5);
4、通过CAS原子指令设置mark中JavaThread为当前线程ID,如果执行CAS成功,则执行同步代码块,否则进入步骤(5);
5、如果执行CAS失败,表示当前存在多个线程竞争锁,当达到全局安全点(safepoint),获得偏向锁的线程被挂起,撤销偏向锁,并升级为轻量级,升级完成后被阻塞在安全点的线程继续执行同步代码块;
偏向锁的撤销

只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,偏向锁的撤销由BiasedLocking::revoke_at_safepoint方法实现:

 

 1 void BiasedLocking::revoke_at_safepoint(Handle h_obj) {
 2   assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");//校验全局安全点
 3   oop obj = h_obj();
 4   HeuristicsResult heuristics = update_heuristics(obj, false);
 5   if (heuristics == HR_SINGLE_REVOKE) {
 6     revoke_bias(obj, false, false, NULL);
 7   } else if ((heuristics == HR_BULK_REBIAS) ||
 8              (heuristics == HR_BULK_REVOKE)) {
 9     bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);
10   }
11   clean_up_cached_monitor_info();
12 }

1、偏向锁的撤销动作必须等待全局安全点;
2、暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态;
3、撤销偏向锁,恢复到无锁(标志位为 01)或轻量级锁(标志位为 00)的状态;

偏向锁在Java 1.6之后是默认启用的,但在应用程序启动几秒钟之后才激活,可以使用-XX:BiasedLockingStartupDelay=0参数关闭延迟,如果确定应用程序中所有锁通常情况下处于竞争状态,可以通过XX:-UseBiasedLocking=false参数关闭偏向锁。

轻量级锁的获取
当关闭偏向锁功能,或多个线程竞争偏向锁导致偏向锁升级为轻量级锁,会尝试获取轻量级锁,其入口位于ObjectSynchronizer::slow_enter
 1 void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
 2   markOop mark = obj->mark();
 3   assert(!mark->has_bias_pattern(), "should not see bias pattern here");
 4 
 5   if (mark->is_neutral()) {//是否为无锁状态001
 6     // Anticipate successful CAS -- the ST of the displaced mark must
 7     // be visible <= the ST performed by the CAS.
 8     lock->set_displaced_header(mark);
 9     if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {//CAS成功,释放栈锁
10       TEVENT (slow_enter: release stacklock) ;
11       return ;
12     }
13     // Fall through to inflate() ...
14   } else
15   if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
16     assert(lock != mark->locker(), "must not re-lock the same lock");
17     assert(lock != (BasicLock*)obj->mark(), "don\'t relock with same BasicLock");
18     lock->set_displaced_header(NULL);
19     return;
20   }
21 
22 #if 0
23   // The following optimization isn\'t particularly useful.
24   if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
25     lock->set_displaced_header (NULL) ;
26     return ;
27   }
28 #endif
29 
30   // The object header will never be displaced to this lock,
31   // so it does not matter what the value is, except that it
32   // must be non-zero to avoid looking like a re-entrant lock,
33   // and must not look locked either.
34   lock->set_displaced_header(markOopDesc::unused_mark());
35   ObjectSynchronizer::inflate(THREAD, obj())->JVM技术专题「原理专题」深入剖析Java对象内存分配及跨代引用分析

java对象内存布局

Java对象的内存布局以及对象的访问定位

JVM--对象的内存布局

Java对象的内存布局以及对象的访问定位

二:JVM内存模型深度剖析与优化