在 Java 中,构造函数中最终的字段赋值涉及哪些操作?
In Java , what operations are involved in the final field assignment in the constructor?
如果 Simple class 字段 a 被 final 关键字。
如果a是一个final字段,这个程序会正常退出;
如果是普通字段,这个程序会一直保持运行。
这种情况只出现在C2编译器中。
我认为这种情况与flag字段在多线程中的可见性有关environment.However,我尝试通过观察汇编代码hsdis ,发现有无final关键字的区别。
我没发现什么不同。
实际上,我知道存储 "final" 字段不会在 x86 平台上发出任何汇编指令。可是为什么会出现这种情况呢?有没有一些我不知道的特殊操作?
感谢阅读。
class MultiProcessorTask {
private boolean flag= true;
public void runMethod() {
while (flag) {
new Simple(1);
}
}
public void stopMethod() {
System.out.println("change 'flag' field ...");
flag= false;
}
}
class ThreadA extends Thread {
private MultiProcessorTask task;
ThreadA(MultiProcessorTask task) {this.task = task;}
@Override
public void run() {
task.runMethod();
}
}
class Simple {
private int a; // modify "a" as "final"
Simple(int a) {this.a = a;}
}
public class TestRun {
public static void main(String[] args) {
MultiProcessorTask task = new MultiProcessorTask();
ThreadA a = new ThreadA(task);
a.start();
task.stopMethod();
System.out.println("it's over");
}
}
反汇编代码输出:
final
案例中的runMethod
:
the final case
non-final
案例中的runMethod
:
the non-final case
你反汇编错了。我的意思是,两个屏幕截图上都有一个独立编译的 runMethod
,但是,它在现实中从未执行过。相反,执行从解释器跳转到 OSR 存根。您需要查找标有 %
符号(表示堆栈替换)的编译。
Compiled method (c2) 646 662 % MultiProcessorTask::runMethod @ 0 (20 bytes)
^
OSR
非最终案例和最终案例的编译代码存在差异。我只留下了相关部分:
非最终版
0x000000000309ae31: test %eax,-0x5aae37(%rip) ; safepoint poll
0x000000000309ae37: jmp 0x000000000309ae31 ; loop
决赛
0x0000000002c3a3a0: test %eax,-0x265a3a6(%rip) ; safepoint poll
0x0000000002c3a3a6: movzbl 0xc(%rbx),%r11d ; load 'flag' field
0x0000000002c3a3ab: test %r11d,%r11d
0x0000000002c3a3ae: jne 0x0000000002c3a3a0 ; loop if flag == true
的确,第一种情况编译成无限循环,而第二种情况保留字段检查。
你看,在这两种情况下根本没有 Simple
实例分配,也没有字段分配。因此,这不是用于编译 final
字段分配的指令的问题,而是编译器级别的障碍,它可以防止在循环之外缓存 flag
字段。
但是由于完全消除了分配,因此 final
字段分配所隐含的障碍也可以消失。在这里我们看到了一个错过的优化机会。事实上,这种错过的优化在较新的 JVM 版本中得到了修复。如果您 运行 在 JDK 11 上使用相同的示例,则无论 final
修饰符如何,这两种情况都会出现无限循环。
如果 Simple class 字段 a 被 final 关键字。
如果a是一个final字段,这个程序会正常退出; 如果是普通字段,这个程序会一直保持运行。
这种情况只出现在C2编译器中。
我认为这种情况与flag字段在多线程中的可见性有关environment.However,我尝试通过观察汇编代码hsdis ,发现有无final关键字的区别。
我没发现什么不同。
实际上,我知道存储 "final" 字段不会在 x86 平台上发出任何汇编指令。可是为什么会出现这种情况呢?有没有一些我不知道的特殊操作?
感谢阅读。
class MultiProcessorTask {
private boolean flag= true;
public void runMethod() {
while (flag) {
new Simple(1);
}
}
public void stopMethod() {
System.out.println("change 'flag' field ...");
flag= false;
}
}
class ThreadA extends Thread {
private MultiProcessorTask task;
ThreadA(MultiProcessorTask task) {this.task = task;}
@Override
public void run() {
task.runMethod();
}
}
class Simple {
private int a; // modify "a" as "final"
Simple(int a) {this.a = a;}
}
public class TestRun {
public static void main(String[] args) {
MultiProcessorTask task = new MultiProcessorTask();
ThreadA a = new ThreadA(task);
a.start();
task.stopMethod();
System.out.println("it's over");
}
}
反汇编代码输出:
final
案例中的runMethod
:
the final case
non-final
案例中的runMethod
:
the non-final case
你反汇编错了。我的意思是,两个屏幕截图上都有一个独立编译的 runMethod
,但是,它在现实中从未执行过。相反,执行从解释器跳转到 OSR 存根。您需要查找标有 %
符号(表示堆栈替换)的编译。
Compiled method (c2) 646 662 % MultiProcessorTask::runMethod @ 0 (20 bytes)
^
OSR
非最终案例和最终案例的编译代码存在差异。我只留下了相关部分:
非最终版
0x000000000309ae31: test %eax,-0x5aae37(%rip) ; safepoint poll
0x000000000309ae37: jmp 0x000000000309ae31 ; loop
决赛
0x0000000002c3a3a0: test %eax,-0x265a3a6(%rip) ; safepoint poll
0x0000000002c3a3a6: movzbl 0xc(%rbx),%r11d ; load 'flag' field
0x0000000002c3a3ab: test %r11d,%r11d
0x0000000002c3a3ae: jne 0x0000000002c3a3a0 ; loop if flag == true
的确,第一种情况编译成无限循环,而第二种情况保留字段检查。
你看,在这两种情况下根本没有 Simple
实例分配,也没有字段分配。因此,这不是用于编译 final
字段分配的指令的问题,而是编译器级别的障碍,它可以防止在循环之外缓存 flag
字段。
但是由于完全消除了分配,因此 final
字段分配所隐含的障碍也可以消失。在这里我们看到了一个错过的优化机会。事实上,这种错过的优化在较新的 JVM 版本中得到了修复。如果您 运行 在 JDK 11 上使用相同的示例,则无论 final
修饰符如何,这两种情况都会出现无限循环。