JVM进阶教程之字段访问优化浅析

所属分类: 软件编程 / java 阅读数: 26
收藏 0 赞 0 分享

前言

在实际中,Java程序中的对象或许 本身就是逃逸 的,或许因为 方法内联不够彻底 而被即时编译器 当成是逃逸 的,这两种情况都将

导致即时编译器 无法进行标量替换 ,这时,针对对象字段访问的优化显得更为重要。

static int bar(Foo o, int x) {
 o.a = x;
 return o.a;
}
  1. 对象o是传入参数, 不属于逃逸分析的范围 (JVM中的逃逸分析针对的是 新建对象 )
  2. 该方法会将所传入的int型参数x的值存储至实例字段Foo.a中,然后再读取并返回同一字段的值
  3. 这段代码涉及 两次 内存访问操作:存储和读取实例字段Foo.a
  4. 代码可以手工优化成如下
static int bar(Foo o, int x) {
 o.a = x;
 return x;
}

即时编译器也能作出类似的 自动优化

字段读取优化

即时编译器会优化 实例字段 和 静态字段 的访问,以 减少总的内存访问次数

即时编译器将 沿着控制流 ,缓存各个字段 存储节点 将要存储的值,或者字段 读取节点 所得到的值

  • 当即时编译器 遇到对同一字段的读取节点 时,如果缓存值还没有失效,那么将读取节点 替换 为该缓存值
  • 当即时编译器 遇到对同一字段的存储节点 时,会 更新 所缓存的值
    • 当即时编译器遇到 可能更新 字段的节点时,它会采取 保守 的策略, 舍弃所有的缓存值
    • 方法调用节点 :在即时编译器看来,方法调用会执行 未知代码
    • 内存屏障节点 :其他线程可能异步更新了字段

样例1

static int bar(Foo o, int x) {
 int y = o.a + x;
 return o.a + y;
}

实例字段Foo.a被读取两次,即时编译器会将第一次读取的值缓存起来,并且 替换 第二次的字段读取操作,以 节省 一次内存访问

static int bar(Foo o, int x) {
 int t = o.a;
 int y = t + x;
 return t + y;
}

样例2

static int bar(Foo o, int x) {
 o.a = 1;
 if (o.a >= 0)
  return x;
 else
  return -x;
}

字段读取节点被替换成一个 常量 ,进一步触发更多的优化

static int bar(Foo o, int x) {
 o.a = 1;
 return x;
}

样例3

class Foo {
 boolean a;
 void bar() {
  a = true;
  while (a) {}
 }
 void whatever() { a = false; }
}

即时编译器会将while循环中读取实例字段a的操作 直接替换为常量true

void bar() {
 a = true;
 while (true) {}
}
// 生成的机器码将陷入这一死循环中
0x066b: mov r11,QWORD PTR [r15+0x70] // 安全点测试
0x066f: test DWORD PTR [r11],eax  // 安全点测试
0x0672: jmp 0x066b     // while (true)

1、可以通过 volatile 关键字标记实例字段a,以 强制 对a的读取

2、实际上,即时编译器将 在volatile字段访问前后插入内存屏障节点

  • 这些 内存屏障节点 将 阻止 即时编译器 将屏障之前所缓存的值用于屏障之后的读取节点之上
  • 在X86_64平台上,volatile字段读取前后的内存屏障都是no-op
    • 在 即时编译过程中的屏障节点 ,还是会 阻止即时编译器的字段读取优化
    • 强制在循环中使用 内存读取指令 访问实例字段Foo.a的最新值

3、同理, 加解锁操作同样也会阻止即时编译器的字段读取优化

字段存储优化

如果一个字段先后被存储了两次,而且这 两次存储之间没有对第一次存储内容读取 ,那么即时编译器将 消除 第一个字段存储

样例1

class Foo {
 int a = 0;
 void bar() {
  a = 1;
  a = 2;
 }
}

即时编译器将消除bar方法的冗余存储

void bar() {
 a = 2;
}

样例2

即便在某个字段的两个存储操作之间读取该字段,即时编译器也可能在 字段读取优化 的帮助下,将第一个存储操作当作 冗余存储

场景:例如两个存储操作之间隔着许多代码,又或者因为 方法内联 的原因,将两个存储操作纳入到同一编译单元里(如构造器中字段的初始化以及随后的更新)

class Foo {
 int a = 0;
 void bar() {
  a = 1;
  int t = a;
  a = t + 2;
 }
}
// 优化为
class Foo {
 int a = 0;
 void bar() {
  a = 1;
  int t = 1;
  a = t + 2;
 }
}
// 进一步优化为
class Foo {
 int a = 0;
 void bar() {
  a = 3;
 }
}

如果所存储的字段被标记为 volatile ,那么即时编译器也 不能消除冗余存储

死代码消除

样例1

int bar(int x, int y) {
 int t = x*y;
 t = x+y;
 return t;
}

没有节点依赖于t的第一个值 x*y ,因此该乘法运算将被消除

int bar(int x, int y) {
 return x+y;
}

样例2

int bar(boolean f, int x, int y) {
 int t = x*y;
 if (f)
  t = x+y;
 return t;
}

部分程序路径上有冗余存储(f=true),该路径上的乘法运算将会被消除

int bar(boolean f, int x, int y) {
 int t;
 if (f)
  t = x+y;
 else
  t = x*y;
 return t;
}

样例3

int bar(int x) {
 if (false)
  return x;
 else
  return -x;
}

不可达分支指的是任何程序路径都不可达到的分支,即时编译器将 消除不可达分支

int bar(int x) {
 return -x;
}

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

更多精彩内容其他人还在看

Collections工具类_动力节点Java学院整理

Collections工具类提供了大量针对Collection/Map的操作。这篇文章主要介绍了Collections工具类_动力节点Java学院整理,需要的朋友可以参考下
收藏 0 赞 0 分享

SpringMVC集成Swagger实例代码

本篇文章主要介绍了SpringMVC集成Swagger实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

十大常见Java String问题_动力节点Java学院整理

本文介绍Java中关于String最常见的10个问题,需要的朋友参考下吧
收藏 0 赞 0 分享

Java微信公众平台开发(13) 微信JSSDK中Config配置

这篇文章主要为大家详细介绍了Java微信公众平台开发第十三步,微信JSSDK中Config配置,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Java实现一个达达租车系统的步骤详解

这篇文章主要给大家介绍了利用Java实现一个达达租车系统的步骤,文中给出了详细的实现思路和示例代码,并在文末给出了完整的源码供大家学习下载,需要的朋友可以参考借鉴,下面来一起看看吧。
收藏 0 赞 0 分享

Java微信公众平台开发(14) 微信web开发者工具使用

这篇文章主要为大家详细介绍了Java微信公众平台开发第十四步,微信web开发者工具的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Spring Boot整合RabbitMQ实例(Topic模式)

Topic Exchange 转发消息主要是根据通配符。接下来通过本文给大家分享Spring Boot整合RabbitMQ实例(Topic模式),需要的朋友参考下吧
收藏 0 赞 0 分享

Java微信公众平台开发(15) 微信JSSDK的使用

这篇文章主要为大家详细介绍了Java微信公众平台开发第十五步,微信JSSDK的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

java多线程的同步方法实例代码

这篇文章主要介绍了 java多线程的同步方法实例代码的相关资料,需要的朋友可以参考下
收藏 0 赞 0 分享

spring boot整合RabbitMQ实例详解(Fanout模式)

这篇文章主要介绍了spring boot整合RabbitMQ的实例讲解(Fanout模式),非常不错,具有参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多