JVM_04 指令重排序原则

< previous next >

一、Java 8 大原子操作(目前这种说法已弃用)

参考资料 《深入理解java虚拟机》page: 364

指令作用区解释
lock主内存标识变量为线程独占
unlock主内存解锁
read主内存读到工作内存
write主内存写到主内存
load工作内存read后的值放到线程本地变量副本
store工作内存存储值到主内存给 write 备用
use工作内存传值给执行引擎
assign工作内存执行引擎执行结果赋值给本地变量

在这里插入图片描述

图 1 java 8大原子指令
Java 内存模型(Java Memory Model - JMM)

​ Java 内存模型的主要目标是定义程序中的各个变量的访问规则,即如何在虚拟机中将变量存储到内存和从内存中取出。此处的变量不包括局部变量和方法参数,因为它们是线程私有的,不会被共享,自然不存在竞争问题。由于 JVM运行程序的实体是线程,而每个线程创建时 JVM 都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而 Java 内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问, 但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然 后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存, 线程间的通信(传值)必须通过主内存来完成,线程、主内存、工作内存三者的关系如下图

在这里插入图片描述

图 2 JMM 内存模型

二、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
指令地址指令解释
0new #2 <java/lang/Object>申请内存空间(成员变量赋默认值)
3dup
4invokespecial #1 <java/lang/Object.>调用构造方法
7astore_1将 o 和 new Object() 建立连接
8return返回

参考资料: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位机)
  1. 4G以下,直接砍掉高32位
  2. 4G - 32G,默认开启内存压缩 ClassPointers Oops
  3. 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 byte2 byte4 byte8 byte
bytecharintlong
booleanshortfloatdouble

五、对象 markword

参考资料:Java 线程&锁(二)

图 3 对象 markword

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

六、对象定位

  • 句柄池
  • 直接指针

在这里插入图片描述

图 4 通过句柄访问对象

在这里插入图片描述

图 5 通过直接指针访问对象
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值