欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > JVM常用概念之即时常量

JVM常用概念之即时常量

2025/3/16 7:46:05 来源:https://blog.csdn.net/nanxiaotao/article/details/146194900  浏览:    关键词:JVM常用概念之即时常量

问题

JVM的优化器对程序可以利用的常量值做了什么工作?

基础知识

常量的优化是最便捷的,常量已经在编译时进行了处理,在应用运行时不需要做如何操作,那对于变量、常量变量、静态变量、常量静态变量编译和运行时会有什么区别呢?

class M {final int x;M(int x) { this.x = x; }
}M m1 = new M(1337);
M m2 = new M(8080);void work(M m) {return m.x; // what to compile in here, 1337 or 8080?
}

实验

源码

在这里插入代码片@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class JustInTimeConstants {static final long x_static_final = Long.getLong("divisor", 1000);static       long x_static       = Long.getLong("divisor", 1000);final long x_inst_final   = Long.getLong("divisor", 1000);long x_inst         = Long.getLong("divisor", 1000);@Benchmark public long _static_final() { return 1000 / x_static_final; }@Benchmark public long _static()       { return 1000 / x_static;       }@Benchmark public long _inst_final()   { return 1000 / x_inst_final;   }@Benchmark public long _inst()         { return 1000 / x_inst;         }}

运行结果

Benchmark                          Mode  Cnt  Score   Error  Units
JustInTimeConstants._inst          avgt   15  9.670 ± 0.014  ns/op
JustInTimeConstants._inst_final    avgt   15  9.690 ± 0.036  ns/op
JustInTimeConstants._static        avgt   15  9.705 ± 0.015  ns/op
JustInTimeConstants._static_final  avgt   15  1.899 ± 0.001  ns/op

通过-prof perfasm进行进一步分析,结果如下:

# JustInTimeConstants._inst / _inst_final hottest loop
0.21%            ↗  mov    0x40(%rsp),%r10
0.02%            │  mov    0x18(%r10),%r10    ; get field x_inst / x_inst_final|  ...
0.13%            │  idiv   %r10               ; ldiv
76.59%   95.38%  │  mov    0x38(%rsp),%rsi    ; prepare and consume the value (JMH infra)
0.40%            │  mov    %rax,%rdx
0.10%            │  callq  CONSUME|  ...
1.51%            │  test   %r11d,%r11d        ; call @Benchmark again╰  je     BACK

如上述执行结果所示,大部分时间成本都花在执行实际的整数除法上。

# JustInTimeConstants._static hottest loop
0.04%            ↗  movabs $0x7826385f0,%r10  ; native mirror for JustInTimeConstants.class
0.02%            │  mov    0x70(%r10),%r10    ; get static x_static|  ...
0.02%            │  idiv   %r10               ;*ldiv
72.78%   95.51%  |  mov    0x38(%rsp),%rsi    ; prepare and consume the value (JMH infra)
0.38%            │  mov    %rax,%rdx
0.04%    0.06%   │  data16 xchg %ax,%ax0.02%   │  callq  CONSUME|  ...
0.13%            │  test   %r11d,%r11d        ; call @Benchmark again╰  je     BACK

如上述执行结果所示,static修饰的字段,它从静态字段所在的本地类镜像中读取静态字段。由于运行时知道我们正在处理的类(静态字段访问是静态解析的!),我们将常量指针内联到镜像,并通过其预定义的偏移量访问该字段。但是,由于我们不知道字段的值是什么——实际上有人可能在代码生成后更改了它——我们仍然执行相同的整数除法。

# JustInTimeConstants._static_final hottest loop
1.36%    1.40%   ↗  mov    %r8,(%rsp)
7.73%    7.40%   │  mov    0x8(%rsp),%rdx       ; <--- slot holding the "long" constant "1"
0.45%    0.51%   │  mov    0x38(%rsp),%rsi      ; prepare and consume the value (JMH infra)
3.59%    3.24%   │  nop
1.44%    0.54%   │  callq  CONSUME| ...
3.46%    2.37%   │  test   %r10d,%r10d          ; call @Benchmark again╰  je     BACK

如上述_static_final的汇编执行结果,JIT 编译器确切地知道它正在处理的值,因此它可以积极地对其进行优化。在这里,循环计算只是重用了保存预先计算的值“1000 / 1000”的槽,即“1”。因此,性能可以通过编译器通过static final进行常量折叠的能力来解释。

总结

在上述示例中,字节码编译器(例如 javac)不知道static final字段的值是什么,因为该字段是用运行时值初始化的。当 JIT 编译发生时,类已成功初始化,并且值已存在,可以使用!这实际上是即时常量。这允许开发非常高效但运行时可调整的代码:事实上,整个示例被认为是基于预处理器的断言的替代品。而在C++领域,因为编译是完全提前的,因此如果你想让关键代码依赖于运行时选项。

这个示例的一个重要部分是解释器/分层编译。类初始化器通常是冷代码,因为它们只执行一次。但更重要的是处理类初始化的延迟部分,当我们想在第一次访问字段时加载和初始化类时。解释器或基线 JIT 编译器(例如 Hotspot 中的 C1)为我们运行它。当优化 JIT 编译器(例如 Hotspot 中的 C2)为相同方法运行时,重新编译的方法所需的类通常已完全初始化,并且它们的static final已完全已知。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词