- 静态成员变量赋默认值
3. resolution
-
classLoader源码中loadClass第二个参数boolean reolve,是否进行解析。
-
将类、方法、属性等符号引用解析为直接引用。
-
常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
(符号引用:常量池中有一个类的名字,实际上这个类可能是在内存中的另一个片区域,被这个名字指向,当使用的时候我们写的字符指向这个名字,这个名字指向那一片真正的区域)
- 解析就是让写的字符直接指向那一片类真正存在的内存空间。
二、Initializing
1. 调用类初始化代码,给静态成员变量赋初始值。
-
上述总结:
-
类:load - 默认值 - 初始值
-
object:new - 申请内存 - 默认值 - 初始值
-
类初始化过程,0时申请空间并设默认值,4设初始值,7把引用指向空间。
-
若发生指令重排则可能47颠倒,默认值后直接交给引用,volatile可避免。这也是DCL单例中
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
为什么成员变量需要加volatile。
三、JMM
1. 硬件层的并发优化基础知识(深入理解计算机系统 原书第三版 P421)
- 存储器结构层次:
-
运行速度 cpu > 内存 > 硬盘,cpu是内存100倍,是硬盘的100万倍(粗略)
-
在运行时,cpu计算在找一个计算需要的数时一层一层往下去找,看目标位置,找到之后一层层向上运到寄存器。
-
L0到L2都是每个cpu自己内部独立的,从L3往下都是所有cpu共享的,L3区域在主板上。
-
各层之间速度对比:
-
老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个位置。
-
伪共享问题:xy两个数在一个缓存行,cpu1需要x则整个缓存行读取进来改了x,cpu2需要y则整个缓存行读取进来改了y,改完x之后cpu1通知2说这个缓存行我改过了你标记为无效,重新读一下xy整个缓存行,2改完2之后同样通知1重新读取。
-
位于同一缓存行的两个不同数据,被两个不同cpu锁定,产生互相影响的伪共享问题。
-
可以用对齐来解决,即每个需要的数放入不同缓存行,本缓存行其他位置用别的无用的数来填充满64字节,这样提升了效率,而空间多使用了一点点。
-
如上图,声明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个是因为执行的时候,比如这样,b也占一个字节)
-
因为3+3的话,先放一个3然后b占一个,wcbuffer直接满了就发车了,如果是直接扔6个,前4个进入了wcbuffer满了发车了,后两个存进来之后还得等两个其他的占上剩下的2字节才能发车,所以慢,所以手动分成两个缓存比直接扔给缓存然后顺序执行的效率高很多,也是相当于上述缓存行对齐的一种应用场景。
-
特定情况下有序性保障: