Demo1
代码
package sync;
public class TestRunnable implements Runnable {
static int count=0;
@Override
public void run() {
count++;
}
}
并发场景
- TestRunnable类被加载到方法区中,count作为类静态变量存储在方法区中
- 当并发时多个Tread调用TestRunnable类的Run方法去修改方法区中唯一的count
- 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指向这个对象的对象头,里面记录了对象的布局等等信息。
每次访问代码块前,尝试获取括号中对象在堆中对象头的锁
- 如果获取到则执行代码块,执行完之后将锁释放掉;
- 如果获取不到则阻塞等待,如此可以起到互斥的效果。
②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
- 对齐包含两层含义:
- 每个对象的起始地址是8B的整数倍,即每个对象大小是8B的整数倍
- 单个对象中的每个数据元素的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)
- 虚拟机参数可以在虚拟机参数文档中查询。