Demo1

代码

package sync;

public class TestRunnable implements Runnable {
    static int count=0;
    @Override
    public void run() {
       count++;
    }
}

并发场景

  1. TestRunnable类被加载到方法区中,count作为类静态变量存储在方法区中
  2. 当并发时多个Tread调用TestRunnable类的Run方法去修改方法区中唯一的count
  3. a线程读取了count并自增还没写回去,b线程读取了原来的count进行计算,导致出现并发错误

Demo2

代码

package sync;

public class TestRunnable implements Runnable {
    static int count=0;
    static MyLock myLock=new MyLock();
    @Override
    public void run() {
        synchronized (myLock){
            count++;
        }
    }
}
package sync;

public class MyLock {
}

并发场景

①synchronized是锁,锁的是代码块还是括号中的参数?

参数。代码块在运行时数据区的虚拟机栈中,而一个线程有一个虚拟机栈,先不说代码块没法锁,如果锁了代码块,哪个线程不停滞。而锁括号中的实例对象可以做到看起来锁了代码块的效果,实例对象在运行时数据区的堆中,而每个实例对象在堆中的存储布局包括对象头、实例数据、对齐填充,而对象头又包括MarkWord、类型指针、数组长度,MarkWord中包括哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等信息,每个对象指针oop指向这个对象的对象头,里面记录了对象的布局等等信息。

每次访问代码块前,尝试获取括号中对象在堆中对象头的锁

  1. 如果获取到则执行代码块,执行完之后将锁释放掉;
  2. 如果获取不到则阻塞等待,如此可以起到互斥的效果。

②synchronized作为关键字无法看到源码,如何验证它的工作流程?

Java本身实现了许多锁,并不是只有synchronized关键字才能实现锁,这些锁可以看源码,但是synchronized不可以看源码,而通常的打印方式只能打印到对象的实例数据,怎么解决呢?

  • 此时jol包可以用来打印对象头,Maven仓库中可以看到Java Object Layout:Core
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.11</version>
</dependency>
public static void main(String[] args) {
        System.out.println(ClassLayout.parseInstance(???).toPrintable());//???替换为要打印的对象,toPrintable()方法是以表格形式打印
    }

1、开启指针压缩且有数组对象(-XX:+UseCompressedOops)

sync.MyLock 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) 47 c1 00 20 (01000111 11000001 00000000 00100000) (536920391)
12 4 int MyLock.num 1
16 4 int MyLock.gg 233
20 1 boolean MyLock.flag true
21 3   (alignment/padding gap)  
24 4 int[] MyLock.array [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
28 4   (loss due to the next object alignment)  

Instance size: 32 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

2、关闭指针压缩且有数组对象(-XX:-UseCompressedOops)

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) e0 36 52 17 (11100000 00110110 01010010 00010111) (391263968)
12 4   (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 4 int MyLock.num 1
20 4 int MyLock.gg 233
24 1 boolean MyLock.flag true
25 7   (alignment/padding gap)  
32 8 int[] MyLock.array [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Instance size: 40 bytes Space losses: 7 bytes internal + 0 bytes external = 7 bytes total

3、markOop.hpp源码注释

//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
//
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
//未持有锁:
sync.MyLock 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)             47 c1 00 20 (01000111 11000001 00000000 00100000) (536920391)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

//已持有锁:
sync.MyLock object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                VALUE
      0     4        (object header)            40 f6 d4 02 (01000000 11110110 11010100 00000010) (47511104)
      4     4        (object header)            00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)            47 c1 00 20 (01000111 11000001 00000000 00100000) (536920391)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
  • 普通对象前25位是空的,而打印出来的00000001 00000000 00000000 00000000,说明采用的是小端存储(不同的芯片有不同的规定,不同操作系统又可以进行软件上不同的实现,具体情况具体分析)
  • hashCode一开始是没有的,只有调用hashCode()方法才会进行计算,而hashCode()是native方法,源码需要在/hotspot/src/share/vm/
  • 未持有锁时,锁标志位为01;对象被锁住之后,标志位为00
  • 判断某个线程是否持有某个特定的锁,可以调用Tread.holdsLock(Object object)方法判断,原理就是当对象被锁定时,有54位的JavaThread,关联着JVM的Monitor,其中记录了Owner信息

小发现

  • 对象头长度在开启和关闭指针压缩时有变化
  • 关闭指针压缩后,数组指针正好是8B,为了对齐,即保证Offset%Size==0,为了保证对齐,可能会出现两个padding
  • 对齐包含两层含义:
    1. 每个对象的起始地址是8B的整数倍,即每个对象大小是8B的整数倍
    2. 单个对象中的每个数据元素的Offest%Size==0
  • 在无继承关系时数据的顺序为doubles/longs、ints、shorts、bytes/booleans、oop,之后按照声明顺序排列
  • OpenJDK官方文档中写道对象头由两个字长组成,第一个字长为MarkWord、第二个字长为Klass Pointer,但是开启指针压缩时对象头为12B,而不是16B(64位虚拟机一个字长为8B)。此时可以阅读OpenJDK源码下载,阅读hotspot/src下的代码,发现MarkWord固定为64bit,开启指针压缩时把KlassPointer用32位表示而不是64位,可以提高GC运行效率,此时最大内存为32GB(232*8B=32GB)
  • 虚拟机参数可以在虚拟机参数文档中查询。