第25篇-虚拟机对象操作指令之putstatic

之前已经介绍了getstatic与getfield指令的汇编代码执行逻辑,这一篇介绍putstatic指令的执行逻辑,putfield将不再介绍,大家可以自己去研究,相信大家有这个实力。

putstatic指令为指定类的静态域赋值。字节码指令的格式如下:

putstatic indexbyte1 indexbyte2

无符号数indexbyte1和indexbyte2构建为(indexbyte1<<8)|indexbyte2,该索引所指向的运行时常量池项应当是一个字段的符号引用。

指令的模板定义如下:

def(Bytecodes::_putstatic           , ubcp|____|clvm|____, vtos, vtos, putstatic           , f2_byte      );  

生成函数为putstatic(),函数的实现如下:

void TemplateTable::putstatic(int byte_no) {
  putfield_or_static(byte_no, false);
}

调用TemplateTable::putfield_or_static()函数生成的机器指令对应的汇编代码如下:

0x00007fffe101ff90: movzwl 0x1(%r13),%edx
0x00007fffe101ff95: mov    -0x28(%rbp),%rcx
0x00007fffe101ff99: shl    $0x2,%edx
0x00007fffe101ff9c: mov    0x10(%rcx,%rdx,8),%ebx
0x00007fffe101ffa0: shr    $0x18,%ebx
0x00007fffe101ffa3: and    $0xff,%ebx
// 是否已经对putstatic指令进行了连接,如果已经连接,则跳转到resolved
0x00007fffe101ffa9: cmp    $0xb3,%ebx
0x00007fffe101ffaf: je     0x00007fffe102004e 

调用TemplateTable::resolve_cache_and_index()函数生成如下汇编代码:

// 执行到这里,说明字段还没有连接
0x00007fffe101ffb5: mov    $0xb3,%ebx

// 调用MacroAssembler::call_VM()函数生成如下代码,
// 用来执行InterpreterRuntime::resolve_get_put()函数
0x00007fffe101ffba: callq  0x00007fffe101ffc4
0x00007fffe101ffbf: jmpq   0x00007fffe1020042
0x00007fffe101ffc4: mov    %rbx,%rsi
0x00007fffe101ffc7: lea    0x8(%rsp),%rax
0x00007fffe101ffcc: mov    %r13,-0x38(%rbp)
0x00007fffe101ffd0: mov    %r15,%rdi
0x00007fffe101ffd3: mov    %rbp,0x200(%r15)
0x00007fffe101ffda: mov    %rax,0x1f0(%r15)
0x00007fffe101ffe1: test   $0xf,%esp
0x00007fffe101ffe7: je     0x00007fffe101ffff
0x00007fffe101ffed: sub    $0x8,%rsp
0x00007fffe101fff1: callq  0x00007ffff66b567c
0x00007fffe101fff6: add    $0x8,%rsp
0x00007fffe101fffa: jmpq   0x00007fffe1020004
0x00007fffe101ffff: callq  0x00007ffff66b567c
0x00007fffe1020004: movabs $0x0,%r10
0x00007fffe102000e: mov    %r10,0x1f0(%r15)
0x00007fffe1020015: movabs $0x0,%r10
0x00007fffe102001f: mov    %r10,0x200(%r15)
0x00007fffe1020026: cmpq   $0x0,0x8(%r15)
0x00007fffe102002e: je     0x00007fffe1020039
0x00007fffe1020034: jmpq   0x00007fffe1000420
0x00007fffe1020039: mov    -0x38(%rbp),%r13
0x00007fffe102003d: mov    -0x30(%rbp),%r14
0x00007fffe1020041: retq   


0x00007fffe1020042: movzwl 0x1(%r13),%edx
0x00007fffe1020047: mov    -0x28(%rbp),%rcx
0x00007fffe102004b: shl    $0x2,%edx

接下来生成的汇编代码如下:

// ---- resolved ----

// 执行如下代码时,表示字段已经连接完成

0x00007fffe102004e: mov    0x20(%rcx,%rdx,8),%rbx
0x00007fffe1020053: mov    0x28(%rcx,%rdx,8),%eax
0x00007fffe1020057: mov    0x18(%rcx,%rdx,8),%rcx
0x00007fffe102005c: mov    0x70(%rcx),%rcx
0x00007fffe1020060: mov    %eax,%edx
// 将_flags向右移动21位,判断是否有volatile关键字
0x00007fffe1020062: shr    $0x15,%edx
0x00007fffe1020065: and    $0x1,%edx
// 将_flags向右移动28位,剩下TosState
0x00007fffe1020068: shr    $0x1c,%eax

// 如果不为btos,则跳转到notByte
0x00007fffe102006b: and    $0xf,%eax
0x00007fffe102006e: jne    0x00007fffe1020083

// btos

// 将栈顶的值存储到%eax中,这个值会写入到对应的字段中
0x00007fffe1020074: mov    (%rsp),%eax
0x00007fffe1020077: add    $0x8,%rsp
// %rcx为_java_mirror,%rbx为_f2,表示域在类中的偏移
0x00007fffe102007b: mov    %al,(%rcx,%rbx,1)
0x00007fffe102007e: jmpq   0x00007fffe10201be  // 跳转到Done
// -- notByte --
// 如果不为atos,则跳转到notObj
0x00007fffe1020083: cmp    $0x7,%eax
0x00007fffe1020086: jne    0x00007fffe1020130

// atos
// 将栈顶的值弹出到%rax中,这个值将用来更新对应字段的值
0x00007fffe102008c: pop    %rax
// ...
// 将值更新到对应的字段上
0x00007fffe1020115: mov    %eax,(%rcx,%rbx,1)
// 其中的0x9是CardTableModRefBS::card_shift,shr表示逻辑右移,由于%rcx指向的是
// java.lang.Class实例的首地址,向右移后%rcx就算出了卡表的索引
0x00007fffe1020118: shr    $0x9,%rcx
// 地址常量$0x7fffe07ff000表示卡表的基地址
0x00007fffe102011c: movabs $0x7fffe07ff000,%r10 
// 将对应的卡表项标记为脏,其中常量0x0就表示是脏卡
0x00007fffe1020126: movb $0x0,(%r10,%rcx,1) 
0x00007fffe102012b: jmpq 
0x00007fffe10201be // 跳转到Done
// ---- notObj ----
// 如果不为itos,那么跳转到notInt
0x00007fffe1020130: cmp    $0x3,%eax
0x00007fffe1020133: jne    0x00007fffe1020148

// itos
0x00007fffe1020139: mov    (%rsp),%eax
// 如果不为ctos,则跳转到notChar
0x00007fffe102013c: add    $0x8,%rsp
0x00007fffe1020140: mov    %eax,(%rcx,%rbx,1)
0x00007fffe1020143: jmpq   0x00007fffe10201be   // 跳转到Done
0x00007fffe1020148: cmp    $0x1,%eax
0x00007fffe102014b: jne    0x00007fffe1020161

// ctos
0x00007fffe1020151: mov    (%rsp),%eax
0x00007fffe1020154: add    $0x8,%rsp
0x00007fffe1020158: mov    %ax,(%rcx,%rbx,1)
0x00007fffe102015c: jmpq   0x00007fffe10201be  // 跳转到Done
0x00007fffe1020161: cmp    $0x2,%eax
0x00007fffe1020164: jne    0x00007fffe102017a

// stos
0x00007fffe102016a: mov    (%rsp),%eax
0x00007fffe102016d: add    $0x8,%rsp
0x00007fffe1020171: mov    %ax,(%rcx,%rbx,1)
0x00007fffe1020175: jmpq   0x00007fffe10201be  // 跳转到Done
0x00007fffe102017a: cmp    $0x4,%eax
0x00007fffe102017d: jne    0x00007fffe1020194

// ltos
0x00007fffe1020183: mov    (%rsp),%rax
0x00007fffe1020187: add    $0x10,%rsp
0x00007fffe102018b: mov    %rax,(%rcx,%rbx,1)
0x00007fffe102018f: jmpq   0x00007fffe10201be  // 跳转到Done
0x00007fffe1020194: cmp    $0x5,%eax
0x00007fffe1020197: jne    0x00007fffe10201b0

// ftos
0x00007fffe102019d: vmovss (%rsp),%xmm0
0x00007fffe10201a2: add    $0x8,%rsp
0x00007fffe10201a6: vmovss %xmm0,(%rcx,%rbx,1)
0x00007fffe10201ab: jmpq   0x00007fffe10201be   // 跳转到Done

// dtos
0x00007fffe10201b0: vmovsd (%rsp),%xmm0
0x00007fffe10201b5: add    $0x10,%rsp
0x00007fffe10201b9: vmovsd %xmm0,(%rcx,%rbx,1)


// ---- Done ----

0x00007fffe10201be: test   %edx,%edx
0x00007fffe10201c0: je     0x00007fffe10201cb
0x00007fffe10201c6: lock addl $0x0,(%rsp)

// ---- notVolatile ---- 
  

在如上代码中,最值得关注的2个点如下:

(1)更新引用字段时,通过屏障将对应的卡表项标记为脏,这样可在GC过程中扫描脏卡就可将活跃对象标记出来而不会造成遗漏;

(2)当字段有volatile关键字修饰时,需要填写lock指令前缀,这个前缀在之前介绍x86-64机器指令时没有介绍过,这里摘抄一下别人对此指令的介绍:

Intel手册对 lock 前缀的说明如下:

  1. 确保被修饰指令执行的原子性;
  2. 禁止该指令与前面和后面的读写指令重排序;
  3. 指令执行完后把写缓冲区的所有数据刷新到内存中(这样这个指令之前的其他修改对所有处理器可见)。

在所有的 X86 CPU 上都具有锁定一个特定内存地址的能力,当这个特定内存地址被锁定后,它就可以阻止其他的系统总线读取或修改这个内存地址。这种能力是通过 lock 指令前缀再加上下面的汇编指令来实现的。当使用 lock 指令前缀时,它会使 CPU 宣告一个 lock# 信号,这样就能确保在多处理器系统或多线程竞争的环境下互斥地使用这个内存地址。当指令执行完毕,这个锁定动作也就会消失。

推荐阅读:

第1篇-关于JVM运行时,开篇说的简单些

第2篇-JVM虚拟机这样来调用Java主类的main()方法

第3篇-CallStub新栈帧的创建

第4篇-JVM终于开始调用Java主类的main()方法啦

第5篇-调用Java方法后弹出栈帧及处理返回结果

第6篇-Java方法新栈帧的创建

第7篇-为Java方法创建栈帧

第8篇-dispatch_next()函数分派字节码

第9篇-字节码指令的定义

第10篇-初始化模板表

第11篇-认识Stub与StubQueue

第12篇-认识CodeletMark

第13篇-通过InterpreterCodelet存储机器指令片段

第14篇-生成重要的例程

第15章-解释器及解释器生成器

第16章-虚拟机中的汇编器

第17章-x86-64寄存器

第18章-x86指令集之常用指令

第19篇-加载与存储指令(1)

第20篇-加载与存储指令之ldc与_fast_aldc指令(2)

第21篇-虚拟机字节码之运算指令

第22篇-虚拟机字节码指令之类型转换

第23篇-虚拟机对象操作指令之getstatic

第24篇-虚拟机对象操作指令之getfield

如果有问题可直接评论留言或加作者微信mazhimazh

关注公众号,有HotSpot VM源码剖析系列文章!

第25篇-虚拟机对象操作指令之putstatic

  

 

 

 

 

 

 

第25篇-虚拟机对象操作指令之putstatic

上一篇:idea使用docker查看日志乱码问题


下一篇:kivy之Bubble气泡部件的实操学习