成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JVM JIT編譯能改變某些反射的執(zhí)行結果

lcodecorex / 1887人閱讀

摘要:某個測試服務器試圖通過反射來修改變量的值,出現(xiàn)了時靈時不靈的現(xiàn)象。這個閾值隨時會變,只是測著玩的編譯是可以取消的,現(xiàn)在修改如下,在用反射設值后,再次執(zhí)行萬次直接取值現(xiàn)在的執(zhí)行結果又是了。結論不要修改變量,會出問題的關于編譯期優(yōu)化的更多知識

某個測試服務器試圖通過反射來修改static final變量的值,出現(xiàn)了時靈時不靈的現(xiàn)象。

開發(fā)環(huán)境無法重現(xiàn)。這是怎么回事呢?

先介紹背景知識

一般認為,static final常量會被編譯器執(zhí)行內聯(lián)優(yōu)化,即它的值會被內聯(lián)到調用位置。

這對于如下方式初始化的字面常量有效:

private static final boolean MY_VALUE = false;

但對于如下方式初始化的運行時常量無效:

private static final boolean MY_VALUE = System.getProperty("dsasdkdfskdsdfk") != null;

為什么會不一樣呢?因為第一種方式字面量(literal, 硬編碼在代碼里的值,可以是布爾值、數(shù)值、字符串等等)是編譯時就能確定的,而第二種方式的值是某個調用的返回值,直到運行的那一刻才確定。

具體的常量優(yōu)化規(guī)則可參考語言規(guī)范:http://docs.oracle.com/javase...

然后我就發(fā)現(xiàn)一個危險現(xiàn)象:引用自另一個jar的常量也會被內聯(lián)!

如果你引用一個第三方庫中的常量,然后升級了這個庫的版本,新版本改變了常量的值,那么你的程序就錯了!除非你重新編譯你的程序!

有時候這是很隱蔽的!例如你引用的是Tomcat的一個常量,然后你直接把程序放在新版本的Tomcat中運行!

然后解決當前的問題

服務器上的問題是:用反射強行修改static final變量的值,用反射能取得修改后的值,然而Java調用直接取得的值卻仍是舊值。

可用如下Test.java MyEnv.java兩個文件來重現(xiàn),但是在開發(fā)環(huán)境并沒有重現(xiàn)出問題:

Test.java

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class Test {
  public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    Field myField = MyEnv.class.getDeclaredField("MY_VALUE");
    myField.setAccessible(true);
    
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(myField, myField.getModifiers() & ~Modifier.FINAL);
    
    myField.set(null, true);

    System.out.println("Get via reflection: " + myField.get(null)); // true on the server
    System.out.println("Get directly:" + MyEnv.getValue()); // false on the server
  }
}

MyEnv.java

public class MyEnv {
 private static final boolean MY_VALUE = System.getProperty("dsasdkdfskdsdfk") != null;
 
 public static boolean getValue() {
 return MY_VALUE;
 }
}

按照語言規(guī)范里的編譯器常量優(yōu)化規(guī)則,這個常量不會被內聯(lián),所以開發(fā)環(huán)境的執(zhí)行結果(兩個都是true)似乎是對的?

但是JVM有運行時優(yōu)化——當代碼頻繁執(zhí)行時,會觸發(fā)JIT編譯!

我們修改Test.java如下,執(zhí)行了10萬次直接取值:

Test.java

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class Test {
  public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    for (int i = 0; i < 100000; i++) {
      MyEnv.getValue();
    }
    Field myField = MyEnv.class.getDeclaredField("MY_VALUE");
    myField.setAccessible(true);
 
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(myField, myField.getModifiers() & ~Modifier.FINAL);
 
    myField.set(null, true);

    System.out.println("Get via reflection: " + myField.get(null)); // true on the server
    System.out.println("Get directly:" + MyEnv.getValue()); // false on the server
  }
}

現(xiàn)在的執(zhí)行結果是true, false,重現(xiàn)了服務器的問題。原因是JVM在運行時通過JIT編譯再次內聯(lián)了常量。

在我的電腦上,觸發(fā)這個JIT編譯的閾值是15239,遠小于10萬。(這個閾值隨時會變,只是測著玩的)

JIT編譯是可以取消的,現(xiàn)在修改Test.java如下,在用反射設值后,再次執(zhí)行10萬次直接取值:

public class Test {
  public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    for (int i = 0; i < 100000; i++) {
      MyEnv.getValue();
    }
    Field myField = MyEnv.class.getDeclaredField("MY_VALUE");
    myField.setAccessible(true);
 
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(myField, myField.getModifiers() & ~Modifier.FINAL);
 
    myField.set(null, true);
    for (int i = 0; i < 100000; i++) {
      MyEnv.getValue();
    }
   System.out.println("Get via reflection: " + myField.get(null)); // true on the server
   System.out.println("Get directly:" + MyEnv.getValue()); // false on the server
  }
}

現(xiàn)在的執(zhí)行結果又是true, true了。
與其說是取消了JIT,不如說是觸發(fā)了新一次JIT!可以用代碼驗證這一推測,這個就留作思考題了:)
(注意,要想觸發(fā)新的JIT,需要更大量的執(zhí)行次數(shù)。)

結論:不要修改final變量,會出問題的!

關于編譯期優(yōu)化的更多知識 https://briangordon.github.io...

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/67623.html

相關文章

  • JAVA運行時簡述(HotSpot)

    摘要:拆解虛擬機的基本步聚如下首先,要等待到自身成為唯一一個正在運行的非守護線程時,在整個等待過程中,虛擬機仍舊是可工作的。將相應的事件發(fā)送給,禁用,并終止信號線程。 本文簡單介紹HotSpot虛擬機運行時子系統(tǒng),內容來自不同的版本,因此可能會與最新版本之間(當前為JDK12)存在一些誤差。 1.命令行參數(shù)處理HotSpot虛擬機中有大量的可影響性能的命令行屬性,可根據(jù)他們的消費者進行簡...

    hosition 評論0 收藏0
  • Class對象和Java反射機制

    摘要:四后記理解好對象不僅能讓我們更好的認識一切皆對象這個觀點,對之后學習泛型,類型擦除都是很有幫助的,而對于反射機制我們只需在適當?shù)膱龊侠盟纯伞? 一 前言 很多書上都說,在java的世界里,一切皆對象。其實從某種意義上說,在java中有兩種對象:實例對象和Class對象。實例對象就是我們平常定義的一個類的實例: /** * Created by aristark on 3/28/16...

    Rainie 評論0 收藏0
  • 吃透這套Java面試題,拿offer成功率再翻一番

    摘要:語言通過字節(jié)碼的方式,在一定程度上解決了傳統(tǒng)解釋型語言執(zhí)行效率低的問題,同時又保留了解釋型語言可移植的特點。有針對不同系統(tǒng)的特定實現(xiàn),,,目的是使用相同的字節(jié)碼,它們都會給出相同的結果。 showImg(https://segmentfault.com/img/bVbsjCK?w=800&h=450); 一、面向對象和面向過程的區(qū)別 面向過程優(yōu)點: 性能比面向對象高,因為類調用時需要實...

    elva 評論0 收藏0
  • Java編程中那些再熟悉不過知識點(持續(xù)更新)

    摘要:語言通過字節(jié)碼的方式,在一定程度上解決了傳統(tǒng)解釋型語言執(zhí)行效率低的問題,同時又保留了解釋型語言可移植的特點。有針對不同系統(tǒng)的特定實現(xiàn),,,目的是使用相同的字節(jié)碼,它們都會給出相同的結果。項目主要基于捐贈的源代碼。 本文來自于我的慕課網(wǎng)手記:Java編程中那些再熟悉不過的知識點,轉載請保留鏈接 ;) 1. 面向對象和面向過程的區(qū)別 面向過程 優(yōu)點: 性能比面向對象高。因為類調用時需要實例...

    taowen 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<