我们知道,当出现race condition的时候,应用就不会同步。
举个例子:
package sync;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import static org.junit.Assert.assertEquals;
public class OceanSyncMethods {
private int sum = 0;
public void setSum(int sum) {
this.sum = sum;
}
public int getSum() {
return sum;
}
public void calculate(){
setSum(getSum()+1);
}
@Test
public void givenMultiThread_whenNonSyncMethod() throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(3);
OceanSyncMethods summation = new OceanSyncMethods();
IntStream.range(0,1000)
.forEach(count -> service.submit(summation::calculate));
service.awaitTermination(1000, TimeUnit.MILLISECONDS);
assertEquals(1000,summation.getSum());
}
}
我们有一个线程池,里面固定3条线程。这些线程执行calculate方法1000次。
按理说,summation.getSum()
会是1000,但是,如果有线程安全的问题,那么有些值就会损失掉。
测试的结果往往不是1000:
java.lang.AssertionError:
Expected :1000
Actual :997
我们当然可以用synchronize关键字来完成同步。
public synchronized void calculate(){
setSum(getSum()+1);
}
既然是给实例方法加锁,我们必须知道是锁在谁身上。最终是实例(summation)来调用calculate方法,所以锁的是实例(对象)。它的含义是,类的每个实例只有一条线程能够执行该方法。
package sync;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import static org.junit.Assert.assertEquals;
public class OceanSyncMethods {
private static int staticSum = 0;
public static synchronized void calculate(){
staticSum = staticSum + 1;
}
@Test
public void givenMultiThread_whenNonSyncMethod() throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(3);
IntStream.range(0,1000)
.forEach(count -> service.submit(OceanSyncMethods::calculate));
service.awaitTermination(5000, TimeUnit.MILLISECONDS);
assertEquals(1000,OceanSyncMethods.staticSum);
}
}
锁在静态方法上也是一种锁,那么,此时锁的是谁呢?
锁的是类对象。因为JVM的一个类有且只有一个类对象,因此每个类只有一个线程能够在synchronize静态方法中执行代码。
package sync;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import static org.junit.Assert.assertEquals;
public class OceanSyncMethods {
private int count;
public void setCount(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void performSynchronizedTask(){
synchronized (this){
setCount(getCount()+1);
}
}
@Test
public void givenMultiThread_whenNonSyncMethod() throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(3);
OceanSyncMethods oceanSyncMethods = new OceanSyncMethods();
IntStream.range(0,1000)
.forEach(count -> service.submit(oceanSyncMethods::performSynchronizedTask));
service.awaitTermination(5000, TimeUnit.MILLISECONDS);
assertEquals(1000,oceanSyncMethods.getCount());
}
}
有时候锁一整个方法效率不高,我们实际只需对有线程安全顾虑的代码加锁即可,因此有了synchronize代码块。
上面我们锁的是this,这个this是个monitor object。每个monitor object只有一个线程可以执行synchronize代码块中的内容。
那么什么是一个monitor呢?monitor就像一个building,synchronize代码块就像一个special room,monitor可以确保只有一个线程进入这个special room,以此来保护data。
注意,如果方法是静态的,那就锁class对象。这时候,这个class会变成monitor。
package sync;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import static org.junit.Assert.assertEquals;
public class OceanSyncMethods {
private static int count = 0;
public static void performSynchronizedTask() {
synchronized (OceanSyncMethods.class) {
count = count+1;
}
}
@Test
public void givenMultiThread_whenNonSyncMethod() throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(3);
IntStream.range(0, 1000)
.forEach(count -> service.submit(OceanSyncMethods::performSynchronizedTask));
service.awaitTermination(5000, TimeUnit.MILLISECONDS);
assertEquals(1000,OceanSyncMethods.count );
}
}
我们要把monitor讲清楚。
monitor就像一个building,里面有一个special room,它被一次只被一个线程占有。
有几个属于:进入这个building叫做“enter the monitor”,进入这个special room叫做“acquire the monitor”,占用这个房间叫做“own the monitor”,离开这个房间叫做"release the monitor",离开这个building叫做“exit the monitor”。
还有一个叫做“monitor region”,就是要执行的代码。
当一个thread来到monitor region的开始位置,它会被放在“entry set”,这就像是monitor building的一个走廊。
在线程合作的情况下,当前持有monitor的thread可以用wait来让自己悬停,这样它就会进入“wait set”。
ok。
当一个thread来到monitor region的开头部位,它就要穿过①号门,进入entry set。如果当前没有其他thread等在entry set并且也没有thread持有monitor,它就穿过门②acquire the monitor。
然后这个thread就可以执行monitor region了。
如果active thread release the monitor,那就过门⑤。如果它wait,那就穿过门③。
如果它在release前没有notify,那么只有entry set中的线程才能acquire the monitor。如果notify了,那么entry set和wait set中的线程是一个竞争关系。
好。
为什么要讲monitor?
在这个问题之前,我们要问:为什么要加锁?
实例变量是存在堆中的,类变量是存在方法区中的,这两个运行时数据区是被所有线程共享的。所以要锁。
而对于局部变量,它是存在栈中的,是线程独有的。
那么,monitor究竟是什么?
每个对象和类都与一个monitor关联。对于对象,monitor保护对象的实例变量;对于类,monitor保护类的类变量。
所以,当我们说锁住一个对象时,其实是在说我们拿到了和该对象相关联的monitor。
那么又是怎么锁类的呢?jvm加载磁盘中的.class文件后,会创建一个java.lang.Class的实例,当你锁一个类的时候,你其实是在锁类对象。
我们写一段代码来观察monitor:
package sync;
public class OceanSync {
private int[] intArray = new int[10];
void reverseOrder(){
synchronized (this){
int halfWay = intArray.length/2;
for (int i = 0; i < halfWay; ++i) {
int upperIndex = intArray.length - i - 1;
int save = intArray[upperIndex];
intArray[upperIndex] = intArray[i];
intArray[i] = save;
}
}
}
public static void main(String[] args) {
OceanSync oceanSync = new OceanSync();
oceanSync.reverseOrder();
}
}
jvm指令:
void reverseOrder();
descriptor: ()V
flags:
Code:
stack=4, locals=7, args_size=1
/**
*将被锁对象的引用(this)存入局部变量表
*/
0: aload_0
1: dup
2: astore_1
//获取this的锁
3: monitorenter
/**
*synchronize块起于此,线程没有拿到锁是不会往下执行的
*/
4: aload_0
5: getfield #2 // Field intArray:[I
8: arraylength
9: iconst_2
10: idiv
11: istore_2
12: iconst_0
13: istore_3
14: iload_3
15: iload_2
16: if_icmpge 66
19: aload_0
20: getfield #2 // Field intArray:[I
23: arraylength
24: iload_3
25: isub
26: iconst_1
27: isub
28: istore 4
30: aload_0
31: getfield #2 // Field intArray:[I
34: iload 4
36: iaload
37: istore 5
39: aload_0
40: getfield #2 // Field intArray:[I
43: iload 4
45: aload_0
46: getfield #2 // Field intArray:[I
49: iload_3
50: iaload
51: iastore
52: aload_0
53: getfield #2 // Field intArray:[I
56: iload_3
57: iload 5
59: iastore
60: iinc 3, 1
63: goto 14
66: aload_1
/**
*弹出引用,释放锁
*/
67: monitorexit
68: goto 78
71: astore 6
73: aload_1
74: monitorexit
75: aload 6
77: athrow
78: return
比方说,锁对象的话,是如何上锁的?
上不上锁我们肯定是有标识的,像flag一样的东西。比如一个对象上锁了,它有什么改变呢?
maven项目里加入依赖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
为了简便,我们的类都会极其简单。首先建一个自己的锁对象(因为我们要观察对象的状态)。
package sync;
public class MyLock {
boolean flag = true;
}
测试:
package sync;
import org.openjdk.jol.info.ClassLayout;
public class TestSync {
static MyLock myLock = new MyLock();
static void somePrint(){
synchronized (myLock){
System.out.println("Nothing important in the sync block");
}
}
public static void main(String[] args) {
System.out.println(Integer.toHexString(myLock.hashCode()));
System.out.println(ClassLayout.parseInstance(myLock).toPrintable());
somePrint();
}
}
我们用了synchronize代码块的方式加锁,并且锁的是MyLock对象。
我们在main方法中打印了myLock的hashcode,并且打印了对象布局:
45ee12a7
sync.MyLock object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 a7 12 ee (00000001 10100111 00010010 11101110) (-300767487)
4 4 (object header) 45 00 00 00 (01000101 00000000 00000000 00000000) (69)
8 4 (object header) 18 0a 89 14 (00011000 00001010 10001001 00010100) (344525336)
12 1 boolean MyLock.flag true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Nothing important in the sync block
Process finished with exit code 0
instance size是16bytes,它必须是8的倍数(64bit虚拟机中)。
这个对象由三部分组成:对象头,变量,对齐数据。
变量就是那个布尔值,占1byte。对齐数据占3bytes。object header占12bytes,也就是96bits。
什么是对象头?
object header
Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object’s layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.
对象头,就是每个由gc管理的堆对象的开头的一般结构。
oop是个指针,它指向对象头。
对象头包括堆对象的布局,类型,gc状态,同步状态,以及identityHashcode。
它由2个word组成。
哪两个word呢?
mark word
The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.
klass pointer
The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the “klass” contains a C++ style “vtable”
一个是mark word,一个是klass pointer。
我们要找的关于锁的状态的变量,在mark word中。
在64位JVM中,mark word占64bits,klass pointer占64bits。
但是我们刚才的打印中,klass pointer只占了32bits,因为开启了指针压缩。
我们最好看看klass pointer的作用。对于两个类,如果其他都一样,那么mark word就会相同,而klass pointer则不同,因为klass pointer是指向对象的引用。
package objectHeader;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class ClassPointer {
public static void main(String[] args) {
System.out.println(VM.current().details());
System.out.println(ClassLayout.parseInstance(new A()).toPrintable());
System.out.println("-------------------------------------------------------");
System.out.println(ClassLayout.parseInstance(new B()).toPrintable());
}
public static class A{
}
public static class B{
}
}
# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 0-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
objectHeader.ClassPointer$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 14 92 14 (00000000 00010100 10010010 00010100) (345117696)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
-------------------------------------------------------
objectHeader.ClassPointer$B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 80 20 92 14 (10000000 00100000 10010010 00010100) (345120896)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
我们关注普通状态下的mark word。
有32bits是hashcode,和我们打印的hashcode是一样的。
但是,它是从后往前倒着数的。
有4bits是年龄。堆是有分段年龄的,它不是一直在两个survivor区移来移去吗?到十五岁的时候就去老年代了。那么记录年龄的东西在哪里呢?
对,就是在mark word那4bits中,4bits最大表示的就是15岁。
还有1bit标识偏向锁。
剩余的2bits就是我们要找的锁的状态了。
以上的图很好地阐释了mark word的布局,以及不同对象状态的表示。
对象有5种状态:
普通、偏向锁、轻量级锁、重量级锁、为gc标记
图片的右半部份展示了标准的上锁过程。
只要一个对象没有上锁,最后两位的值就是01。当一个方法锁住了一个对象,object header中的相应信息就会存在当前栈帧的lock record中。
然后虚拟机就会试图加载mark word中指向lock record的指针,用的是cas操作。如果成功了,当前线程立马就会得到锁。由于lock records总是在word的边缘对齐,mark word的最后两位就会变成00,标志对象正被上锁。
如果该对象以前上过锁,从而导致cas操作失败,虚拟机会检查mark word是否指向当前线程的方法栈。如果是这样的情况,就说明这个线程已经占有这个对象并且可以继续安全地执行(可重入)。对于这样的迭代上锁的对象,lock record最初是0而非mark word的值。只有当两个线程当下都要锁同一个对象(race condition,竞争情况),轻量级锁就要膨胀成重量级锁(不得不调用os函数了),以此来管理等待的线程。
轻量级锁相比重量级锁要廉价,但是它们的性能会受到影响——每一个cas操作都必须原子性地执行在多核处理器上,即使每个对象上锁和解锁都只因为一个特别的线程。
在java6中,这种缺点被所谓 的“免费存储的偏向锁技术” 指出(java6之后的优化)。只有第一次获取锁的时候才执行原子性的cas操作,来把锁住线程的ID加载到mark word中。这个对象因此被说成是对于线程有偏向(歧视)的。未来的对于对象的同一条线程的上锁和解锁是不再需要原子操作的,或者是mark word的更新。甚至在栈上的lock record不会被初始化,因为它永远不会为一个偏向对象做检测。
我们用代码来看一下几种锁的情况:
轻量级锁的情况:
package objectHeader;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class ThinLocking {
public static void main(String[] args) {
System.out.println(VM.current().details());
final A a = new A();
ClassLayout layout = ClassLayout.parseInstance(a);
System.out.println("*** Fresh Object");
System.out.println(layout.toPrintable());
synchronized (a){
System.out.println("*** With the lock");
System.out.println(layout.toPrintable());
}
System.out.println("*** After the lock");
System.out.println(layout.toPrintable());
}
static class A{
}
}
因为只有一条线程,所以肯定是轻量级锁。
# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 0-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
*** Fresh Object
objectHeader.ThinLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 14 a6 14 (00000000 00010100 10100110 00010100) (346428416)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** With the lock
objectHeader.ThinLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 20 f1 87 02 (00100000 11110001 10000111 00000010) (42463520)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 14 a6 14 (00000000 00010100 10100110 00010100) (346428416)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** After the lock
objectHeader.ThinLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 14 a6 14 (00000000 00010100 10100110 00010100) (346428416)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
加了锁之后,mark word还是改变了的。此时mark word中的数据是指向复制的object header的引用,存在栈中。当释放锁时,mark word恢复原样。
重量级锁:
package objectHeader;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import java.util.concurrent.TimeUnit;
public class FatLocking {
public static void main(String[] args) {
System.out.println(VM.current().details());
final A a = new A();
ClassLayout layout = ClassLayout.parseInstance(a);
System.out.println("*** Fresh Object:");
System.out.println(layout.toPrintable());
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (a){
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
return;
}
}
}
});
t.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("*** Before the lock");
System.out.println(layout.toPrintable());
synchronized (a){
System.out.println("*** With the lock");
System.out.println(layout.toPrintable());
}
System.out.println("*** After the lock");
System.out.println(layout.toPrintable());
System.gc();
System.out.println("*** After System.gc()");
System.out.println(layout.toPrintable());
}
public static class A{
}
}
# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 0-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
*** Fresh Object:
objectHeader.FatLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 94 b4 14 (00000000 10010100 10110100 00010100) (347378688)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** Before the lock
objectHeader.FatLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 08 d4 57 (00000101 00001000 11010100 01010111) (1473513477)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 94 b4 14 (00000000 10010100 10110100 00010100) (347378688)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** With the lock
objectHeader.FatLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) aa 9a 44 55 (10101010 10011010 01000100 01010101) (1430559402)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 94 b4 14 (00000000 10010100 10110100 00010100) (347378688)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** After the lock
objectHeader.FatLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) aa 9a 44 55 (10101010 10011010 01000100 01010101) (1430559402)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 94 b4 14 (00000000 10010100 10110100 00010100) (347378688)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** After System.gc()
objectHeader.FatLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 09 00 00 00 (00001001 00000000 00000000 00000000) (9)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 94 b4 14 (00000000 10010100 10110100 00010100) (347378688)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
当锁被主线程拿到的时候,它已经“膨胀”了。
而且锁释放之后还是膨胀着。
gc后又“压缩”了。
偏向锁:
package objectHeader;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import java.util.concurrent.TimeUnit;
public class BiasedLocking
{
public static void main(String[] args) throws InterruptedException {
System.out.println(VM.current().details());
TimeUnit.SECONDS.sleep(6);
final A a = new A();
ClassLayout layout = ClassLayout.parseInstance(a);
System.out.println("*** Fresh Object");
System.out.println(layout.toPrintable());
synchronized (a){
System.out.println("*** With the lock");
System.out.println(layout.toPrintable());
}
System.out.println("After the lock");
System.out.println(layout.toPrintable());
}
public static class A{}
}
# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 0-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
*** Fresh Object
objectHeader.BiasedLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 94 a6 14 (00000000 10010100 10100110 00010100) (346461184)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*** With the lock
objectHeader.BiasedLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f0 48 02 (00000101 11110000 01001000 00000010) (38334469)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 94 a6 14 (00000000 10010100 10100110 00010100) (346461184)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
After the lock
objectHeader.BiasedLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f0 48 02 (00000101 11110000 01001000 00000010) (38334469)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 94 a6 14 (00000000 10010100 10100110 00010100) (346461184)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
当锁释放之后,mark word还是没有改变,因为当前mark word中包含着对象偏向的线程的引用。
参考:https://wiki.openjdk.java.net/display/HotSpot/Synchronization