Java内存模型与指令重排(2)

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会产生如图变化.

Java内存模型与指令重排

两者区别在于当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的值.

就不会出现无限循环了.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/7d9ac423d681217e025e58f6ccda59f9.html