Volatile和Synchronized的实现细节
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实现细节
众所周知,volatile是保证可见和有序的,但不保证同步.
字节码层面,ACC_VOLATILE
volatile修饰的变量编译后,字节码文件的Access flags中会有个 0x0040,即volatile的修饰符,这个就是告诉JVM,该变 量使用了volatile修饰;
这个我们写点代码,然后用IntelliJ的JclassLib插件就可以看到JVM层面,volatile内存区的读写 都加屏障
JVM表示知道了,然后在volatile变量的读写操作前后都加了屏障:
LoadLoadBarrier volatile读操作 LoadStoreBarrier
LoadStoreBarrier volatile写操作 StoreLoadBarrierOS和硬件层面
JVM当然是依赖操作系统及硬件去实现这个屏障的效果<br /> 参考 [https://blog.csdn.net/qq_26222859/article/details/52235930](https://blog.csdn.net/qq_26222859/article/details/52235930)<br /> hsdis - HotSpot Dis Assembler 这个命令是看JVM编译好的字节码,在CPU级别是用啥指令完成的<br /> windows lock 指令实现 | MESI实现
Synchronized实现细节
synchronized会保证同步性,附带着保证可见和有序
字节码层面
ACC_SYNCHRONIZED
monitorenter monitorexit
这里有俩monitorexit是因为,如果发生了异常,synchronized会自动释放锁JVM层面
C C++ 调用了操作系统提供的同步机制
OS和硬件层面
X86 : lock cmpxchg / xxx
参考 https://blog.csdn.net/21aspnet/article/details/88571740
JMM 和 一些规则
Java Memory Model并发内存模型,
这个知道了硬件(CPU和主存)的内存模型,这里就很容易理解了,基本一样.
8大原子操作
happens-before原则
as if serial
对象的内存布局
对象的创建过程
- class loading,把class加载到内存中
- class linking(verification,preparation,resolution),静态变量默认值
- class initializing,静态变量初始值,执行静态语句块
- 申请对象内存
- 成员变量赋默认值
调用构造方法
6.1 成员变量顺序赋初始值<br /> 6.2 执行构造方法语句(先调用父类的构造方法super())
对象在内存中的存储布局
https://www.zhihu.com/question/52116998/answer/133400077
https://blog.csdn.net/zhaocuit/article/details/100208879
对象大小(64位机)
对象的大小跟虚拟机的实现和设置都有关系
普通对象和数组对象也有不同
这里可以做个实验验证下,自己写个javaagent,java.lang.instrument.Instrumentation.getObjectSize(Object o)
观察虚拟机配置
在终端输入下面命令,其实就是查看Java版本的命令,只不过我们指定了参数,打印出JVM的参数
java -XX:+PrintCommandLineFlags -version
得到下面的结果:
我们比较关心 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops,这俩是默认就有的;
如果想关闭,就把加号改成减号,如-XX:-UseCompressedOops
C:\windows\system32>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=267595520 -XX:MaxHeapSize=4281528320 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version “1.8.0_112”
Java™ SE Runtime Environment (build 1.8.0_112-b15)
Java HotSpot™ 64-Bit Server VM (build 25.112-b15, mixed mode)
对象的内存布局
分为三块:对象头(Header),实例数据(Instance)和对齐填充(Padding)
对象头包括两部分
第一部分: markword,8字节<br /> 这个对象的运行时数据,哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID等<br /> 第二部分<br /> ClassPointer类型指针: 表示该对象属于哪个类,XX:+UseCompressedClassPointers 为4字节,不开启为8字节<br /> 如果对象是数组,还有块记录 数组长度,4字节
实例数据
引用类型: -XX:+UseCompressedOops 为4字节 不开启为8字节<br /> Oops: Ordinary Object Pointers<br /> 基础类型: byte 1字节, short 2字节, int 4字节, long 8, float 4, double 8,boolean 1, char 2,<br /> String属于引用类型
对齐填充:对齐成8的倍数,64位的机器读起来效率高一些(一个字节8位,8个字节64位)
Object o = new Object()在内存中占用多少字节?<br /> 答案是16字节<br /> 首先对象头8个字节,如果-XX:+UseCompressedClassPointers,那么ClassPointer 4个字节,然后Padding对齐,结果是 16字节<br /> 如果-XX:-UseCompressedClassPointers,那么ClassPointer 8个字节,结果还是16字节
Hotspot开启内存压缩的规则(64位机)
- 4G以下,直接砍掉高32位
- 4G - 32G,默认开启内存压缩 ClassPointers Oops
32G,压缩无效,使用64位
内存并不是越大越好
对象头具体包括什么
这个很复杂,要看HotSpot的源码,暂时没必要挖太深
很重要的两个东西:
锁标志位和GC标记
严格来说,有3bit表示锁的状态
对象处于不同的状态时,markword各个bit表示出不同的内容(复用存储空间)
网上找的一个说明markword的图(32位JVM的)
可以看到分代年龄占用4bit,这也是为什么,GC年龄默认16
IdentityHashCode的问题:
当一个对象计算过identityHashCode之后,不能进入偏向锁状态
https://cloud.tencent.com/developer/article/1480590
https://cloud.tencent.com/developer/article/1484167
https://cloud.tencent.com/developer/article/1485795
https://cloud.tencent.com/developer/article/1482500
对象怎么定位
T o = new T(),这个o是怎么找到对应的对象的?
https://blog.csdn.net/clover_lily/article/details/80095580
句柄池
o先指向一个间接指针,通过这个间接指针可以找到该对象和T.class
直接指针
o直接指向该对象,该对象可以指向T.class
Hotspot使用直接指针的方式
句柄池方式对GC(三色算法)友好一些
对象怎么分配
这里粗略了解下,后面讲GC时详细讲.
先尝试往栈上分配,栈弹出时回收对象;
栈上放不下的话,
如果对象很大(阈值),那就直接分配到堆内存(老年代);
如果对象不是特别大,就分配到线程本地,如果线程本地分配不下,就找新生代的Eden区