JVM 调优(一)
类的加载过程
Loading->Linking(verification->preparation->resolution)->Initializing
Loading
将.class 文件 load 到内存中
Linking
verification(验证):验证文件是否符合 JVM 规定
Preparation(准备):静态成员变量赋默认值
Resolution(解析,解释):将类、方法、属性等符号引用解析为直接引用,常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
Initializing
调用类初始化代码 <clinit>,给静态成员变量赋初始值
类加载器(ClassLoader)
说明:一般情况下,是从底层往上按顺序处理,先查找是否已经被 load 过,如果 CustomClassLoad 没有加载过,继续往上从 AppClassLoad 中找,如果加载过直接返回,依次类推到最上层 BootStrap,然后往下去 findClass 并 load,如果不是自己加载的就往下寻找直到类被 ClassLoad 加载,如果到最没有没有加载的话抛出异常 notfoundClass.
Loading
双亲委派,主要出于安全来考虑
LazyLoading (懒加载,需要时加载)五种情况
new getstatic putstatic invokestatic 指令,访问 final 变量除外
java.lang.reflect 对类进行反射调用时
初始化子类的时候,父类首先初始化
虚拟机启动时,被执行的主类必须初始化
动态语言支持 java.lang.invoke.MethodHandle 解析的结果为 REF_getstatic REF_putstatic REF_invokestatic 的方法句柄时,该类必须初始化
ClassLoader 的源码 :findInCache -> parent.loadClass -> findClass()
自定义类加载器
混合执行 编译执行 解释执行
Linking
下面我会用一段小程序来体现一个类的加载过程
这里为什么 count 会等于 3 呢?因为一般实例化有一个开辟空间准备的过程,然后在赋值,首先 int count =0,T t =null,然后在初始化 int cout =2,t 实例化会调用构造函数执行了 count++,所以打印出来 count:3,下来这段代码的话,我们自己来分析下打印结果是什么
JMM java 内存模型
硬件级别缓存一致性(缓存锁 MESI)
缓存行,伪共享,一般我们 cpu 执行的时候拿取数据都是一块一块的拿,不用的数据也会拿过来,可能多个 cpu 使用一个缓存行的时候会互相争抢,cache 失效等操作影响性能。but,有一些无法被缓存或者 MESI 解决不了的问题依然必须使用锁总线的方式
MESI Cache 一致性协议
一致性协议(MESI):https://www.cnblogs.com/z00377750/p/9180644.html
MESI 协议中的状态
CPU 中每个缓存行(cache line)使用 4 种状态进行标记(使用额外的两位(bit)表示)
M:被修改(Modified)
该缓存行只被缓存在该 CPU 的缓存中,并且是被修改过的(dirty),即与主存中的数据不一致,该缓存行中的内存需要在未来的某个时间点(允许其它 CPU 读取请主存中相应内存之前)写回(write back)主存。当被写回主存之后,该缓存行的状态会变成独享(exclusive)状态。
E:独享的(Exclusive)
该缓存行只被缓存在该 CPU 的缓存中,它是未被修改过的,与主内存的数据一致,该状态可以在任何时刻当有其它 CPU 读取该内存时变成共享状态(shared)。同样的,它是可以被修改的,状态变为(Modified)被修改的。
S:共享的(Shared)
该状态意味着该缓存行可能被多个 CPU 缓存,并且缓存行数据与主存一致,如果其中一个缓存行被修改,那么其他的 CPU 缓存状态变为(Invalid)无效的
I:无效的(Invalid)
该缓存是无效的(可能有其它 CPU 修改了该缓存行)。
缓存行
缓存行越大,局部性空间效率越高,但是读取效率越低缓存行越小,局部性空间效率越低,但是读取效率越高工业实验取舍后,目前来说,多用 64 字节使用缓存行的对齐能够提高效率。
乱序问题
CPU 为了提高指令执行效率,会在一条指令执行过程中(比如去内存读数据(慢 100 倍)),去同时执行另一条指令,前提是,两条指令没有依赖关系https://www.cnblogs.com/liushaodong/p/4777308.html写操作有一个合并写的概念,WCBuffer,把写操作合并有可能也会出现乱序,原因是有的写操作快有的写操作慢。 一般只有 4 个位置(特别宝贵,稀有物品)在批处理的场景里,我们合理的运用 WCbuffer 可以提高程序的效率。
如何证明乱序问题
以上代码执行一段时间后,会出现 0,0 的情况,这就充分说明了乱序执行。
如何保证有序性
硬件内存屏障 X86
sfence(savefence):在 sfence 指令前的写操作必须在 sfence 指令后的写操作前完成。
lfence(loadfence):在 lfence 指令前的读操作必须在 lfence 指令后的读操作前完成。
mfence(mixfence):在 mfence 指令前的读写操作必须在 mfence 指令后的读写操作前完成。
原子指令,如 x86 上的”lock …” 指令是一个 Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个 CPU。Software Locks 通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序 如 lock addl
JVM 级别如何规范(JSR133)
LoadLoad 屏障:对于这样的语句 Load1; LoadLoad; Load2, 在 Load2 及后续读取操作要读取的数据被访问前,保证 Load1 要读取的数据被读取完毕。
StoreStore 屏障:对于这样的语句 Store1; StoreStore; Store2,在 Store2 及后续写入操作执行前,保证 Store1 的写入操作对其它处理器可见。
LoadStore 屏障:对于这样的语句 Load1; LoadStore; Store2,在 Store2 及后续写入操作被刷出前,保证 Load1 要读取的数据被读取完毕。
StoreLoad 屏障:对于这样的语句 Store1; StoreLoad; Load2,在 Load2 及后续所有读取操作执行前,保证 Store1 的写入对所有处理器可见。
volatile 实现细节
字节码层面
ACC_VOLATILE
JVM 层面
LoadLoad 屏障、StoreStore 屏障、LoadStore 屏障、StoreLoad 屏障
OS 和硬件层面
https://blog.csdn.net/qq_26222859/article/details/52235930hsdis - HotSpot Dis Assemblerwindows lock 指令实现 | MESI 实现
synchronized 实现细节
字节码层面
ACC_SYNCHRONIZEDmonitorenter ---> monitorexit
JVM 层面
C C++ 调用了操作系统提供的同步机制
OS 和硬件层面
X86 : lock cmpxchg / xxxhttps://blog.csdn.net/21aspnet/article/details/88571740
评论