文章目录
volatile 是禁止指令重排序(DCL单例使用)
class T {
int m = 8;
}
T t = new T();
在这个new对象的过程中有几种状态
几条指令
申请内存空间赋值初始值–>复制栈中内容–>消耗栈中一个(弹出来)指向的对象调用构造方法(消耗一个引用)—>弹出栈赋值给局部变量(astore-1;astore-0是this)
初始化有中间态(半初始化),有可能会出现指令重排序
Java有规定一些情况下不允许出现指令重排序(如:多线程时提前进来,使用了半初始化的对象)
其他都没有规定
CPU禁止指令重排序: 内存屏障(原语’mfence lfence sfence’ 或者锁总线)
JVM禁止指令重排序: JSR内存屏障(8个hanppens-before原则 4个内存屏障 LL LS SL SS)
new对象过程
JVM重排序规则
1.程序次序规则:
同一个线程内,按照代码出现的顺序,前面的代码先于后面的代码,准确来说是控制流顺序,因为要考虑到分支和循环结构.
2.管程锁定规则:
一个unlock操作先行发生于后面(时间上)对同一个锁的lock操作.
3.volatile变量规则:
对一个volatile变量的写操作先行发生于后面(时间上)对这个变量的读操作
4.线程启动规则:
thread的start()方法先行发生于这个线程的每一个操作.
5.线程终止规则:
线程的所有操作都先行于此线程的终止检测.可以通过thread.join()方法结束、thread.isAlive()的返回值等手段检测线程的终止.
6.线程中断规则:
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事见的发生.可以通过thread.interrupt()方法检测线程是否中断
7.对象终结规则:
一个对象的初始化完成先行于发生它的finalize()方法的开始.
8.传递性:
如果操作A先行于操作B,操作B先行于操作C,那么操作A先行于操作C
jvm底层
双亲委派
一个孩子向父亲方向;然后父亲向孩子方向的双亲委派过程
父加载器不是类加载器的加载器;也不是父类加载器的父类加载器
getClassLoader().getParent(); 获取类加载器.类加载器的父加载器
classLoader加载时向上询问是否已经有加载,没有就继续向上询问,询问结果返回,如果有则直接拿来用、若都没有则重新加载;
实现:主要是安全;其次是防止资源浪费(已经加载则不需要再加载)
打破双亲委派
打破双亲委派
1.如何打破:重写loadClass()
2.何时打破过:1.JDK1.2之前,自定义ClassLoader都必须重写loadClass()
2.ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader
3.热启动,热部署–>加载同一类库的不同版本
热加载会把classloader文件干掉重写加载一个
自定义类加载器
继承ClassLoader类—>底层调用还是loderClass
重写findClass
public class JvmCode extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
class文件加密
/**
* 文件加密
*/
private static void encFile(String name) throws IOException, InterruptedException {
File file = new File("G:\\新建文件夹 (2)\\新建文件夹", name.replace('.', '/').concat(".class"));
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File("G:\\新建文件夹 (2)\\新建文件夹", name.replaceAll(".", "/").concat(".szjclass")));
int b = 0;
while ((b = fis.read()) != -1) {
fos.wait(b ^ seed);
}
fis.close();
fos.close();
}
类(Loading)、链接(Linking)初始化(Initialization)
1.Verification
验证文件是否符合JVM规定
2.Preparation(赋默认值)
静态成员变量赋默认值
3.Resolution(解析)
将类、方法、属性等符号引用解析为直接引用
常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
Initializing初始化
静态变量赋初始值
调用类初始化
CPU
cache line(缓存行对齐、伪共享)
缓存行:64字节
现代CPU数据一致性:缓存锁(MESI协议等)+锁总线
每次发送自动补全字节,一次性发送,目的提高效率
位于同一缓存行的两个不同数据,被两个不同CPU锁定,产生互相影响的的伪共享问题
熟练使用缓存行的对齐能提高效率
乱序问题
读
CPU速度很快: 执行一条指令(去内存中读取数据–>慢100倍),等待指令返回的时间比较浪费;
在等待一条指令的同时,可运行其他指令,返回则继续运行.以此类推
前提是其他指令与这条指令没有依赖关系
写
WCbuffer: 写缓存区;
速度:CPU>WCbuffer(一般4个位置 太珍贵)>L1>L2>L3>内存>硬盘>云储存
写的同时可以合并写
写操作可以进行合并–>在写入一个数据时由于太慢了,后续的写入又进来,最终会将他们合并在一个缓存中计算得出
如何保证特定情况下CPU指令不乱序
volatile有序
实现细节
字节码层面
ACC_VOLATILE
JVM层面
volatile内存区的读写,都加内存屏障
在读写前后都加,不允许重排序>os和硬件层面
使用hsdis观察汇编码
hsdis---->HostSpot Dis Assembler
虚拟机的反汇编
硬件层面(内存屏障)x86
Inter CPU内存屏障:
sfence:store—>在sfence指令前的写操作当必须在sfence指令后的写操作前完成
ifence:load—>ifence指令前的读操作当必须在ifence指令后的读操作前完成
mfence:modify/min—>mfence指令前的读写操作当必须在mfence指令后的读写操作前完成
软件层面(JVM规范)
JVM规范:
1.LoadLoad屏障:对于这样的语句Load1;LoadLoad;Load2
在load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕.2.StoreStore屏障:
对于这样的语句Store1;StoreStore;Store2,
在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见.3.LoadStore屏障:
对于这样的语句Load1;LoadStore;Store2,
在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕.4.StoreLoad屏障:
对于这样的语句Store1;StoreLoad;Load2,
在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见