public void run() {
int i = 0;
while (!stop) {
i++;
}
System.out.println("finish loop,i=" + i);
}
public void stopIt() {
stop = true;
}
public boolean getStop() {
return stop;
}
public static void main(String[] args) throws Exception {
VisibilityTest v = new VisibilityTest();
v.start();
Thread.sleep(1000);
v.stopIt();
Thread.sleep(2000);
System.out.println("finish main");
System.out.println(v.getStop());
}
}
以32位jdk1.7.0_55为例, 我们可以通过修改JAVA_HOME/jre/lib/i386/jvm.cfg, 将jvm调整为server模式验证下.
修改内容如下图所示, 将-server调整到-client的上面.
-server KNOWN
-client KNOWN
-hotspot ALIASED_TO -client
-classic WARN
-native ERROR
-green ERROR
修改成功后, java -version会产生如图变化.
两者区别在于当jvm运行在-client模式的时候,使用的是一个代号为C1的轻量级编译器,而-server模式启动的虚拟机采用相对重量级,代号为C2的编译器. C2比C1编译器编译的相对彻底,会导致程序启动慢, 但服务起来之后, 性能更高, 同时有可能带来可见性问题.
我们将上述代码运行的汇编代码打印出来, 打印方法也简单提一下.
给主类运行时加上VM Options, -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
此时会提示Could not load hsdis-i386.dll; library not loadable; PrintAssembly is disabled
因为打印汇编需要给jdk安装一个插件, 可能需要自己编译hsdis, 不同平台不太一样,
Windows下32位jdk需要的是hsdis-i386.dll, 64位jdk需要hsdis-amd64.dll.
我们把编译好的hsdis-i386.dll放到JAVA_HOME/jre/bin/server以及JAVA_HOME/jre/bin/client目录中.
运行代码, 控制台会把代码对应的汇编指令一起打印出来. 会有很多行, 我们只需要搜索run方法对应的汇编.
搜索 'run' '()V' in 'VisibilityTest', 可以找到对应的指令.
如下代码所示, 从红字注释的部分可以看出来,
只有第一次进入循环之前, 检查了下stop的值, 不满足条件进入循环后,
再也没有检查stop, 一直在做循环i++.
public void run() {
int i = 0;
while (!stop) {
i++;
}
System.out.println("finish loop,i=" + i);
}
# {method} 'run' '()V' in 'VisibilityTest'
......
0x02d486e9: jne 0x02d48715
// 获取stop的值
0x02d486eb: movzbl 0x64(%ebp),%ecx ; implicit exception: dispatches to 0x02d48703
0x02d486ef: test %ecx,%ecx
// 进入while之前, 若stop满足条件, 则跳转到0x02d48703, 不执行while循环
0x02d486f1: jne 0x02d48703 ;*goto
; - VisibilityTest::run@12 (line 10)
// 循环体内, i++
0x02d486f3: inc %edi ; OopMap{ebp=Oop off=52}
;*goto
; - VisibilityTest::run@12 (line 10)
0x02d486f4: test %edi,0xe00000 ;*goto
; - VisibilityTest::run@12 (line 10)
; {poll}
// jmp, 无条件跳转到0x02d486f3, 一直执行i++操作, 根本不检查stop的值
// 导致死循环
0x02d486fa: jmp 0x02d486f3
0x02d486fc: mov $0x0,%ebp
0x02d48701: jmp 0x02d486eb
// 跳出循环
0x02d48703: mov $0xffffff86,%ecx
......
解决方案也很简单, 只要给stop加上volatile关键字, 再次打印汇编代码, 发现他每次都会检查stop的值.
就不会出现无限循环了.