1. 存储器的层次结构

图片.png

2. 缓存行对其与伪共享

每个CPU都有自己的1级缓存和2级缓存, 所有的CPU共享3级缓存。当一个CPU要读取一个变量时先从1级缓存中找, 找不到则取2级缓存中找,2级缓存找不到则去3级缓存中找, 以此类推直接找打主存,如果主存中找到了则依次存储在3级2级1级中。

image.png
因为每个CPU都有自己的1级缓存和2级缓存, 当2个或多个CPU读取同一块内存时,会把2块一样的内存数据都存储在自己的一级和二级缓存中。

这样的存储方式就会引起缓存数据不一致的问题。 假设一个CPU改动了其中一个变量,那么如果不通知其他CPU此变量已经改动过,其他CPU如果还是按照以前的值就行运算这样计算的结果就会有问题。

那么如何解决这种缓存不一致的问题呢? 答案是MESI缓存一致性协议

2.1 总线锁。

通过计算机组成原理我们知道, 计算总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线,它是由导线组成的传输线束, 按照计算机所传输的信息种类,计算机的总线可以划分为数据总线地址总线控制总线,分别用来传输数据、数据地址和控制信号

CPU通过控制总线锁,锁定对当前内存的独占,达到缓存一致性的目的。
点击查看【processon】
总线锁虽然解决了缓存一致性的问题,但是由于其机制问题硬锁性能太差。

2.2 MESI缓存一致性协议。

语雀内容
虽然MESI缓存一致性协议再保证性能的同时保证数据一致性,但是由于其机制问题一些有些缓存数据(比如跨越多个缓存行的数据)依然不能保证缓存一致性, 因此现在CPU硬件层面的缓存一致性使用锁总线+MESI配合使用的。

2.3 缓存行

语雀内容

3. java对象的的内存布局

3.1 开启虚拟机配置参数

java -XXL+PrintCommandLineFlags -version

image.png
每个参数解释如下:

  • -XX:InitialHeapSize=26711737 初始化堆大小
  • -XX:MaxHeapSize=4273878016 最大堆大小
  • -XX:+PrintCommandLineFlags 打印命令行参数
  • -XX:+UseCompressedClassPointers 使用压缩指针,使用之后指针从8字节大小变为4字节大小
  • -XX:+UseCompressedOops 使用压缩Oops
  • -XX:-UseLargePagesIndividualAllocation
  • XX:+UseParallelGC

    3.2 普通对象布局

  1. 对象头markword: 8字节
  2. ClassPointer指针:使用-XX:+UseCompressedClassPointer后为4字节, 禁止使用为8字节。
  3. 实例数据:
    1. 引用类型使用-XX:+UseCompressedOops为4个字节, 不适用为8个字节
  4. 内存对齐:8的倍数对齐。

    3.3 数组对象布局

  5. 对象头markword: 8字节同上

  6. ClassPointer指针:同上
  7. 数组长度: 4字节。这也是因为为什么数组不能创建比int类型大的元素总数的原因。
  8. 数组数据。
  9. 内存对齐。

3.4 markword 详细

锁状态 25 bit 31 bit 1 bit 1 bit age
4bit
分代年龄
biased_lock
1bit
是否偏向锁
lock
2bit
锁标记
无锁 未使用 hash 未使用 分代年龄 0 01
偏向锁 线程ID Epoch 分代年龄 1 01
轻量级锁 栈中锁记录的指针(64) 00
重量级锁 monitor的指针(64) 10
GC 11
锁状态 25bit age
4bit
分代年龄
biased_lock
1bit
是否偏向锁
lock
2bit
锁标记
23bit 2bit
无锁 hash 分代年龄 0 01
偏向锁 线程ID Epoch 分代年龄 1 01
轻量级锁 栈中锁记录的指针 00
重量级锁 指向互斥量(重量级锁)的指针 10
GC 11