一、Java 8 大原子操作(目前这种说法已弃用)
参考资料 《深入理解java虚拟机》page: 364
指令 | 作用区 | 解释 |
---|---|---|
lock | 主内存 | 标识变量为线程独占 |
unlock | 主内存 | 解锁 |
read | 主内存 | 读到工作内存 |
write | 主内存 | 写到主内存 |
load | 工作内存 | read后的值放到线程本地变量副本 |
store | 工作内存 | 存储值到主内存给 write 备用 |
use | 工作内存 | 传值给执行引擎 |
assign | 工作内存 | 执行引擎执行结果赋值给本地变量 |
Java 内存模型(Java Memory Model - JMM)
Java 内存模型的主要目标是定义程序中的各个变量的访问规则,即如何在虚拟机中将变量存储到内存和从内存中取出。此处的变量不包括局部变量和方法参数,因为它们是线程私有的,不会被共享,自然不存在竞争问题。由于 JVM运行程序的实体是线程,而每个线程创建时 JVM 都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而 Java 内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问, 但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然 后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存, 线程间的通信(传值)必须通过主内存来完成,线程、主内存、工作内存三者的关系如下图
二、happens-before 原则
参考资料 :
JLS 17.4.5
( The Java® Language Specification )
happens-before
原则是规定重排序必须遵循的一些原则,禁止重排序造成某些问题的发生
'''
happens-before relationship means if one action happens-before another, then the first is visiable to and ordered before the second.
*two actions : x and y hb(x, y) -> x happens-before y.
'''
# ① 控制流规则
1. x, y are actions of the same thread, x come before y in program order, then hb(x, y);
同一线程中的两个在程序执行顺序(即控制流顺序,非代码的顺序)上有先后的两个操作,需要遵循 happens-before 原则。
2. There is a happens-before edge from the end of a constructor of an object to the
start of a finalizer (§12.6) for that object.
happens-before 的作用范围在 constructor 到 finalize 之间(即一个对象的完整生命周期)
3. If an action x synchronizes-with a following action y, then we also have hb(x, y).
4. If hb(x, y) and hb(y, z), then hb(x, z). (传递性)
# ② 管程 (monitor) 锁定规则
• An unlock on a monitor happens-before every subsequent lock on that monitor.
一个 unlock 操作先行发生于后面(时间上)对同一把锁的 lock 操作
# ③ volitale 规则
• A write to a volatile field (§8.3.1.4) happens-before every subsequent read of
that field.
对一个 volatile 变量的写操作先行发生于后面(时间上)对这个变量的读操作
# ④ 线程启动规则
• A call to start() on a thread happens-before any actions in the started thread.
Thread.start() 方法先行发生于这个线程的任何操作
# ⑤ 线程终止规则
• All actions in a thread happen-before any other thread successfully returns from
a join() on that thread.
一个线程 return 前,必须保证该线程的所有内容执行完毕以及 join 在这个线程上的所有其他线程 return.
# ⑥ 线程中断规则
对线程的 interrupt() 方法的调用先行发生于被中断线程代码检测到中断事件的发生
# ⑦ 对象终结规则
• The default initialization of any object happens-before any other actions (other
than default-writes) of a program.
对象的初始化完成先行发生于它的其他操作
三、as if serial 原则
不管如何重排序,都要保证相同情况下单线程每次的执行结果不变(好像是在序列化执行)
四、对象的存储布局
java 代码:
Object o = new Object;
Java 汇编代码:
0 new #2 <java/lang/Object>
3 dup
4 invokespecial #1 <java/lang/Object.<init>>
7 astore_1
8 return
指令地址 | 指令 | 解释 |
---|---|---|
0 | new #2 <java/lang/Object> | 申请内存空间(成员变量赋默认值) |
3 | dup | |
4 | invokespecial #1 <java/lang/Object.> | 调用构造方法 |
7 | astore_1 | 将 o 和 new Object() 建立连接 |
8 | return | 返回 |
参考资料:Java 线程&锁(二)
指针压缩:
正常情况下,对象头中的 ClassPointer
和对象内容部分,都是占用 8 字节。如果虚拟机配置开启了指针压缩,将会把对应的部分压缩为 4 字节。
-XX:+UseCompressedClassPointers
- 将对象头中的 ClassPointer
压缩为 4 字节
-XX:+UseCompressedOops
- 将对象的实例数据压缩为 4 字节 (注意这里是实例数据中的每一个字段占4字节)
对象数据展示:import org.openjdk.jol.info.ClassLayout;
public class JolTest {
public static void main(String[] args) {
try {
// 这里的 sleep 必须在 new User 之前
// shortSleep 4秒之后偏向锁启动
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
User u = new User();
u.setId(12);
u.setCode(Long.MAX_VALUE);
// 这里无论设置了多长的 String 类型数据,都不会影响该字段占的空间。因为这里存的只是一个指针。
u.setName("一个很好吃南市领导卡数据量大家伙来看傻掉了卡视角离开发了可视角度卢卡斯");
// 这里锁状态也是 101(偏向锁) 但是并未记录偏向锁的线程ID, 所以其实没加锁
System.out.println(ClassLayout.parseInstance(u).toPrintable());
// 无锁状态 - 偏向锁
synchronized (u) {
// 这里锁状态 101(偏向锁) 已经记录偏向锁的线程ID
System.out.println(ClassLayout.parseInstance(u).toPrintable());
}
System.out.println(Integer.toHexString(System.identityHashCode(u)));
synchronized (u) {
// 这里计算过 identityHashCode 后,无法进入偏向锁状态,所以会直接膨胀为重量级锁
System.out.println(ClassLayout.parseInstance(u).toPrintable());
}
System.gc();
System.out.println(ClassLayout.parseInstance(u).toPrintable());
}
}
执行结果:
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.leecode.bean.User 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) 43 c0 00 f8 (01000011 11000000 00000000 11111000) (-134168509)
12 4 java.lang.Integer User.id 12
16 4 java.lang.Long User.code 9223372036854775807
20 4 java.lang.String User.name (object)
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
com.leecode.bean.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 40 00 bf (00000101 01000000 00000000 10111111) (-1090502651)
4 4 (object header) 94 7f 00 00 (10010100 01111111 00000000 00000000) (32660)
8 4 (object header) 43 c0 00 f8 (01000011 11000000 00000000 11111000) (-134168509)
12 4 java.lang.Integer User.id 12
16 4 java.lang.Long User.code 9223372036854775807
20 4 java.lang.String User.name (object)
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
27abe2cd
com.leecode.bean.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) e8 38 01 05 (11101000 00111000 00000001 00000101) (83966184)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 43 c0 00 f8 (01000011 11000000 00000000 11111000) (-134168509)
12 4 java.lang.Integer User.id 12
16 4 java.lang.Long User.code 9223372036854775807
20 4 java.lang.String User.name (object)
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
com.leecode.bean.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 09 cd e2 ab (00001001 11001101 11100010 10101011) (-1411199735)
4 4 (object header) 27 00 00 00 (00100111 00000000 00000000 00000000) (39)
8 4 (object header) 43 c0 00 f8 (01000011 11000000 00000000 11111000) (-134168509)
12 4 java.lang.Integer User.id 12
16 4 java.lang.Long User.code 9223372036854775807
20 4 java.lang.String User.name (object)
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Hotspot开启内存压缩的规则(64位机)
- 4G以下,直接砍掉高32位
- 4G - 32G,默认开启内存压缩 ClassPointers Oops
- 32G,压缩无效,使用64位
关闭指针压缩后:
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.leecode.bean.User 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) 38 1a 0d 18 (00111000 00011010 00001101 00011000) (403511864)
12 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
16 8 java.lang.Integer User.id 12
24 8 java.lang.Long User.code 9223372036854775807
32 8 java.lang.String User.name (object)
Instance size: 40 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
一个对象占用的内存空间
一个 Object()
占16字节
- markword 8 byte
- classpointer (压缩的) 4 byte
- padding 4 byte
一个 new int[]{}
占 16 字节
- markword 8 byte
- classpointer (压缩的) 4 byte
- arrlength 4 byte
private static class P {
// 8 markword
// 4 classpointer
int id; // 4
String name; // 4 (对于引用类型保存的是指针)
int age; // 4
byte b1; // 1
byte b2; // 1
Object o; // 4
byte b3; // 1
// ----------------------
// 31 byte + 1 byte (padding) = 32 byte
}
复习:基本类型占用的内存空间
1 byte | 2 byte | 4 byte | 8 byte |
---|---|---|---|
byte | char | int | long |
boolean | short | float | double |
五、对象 markword
参考资料:Java 线程&锁(二)
identity_hashcode
:31位的对象标识hashCode
,采用延迟加载技术。调用方法System.identityHashCode()
计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord
的字节没有足够的空间保存 hashCode
,因此该值会移动到管程Monitor
中。
如果该对象调用了 System.identityHashCode(u)
计算过 identity_hashcode
后,将导致该对象无法进入偏向锁状态(因为没地方存偏向的线程ID了)
参考资料 :
https://cloud.tencent.com/developer/article/1480590
https://cloud.tencent.com/developer/article/1484167
https://cloud.tencent.com/developer/article/1485795
https://cloud.tencent.com/developer/article/1482500
六、对象定位
- 句柄池
- 直接指针