国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

lcodecorex / 1880人閱讀

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

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

開發環境無法重現。這是怎么回事呢?

先介紹背景知識

一般認為,static final常量會被編譯器執行內聯優化,即它的值會被內聯到調用位置。

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

private static final boolean MY_VALUE = false;

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

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

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

具體的常量優化規則可參考語言規范:http://docs.oracle.com/javase...

然后我就發現一個危險現象:引用自另一個jar的常量也會被內聯!

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

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

然后解決當前的問題

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

可用如下Test.java MyEnv.java兩個文件來重現,但是在開發環境并沒有重現出問題:

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;
 }
}

按照語言規范里的編譯器常量優化規則,這個常量不會被內聯,所以開發環境的執行結果(兩個都是true)似乎是對的?

但是JVM有運行時優化——當代碼頻繁執行時,會觸發JIT編譯!

我們修改Test.java如下,執行了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
  }
}

現在的執行結果是true, false,重現了服務器的問題。原因是JVM在運行時通過JIT編譯再次內聯了常量。

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

JIT編譯是可以取消的,現在修改Test.java如下,在用反射設值后,再次執行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
  }
}

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

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

關于編譯期優化的更多知識 https://briangordon.github.io...

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

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

相關文章

  • JAVA運行時簡述(HotSpot)

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

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

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

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

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

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

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

    taowen 評論0 收藏0

發表評論

0條評論

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