类的加载过程
Loading->Linking(verification->preparation->resolution)->Initializing
Loading
将.class文件load到内存中
Linking
- verification(验证):验证文件是否符合JVM规定
- Preparation(准备):静态成员变量赋默认值
- Resolution(解析,解释):将类、方法、属性等符号引用解析为直接引用,常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
Initializing
调用类初始化代码 ,给静态成员变量赋初始值
类加载器(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
下面我会用一段小程序来体现一个类的加载过程
public class ClassLoadingDemo{
public static void main(String[] args) {
System.out.println("count:"+T.count);
}
}
class T {
public static int count = 2; //0
public static T t = new T(); // null
private T() {
count ++;
}
}
// count:3
这里为什么count会等于3呢?因为一般实例化有一个开辟空间准备的过程,然后在赋值,首先int count =0,T t =null,然后在初始化 int cout =2,t实例化会调用构造函数执行了count++,所以打印出来count:3,下来这段代码的话,我们自己来分析下打印结果是什么
public class ClassLoadingDemo{
public static void main(String[] args) {
System.out.println("count:"+T.count);
}
}
class T {
public static T t = new T(); // null
public static int count = 2; //0
private T() {
count ++;
}
}
JMM java内存模型
硬件级别缓存一致性(缓存锁MESI)
缓存行,伪共享,一般我们cpu执行的时候拿取数据都是一块一块的拿,不用的数据也会拿过来,可能多个cpu使用一个缓存行的时候会互相争抢,cache失效等操作影响性能。 but,有一些无法被缓存或者MESI解决不了的问题依然必须使用锁总线的方式
MESI Cache一致性协议
一致性协议(MESI):www.cnblogs.com/z00377750/p…
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倍)),去同时执行另一条指令,前提是,两条指令没有依赖关系 www.cnblogs.com/liushaodong… 写操作有一个合并写的概念,WCBuffer,把写操作合并有可能也会出现乱序,原因是有的写操作快有的写操作慢。 一般只有4个位置(特别宝贵,稀有物品) 在批处理的场景里,我们合理的运用WCbuffer可以提高程序的效率。
如何证明乱序问题
public class T04_Disorder {
private static int x = 0, y = 0;
private static int a = 0, b =0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for(;;) {
i++;
x = 0; y = 0;
a = 0; b = 0;
Thread one = new Thread(new Runnable() {
public void run() {
a = 1;
x = b;
}
});
Thread other = new Thread(new Runnable() {
public void run() {
b = 1;
y = a;
}
});
one.start();other.start();
one.join();other.join();
String result = "第" + i + "次 (" + x + "," + y + ")";
if(x == 0 && y == 0) {
System.err.println(result);
break;
} else {
//System.out.println(result);
}
}
}
public static void shortWait(long interval){
long start = System.nanoTime();
long end;
do{
end = System.nanoTime();
}while(start + interval >= end);
}
}
第218385次 (0,0)
以上代码执行一段时间后,会出现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和硬件层面
blog.csdn.net/qq_26222859… hsdis - HotSpot Dis Assembler windows lock 指令实现 | MESI实现
synchronized实现细节
字节码层面
ACC_SYNCHRONIZED monitorenter ---> monitorexit
JVM层面
C C++ 调用了操作系统提供的同步机制
OS和硬件层面
X86 : lock cmpxchg / xxx https://blog.csdn.net/21aspnet/article/details/88571740