JVM(三)【Linking Initializing JMM】

JVM(三)【Linking Initializing JMM】

      • 一、Linking
          • 1. Verification
          • 2. preparation
          • 3. resolution
      • 二、Initializing
          • 1. 调用类初始化代码,给静态成员变量赋初始值。
      • 三、JMM
          • 1. 硬件层的并发优化基础知识(深入理解计算机系统 原书第三版 P421)

一、Linking

1. Verification
  • 检验加载的文件是否符合JVM规定
2. preparation
  • 静态成员变量赋默认值
3. resolution
  • classLoader源码中loadClass第二个参数boolean reolve,是否进行解析。
  • 将类、方法、属性等符号引用解析为直接引用。
  • 常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
    (符号引用:常量池中有一个类的名字,实际上这个类可能是在内存中的另一个片区域,被这个名字指向,当使用的时候我们写的字符指向这个名字,这个名字指向那一片真正的区域)
  • 解析就是让写的字符直接指向那一片类真正存在的内存空间。

二、Initializing

1. 调用类初始化代码,给静态成员变量赋初始值。
  • 上述总结:
    • 类:load - 默认值 - 初始值
    • object:new - 申请内存 - 默认值 - 初始值
      JVM(三)【Linking Initializing JMM】_第1张图片
    • 类初始化过程,0时申请空间并设默认值,4设初始值,7把引用指向空间。
    • 若发生指令重排则可能47颠倒,默认值后直接交给引用,volatile可避免。这也是DCL单例中为什么成员变量需要加volatile。

三、JMM

1. 硬件层的并发优化基础知识(深入理解计算机系统 原书第三版 P421)
  • 存储器结构层次:
    • 运行速度 cpu > 内存 > 硬盘,cpu是内存100倍,是硬盘的100万倍(粗略)
    • 在运行时,cpu计算在找一个计算需要的数时一层一层往下去找,看目标位置,找到之后一层层向上运到寄存器。
    • L0到L2都是每个cpu自己内部独立的,从L3往下都是所有cpu共享的,L3区域在主板上。
    • 各层之间速度对比:
      JVM(三)【Linking Initializing JMM】_第2张图片
  • 老cpu使用总线锁来保证各个cpu中的数据一致,效率极低。
  • 现在用各种各样的一致性协议和总线锁相互配合:例如 MSI MESI MOSI…,intel cpu使用的MESI。
  • MESI Cache一致性协议简单大致内容:
    • 大致是每一个缓存的内容都加一个标记。
    • X跟主存内容相比如果更改过标记为Modified
    • 如果是这个cpu独享的,标记为Exclusive
    • 如果这个内容读的时候别的cpu也在读,标记为Shared
    • 如果这个内容读的时候别的cpu把他改了,标记为Invalid无效
    • 以上四种状态首字母合起来简称MESI
  • cache line 缓存行:
    • 读取缓存以cache line为基本单位,一般是64字节 4个位置。
      JVM(三)【Linking Initializing JMM】_第3张图片
    • 伪共享问题:xy两个数在一个缓存行,cpu1需要x则整个缓存行读取进来改了x,cpu2需要y则整个缓存行读取进来改了y,改完x之后cpu1通知2说这个缓存行我改过了你标记为无效,重新读一下xy整个缓存行,2改完2之后同样通知1重新读取。
    • 位于同一缓存行的两个不同数据,被两个不同cpu锁定,产生互相影响的伪共享问题。
    • 可以用对齐来解决,即每个需要的数放入不同缓存行,本缓存行其他位置用别的无用的数来填充满64字节,这样提升了效率,而空间多使用了一点点。
      JVM(三)【Linking Initializing JMM】_第4张图片
    • 如上图,声明7个long类型(8字节),7×8=56,然后声明需要的x变量,现在是8×8=64字节,y同理,这样xy就分开了,其他为填充的无用数据。
    • disruptor指针的源码实现有一段:,前面声明7个long类型(8字节),后面声明7个long,保证cursor不管跟前面的对齐还是后面的对齐,一定是自己在一个缓存行内,所以效率十分高。
      在这里插入图片描述
  • 乱序问题:
    • Cpu为了提高指令执行效率,会在一条指令执行过程中(比如去内存读数据,因为cpu速度至少快内存100倍),cpu等待内存返回的值的同时进行下一条指令的执行,前提是两条指令没有依赖关系。
    • 写的时候,多个指令合并一起结果给内存中,原因还是内存太慢了,L2L3之间其实还有一个WCBuffer,合并写的缓存,放入一起交给内存。
    • WCBuffer一般只有4个字节,这个缓存实际上比L1 L2还要快,当这四个字节都满了才会把内容刷走,所以就有了合并写技术:
    • 假如有一个6字节的数据需要执行,一下把这6个字节挨个执行的速度远慢于3个字节执行,3个字节执行的速度(3个是因为执行的时候JVM(三)【Linking Initializing JMM】_第5张图片,比如这样,b也占一个字节)
    • 因为3+3的话,先放一个3然后b占一个,wcbuffer直接满了就发车了,如果是直接扔6个,前4个进入了wcbuffer满了发车了,后两个存进来之后还得等两个其他的占上剩下的2字节才能发车,所以慢,所以手动分成两个缓存比直接扔给缓存然后顺序执行的效率高很多,也是相当于上述缓存行对齐的一种应用场景。
  • 特定情况下有序性保障:
    • cpu汇编级别的内存屏障(硬件级别的),不同Cpu内存屏障设计的不同,禁止指令重排序。
    • 上图中sfence是save fence,lfence是load fence,mfence是读+写。
    • java级别的内存屏障,读读屏障,读写屏障,写写屏障,依赖于硬件级别的内存屏障,可能是上面这种也可能是汇编指令的lock,但java级别的和硬件级别的内存屏障是两个不同的东西。

你可能感兴趣的:(JVM调优)