从Hotspot源码分析volatile的实现原理
本人开源了重试利器 Attempt 感兴趣的可以去 clone下代码阅读,也欢迎 start 和 discuss。gitHub 搜索:IceFrozen/Attempt
0
概诉
1
volatile 应用场景
01
可见性
02
有序性
下面我们来看有序性。
运行结果如下:
执行次数:247521,x:0 y:0
看第二个例子:
2
原理
1、 指令重排序
2、 MESI协议的导致的缓存未刷新。
01
指令重排序
02
内存屏障
当第二个操作是volatile写时,不管第一个操作是什么,都不能重排。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。
当第一个操作是volatile读时,不管第二个操作是什么,都不能重排。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。
当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。
总结来说,volatile 可见性包括两个方面:
写入的 volatile 变量在写完之后能被别的 CPU 在下一次读取中读取到;
写入 volatile 变量之前的操作在别的 CPU 看到 volatile 的最新值后一定也能被看到;
对于第一个方面,主要通过:
读取 volatile 变量不能使用寄存器,每次读取都要去内存拿;
禁止读 volatile 变量后续操作被重排到读 volatile 之前;
03
x86平台下的内存屏障
2
HotSpot屏障的实现
01
字节码上的区别
上述代码编译后,我们用javap命令来看一下字节码:
略
volatile int val;
descriptor: I
flags: ACC_VOLATILE
int val2;
descriptor: I
flags:
略
public int add();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field val:I
5: iconst_1
6: iadd
7: putfield #2 // Field val:I
10: aload_0
11: getfield #2 // Field val:I
14: ireturn
LineNumberTable:
line 7: 0
line 8: 10
public int add2();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #3 // Field val2:I
5: iconst_1
6: iadd
7: putfield #3 // Field val2:I
10: aload_0
11: getfield #3 // Field val2:I
14: ireturn
LineNumberTable:
line 11: 0
line 12: 10
}
02
读操作
void TemplateTable::getfield(int byte_no) {
getfield_or_static(byte_no, false);
}
void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteControl rc) {
transition(vtos, vtos);
const Register cache = rcx;
const Register index = rdx;
const Register obj = c_rarg3;
const Register off = rbx;
const Register flags = rax;
const Register bc = c_rarg3; // uses same reg as obj, so don't mix them
//给该字段创建一个ConstantPoolCacheEntry,该类表示常量池中某个方法或者字段的解析结果
resolve_cache_and_index(byte_no, cache, index, sizeof(u2));
//发布jvmti事件
jvmti_post_field_access(cache, index, is_static, false);
//加载该字段的偏移量,flags,如果是静态字段还需要解析该类class实例对应的oop
load_field_cp_cache_entry(obj, cache, index, off, flags, is_static);
if (!is_static) {
//将被读取属性的oop放入obj中
pop_and_check_object(obj);
}
const Address field(obj, off, Address::times_1);
Label Done, notByte, notBool, notInt, notShort, notChar,
notLong, notFloat, notObj, notDouble;
__ shrl(flags, ConstantPoolCacheEntry::tos_state_shift);
// Make sure we don't need to mask edx after the above shift
assert(btos == 0, "change code, btos != 0");
__ andl(flags, ConstantPoolCacheEntry::tos_state_mask);
//判断是否是byte类型
__ jcc(Assembler::notZero, notByte);
//读取该属性,并放入rax中
__ load_signed_byte(rax, field);
__ push(btos);
if (!is_static) {
//将该指令改写成_fast_bgetfield,下一次执行时就是_fast_bgetfield
patch_bytecode(Bytecodes::_fast_bgetfield, bc, rbx);
}
//跳转到Done
__ jmp(Done);
__ bind(notByte);
//判断是否boolean类型
__ cmpl(flags, ztos);
__ jcc(Assembler::notEqual, notBool);
// ztos (same code as btos)
__ load_signed_byte(rax, field);
__ push(ztos);
// Rewrite bytecode to be faster
if (!is_static) {
// use btos rewriting, no truncating to t/f bit is needed for getfield.
patch_bytecode(Bytecodes::_fast_bgetfield, bc, rbx);
}
__ jmp(Done);
__ bind(notBool);
//判断是否引用类型
__ cmpl(flags, atos);
__ jcc(Assembler::notEqual, notObj);
// atos
__ load_heap_oop(rax, field);
__ push(atos);
if (!is_static) {
patch_bytecode(Bytecodes::_fast_agetfield, bc, rbx);
}
__ jmp(Done);
__ bind(notObj);
//判断是否int类型
__ cmpl(flags, itos);
__ jcc(Assembler::notEqual, notInt);
// itos
__ movl(rax, field);
__ push(itos);
// Rewrite bytecode to be faster
if (!is_static) {
patch_bytecode(Bytecodes::_fast_igetfield, bc, rbx);
}
__ jmp(Done);
__ bind(notInt);
//判断是否char类型
__ cmpl(flags, ctos);
__ jcc(Assembler::notEqual, notChar);
// ctos
__ load_unsigned_short(rax, field);
__ push(ctos);
// Rewrite bytecode to be faster
if (!is_static) {
patch_bytecode(Bytecodes::_fast_cgetfield, bc, rbx);
}
__ jmp(Done);
__ bind(notChar);
//判断是否short类型
__ cmpl(flags, stos);
__ jcc(Assembler::notEqual, notShort);
// stos
__ load_signed_short(rax, field);
__ push(stos);
// Rewrite bytecode to be faster
if (!is_static) {
patch_bytecode(Bytecodes::_fast_sgetfield, bc, rbx);
}
__ jmp(Done);
__ bind(notShort);
//判断是否long类型
__ cmpl(flags, ltos);
__ jcc(Assembler::notEqual, notLong);
// ltos
__ movq(rax, field);
__ push(ltos);
// Rewrite bytecode to be faster
if (!is_static) {
patch_bytecode(Bytecodes::_fast_lgetfield, bc, rbx);
}
__ jmp(Done);
__ bind(notLong);
//判断是否float类型
__ cmpl(flags, ftos);
__ jcc(Assembler::notEqual, notFloat);
// ftos
__ movflt(xmm0, field);
__ push(ftos);
// Rewrite bytecode to be faster
if (!is_static) {
patch_bytecode(Bytecodes::_fast_fgetfield, bc, rbx);
}
__ jmp(Done);
__ bind(notFloat);
// 只剩一种double类型
__ movdbl(xmm0, field);
__ push(dtos);
// Rewrite bytecode to be faster
if (!is_static) {
patch_bytecode(Bytecodes::_fast_dgetfield, bc, rbx);
}
__ bind(Done);
// [jk] not needed currently
// volatile_barrier(Assembler::Membar_mask_bits(Assembler::LoadLoad |
// Assembler::LoadStore));
这里有几个概念解释一下:
void TemplateTable::fast_accessfield(TosState state) {
transition(atos, state);
//发布JVMTI事件
if (JvmtiExport::can_post_field_access()) {
// Check to see if a field access watch has been set before we
// take the time to call into the VM.
Label L1;
__ mov32(rcx, ExternalAddress((address) JvmtiExport::get_field_access_count_addr()));
__ testl(rcx, rcx);
__ jcc(Assembler::zero, L1);
// access constant pool cache entry
__ get_cache_entry_pointer_at_bcp(c_rarg2, rcx, 1);
__ verify_oop(rax);
__ push_ptr(rax); // save object pointer before call_VM() clobbers it
__ mov(c_rarg1, rax);
// c_rarg1: object pointer copied above
// c_rarg2: cache entry pointer
__ call_VM(noreg,
CAST_FROM_FN_PTR(address,
InterpreterRuntime::post_field_access),
c_rarg1, c_rarg2);
__ pop_ptr(rax); // restore object pointer
__ bind(L1);
}
//获取该字段对应的ConstantPoolCacheEntry
__ get_cache_and_index_at_bcp(rcx, rbx, 1);
//获取字段偏移量
__ movptr(rbx, Address(rcx, rbx, Address::times_8,
in_bytes(ConstantPoolCache::base_offset() +
ConstantPoolCacheEntry::f2_offset())));
//校验rax中实例对象oop,这里没有像getfield一样先把实例对象从栈顶pop到rax中,而是直接校验
//这是因为fast_accessfield类指令的栈顶缓存类型是atos而不是vtos,即上一个指令执行完后会自动将待读取的实例放入rax中
__ verify_oop(rax);
__ null_check(rax);
Address field(rax, rbx, Address::times_1);
// access field
switch (bytecode()) {
case Bytecodes::_fast_agetfield:
//将属性值拷贝到rax中
__ load_heap_oop(rax, field);
__ verify_oop(rax);
break;
case Bytecodes::_fast_lgetfield:
__ movq(rax, field);
break;
case Bytecodes::_fast_igetfield:
__ movl(rax, field);
break;
case Bytecodes::_fast_bgetfield:
__ movsbl(rax, field);
break;
case Bytecodes::_fast_sgetfield:
__ load_signed_short(rax, field);
break;
case Bytecodes::_fast_cgetfield:
__ load_unsigned_short(rax, field);
break;
case Bytecodes::_fast_fgetfield:
__ movflt(xmm0, field);
break;
case Bytecodes::_fast_dgetfield:
__ movdbl(xmm0, field);
break;
default:
ShouldNotReachHere();
}
}
我们看到,其实对于volatile变量来说,在读取的时候,并没有做处理。我们来看写操作。
03
写操作
void TemplateTable::putfield(int byte_no) {
putfield_or_static(byte_no, false);
}
void TemplateTable::putstatic(int byte_no) {
putfield_or_static(byte_no, true);
}
void TemplateTable::putfield_or_static(int byte_no, bool is_static) {
transition(vtos, vtos);
const Register cache = rcx;
const Register index = rdx;
const Register obj = rcx;
const Register off = rbx;
const Register flags = rax;
const Register bc = c_rarg3;
//找到该属性对应的ConstantPoolCacheEntry
resolve_cache_and_index(byte_no, cache, index, sizeof(u2));
//发布事件
jvmti_post_field_mod(cache, index, is_static);
//获取字段偏移量,flags,如果是静态属性获取对应类的class实例
load_field_cp_cache_entry(obj, cache, index, off, flags, is_static);
Label notVolatile, Done;
__ movl(rdx, flags);
__ shrl(rdx, ConstantPoolCacheEntry::is_volatile_shift);
__ andl(rdx, 0x1);
//.......
// btos
{
//将栈顶的待写入值放入rax中
__ pop(btos);
//待写入的值pop出去后,如果是实例属性则栈顶元素为准备写入的实例
//校验该实例是否为空,将其拷贝到obj寄存器中
if (!is_static) pop_and_check_object(obj);
//将rax中的待写入值写入到filed地址处
__ movb(field, rax);
if (!is_static) {
//将该字节码改写成_fast_bputfield,下一次执行时直接执行_fast_bputfield,无需再次判断属性类型
patch_bytecode(Bytecodes::_fast_bputfield, bc, rbx, true, byte_no);
}
__ jmp(Done);
}
__ bind(notByte);
//判断是否boolean类型
__ cmpl(flags, ztos);
__ jcc(Assembler::notEqual, notBool);
// ztos
{
__ pop(ztos);
if (!is_static) pop_and_check_object(obj);
__ andl(rax, 0x1);
__ movb(field, rax);
if (!is_static) {
patch_bytecode(Bytecodes::_fast_zputfield, bc, rbx, true, byte_no);
}
__ jmp(Done);
}
__ bind(notBool);
//判断是否引用类型
__ cmpl(flags, atos);
__ jcc(Assembler::notEqual, notObj);
__ bind(notObj);
//判断是否int类型
__ cmpl(flags, itos);
__ jcc(Assembler::notEqual, notInt);
// itos
{
__ pop(itos);
if (!is_static) pop_and_check_object(obj);
__ movl(field, rax);
if (!is_static) {
patch_bytecode(Bytecodes::_fast_iputfield, bc, rbx, true, byte_no);
}
__ jmp(Done);
}
__ bind(notInt);
//判断是否char类型
__ cmpl(flags, ctos);
__ jcc(Assembler::notEqual, notChar);
// ctos
{
__ pop(ctos);
if (!is_static) pop_and_check_object(obj);
__ movw(field, rax);
if (!is_static) {
patch_bytecode(Bytecodes::_fast_cputfield, bc, rbx, true, byte_no);
}
__ jmp(Done);
}
__ bind(notChar);
//判断是否short类型
__ cmpl(flags, stos);
__ jcc(Assembler::notEqual, notShort);
// stos
{
__ pop(stos);
if (!is_static) pop_and_check_object(obj);
__ movw(field, rax);
if (!is_static) {
patch_bytecode(Bytecodes::_fast_sputfield, bc, rbx, true, byte_no);
}
__ jmp(Done);
}
__ bind(notShort);
//判断是否long类型
__ cmpl(flags, ltos);
__ jcc(Assembler::notEqual, notLong);
// ltos
{
__ pop(ltos);
if (!is_static) pop_and_check_object(obj);
__ movq(field, rax);
if (!is_static) {
patch_bytecode(Bytecodes::_fast_lputfield, bc, rbx, true, byte_no);
}
__ jmp(Done);
}
__ bind(notLong);
//判断是否float类型
__ cmpl(flags, ftos);
__ jcc(Assembler::notEqual, notFloat);
// ftos
{
__ pop(ftos);
if (!is_static) pop_and_check_object(obj);
__ movflt(field, xmm0);
if (!is_static) {
patch_bytecode(Bytecodes::_fast_fputfield, bc, rbx, true, byte_no);
}
__ jmp(Done);
}
__ bind(notFloat);
// dtos
{
//只剩一个,double类型
__ pop(dtos);
if (!is_static) pop_and_check_object(obj);
__ movdbl(field, xmm0);
if (!is_static) {
patch_bytecode(Bytecodes::_fast_dputfield, bc, rbx, true, byte_no);
}
}
__ bind(Done);
//判断是否volatile变量,如果不是则跳转到notVolatile
__ testl(rdx, rdx);
__ jcc(Assembler::zero, notVolatile);
//如果是
volatile_barrier(Assembler::Membar_mask_bits(Assembler::StoreLoad |
Assembler::StoreStore));
__ bind(notVolatile);
}
同理,我们根据上面的template表,可以看到实际执行的代码段fast_storefield如下。
void TemplateTable::fast_storefield(TosState state) {
transition(state, vtos);
ByteSize base = ConstantPoolCache::base_offset();
//发布jvmti时间
jvmti_post_fast_field_mod();
//获取该字段对应的ConstantPoolCacheEntry
__ get_cache_and_index_at_bcp(rcx, rbx, 1);
//获取该字段的flags
__ movl(rdx, Address(rcx, rbx, Address::times_8,
in_bytes(base +
ConstantPoolCacheEntry::flags_offset())));
//获取该字段的偏移量
__ movptr(rbx, Address(rcx, rbx, Address::times_8,
in_bytes(base + ConstantPoolCacheEntry::f2_offset())));
Label notVolatile;
__ shrl(rdx, ConstantPoolCacheEntry::is_volatile_shift);
__ andl(rdx, 0x1);
//将待写入的实例对象pop到rcx中,注意此处并没有像putfield一样把待写入的值先pop到rax中,
//这是因为fast_storefield类的栈顶缓存类型不是vtos而是具体的写入值类型对应的类型,即上一个
//字节码指令执行完成后会自动将待写入的值放入rax中
pop_and_check_object(rcx);
// field address
const Address field(rcx, rbx, Address::times_1);
// access field
switch (bytecode()) {
case Bytecodes::_fast_aputfield:
do_oop_store(_masm, field, rax, _bs->kind(), false);
break;
case Bytecodes::_fast_lputfield:
//将rax中的属性值写入到field地址
__ movq(field, rax);
break;
case Bytecodes::_fast_iputfield:
__ movl(field, rax);
break;
case Bytecodes::_fast_zputfield:
__ andl(rax, 0x1); // boolean is true if LSB is 1
// fall through to bputfield
case Bytecodes::_fast_bputfield:
__ movb(field, rax);
break;
case Bytecodes::_fast_sputfield:
// fall through
case Bytecodes::_fast_cputfield:
__ movw(field, rax);
break;
case Bytecodes::_fast_fputfield:
__ movflt(field, xmm0);
break;
case Bytecodes::_fast_dputfield:
__ movdbl(field, xmm0);
break;
default:
ShouldNotReachHere();
}
//判断是否volatile变量
__ testl(rdx, rdx);
__ jcc(Assembler::zero, notVolatile);
volatile_barrier(Assembler::Membar_mask_bits(Assembler::StoreLoad |
Assembler::StoreStore));
__ bind(notVolatile);
}
如果是volatile变量,在属性修改完成后就会执行lock addl $0×0,(%rsp);。
lock指令有两个含义:
04
解释器下实现
hotspot解释器模块(hotspot\src\share\vm\interpreter)有两个实现:
C++解释器 : bytecodeInterpreter + cppInterpreter
模板解释器 : templateTable + templateInterpreter
模板解释器我们看完了,下面我们来看一下C++ 解释器的实现。代码位于hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp
可以看到,在执行完操作之后,bytecodeInterpreter 增加了 OrderAccess::storeload()`屏障,代码如下:
注意:在java9以后的版本当中,除了之外,都换成了 fence 方法以外,都换成了编译器屏障,如下:
3
总结
你以为就完了,那就太容易,只有理论没有时间自然不能信服,下面我需要用到反汇编指令查看上述的问题。首先,我们用到的linux版本如下
root@ubuntu:~/workplace/javalean/debug/javaclass$ uname -a
Linux ubuntu 5.4.0-73-generic #82~18.04.1-Ubuntu SMP Fri Apr 16 15:10:02 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
使用hsdis-amd64.so 首先你需要在网上找一个 hsdis-amd64.so 库,然后放到jdk的目录下/jre/lib/amd66
编写我们的程序,使得程序可以执行 如下:
编译,并加上打印参数如下
- -XX:+UnlockDiagnosticVMOptions
- -XX:PrintAssembly 打印JIT编译后的汇编
- -XX:CompileCommand=print,*MyClass.myMethod 过滤输出
过滤 add 和add2的方法,这里加上了 -Xcomp 确保是使用模板解释器来进行编译
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=compileonly,*VolatileCode::add* VolatileCode
得到结果如下,add 方法下:
add2的方法:
对于add 和add2 来说 add 比add多了一个 lock指令。