摘要:某個(gè)測(cè)試服務(wù)器試圖通過(guò)反射來(lái)修改變量的值,出現(xiàn)了時(shí)靈時(shí)不靈的現(xiàn)象。這個(gè)閾值隨時(shí)會(huì)變,只是測(cè)著玩的編譯是可以取消的,現(xiàn)在修改如下,在用反射設(shè)值后,再次執(zhí)行萬(wàn)次直接取值現(xiàn)在的執(zhí)行結(jié)果又是了。結(jié)論不要修改變量,會(huì)出問(wèn)題的關(guān)于編譯期優(yōu)化的更多知識(shí)
某個(gè)測(cè)試服務(wù)器試圖通過(guò)反射來(lái)修改static final變量的值,出現(xiàn)了時(shí)靈時(shí)不靈的現(xiàn)象。
開發(fā)環(huán)境無(wú)法重現(xiàn)。這是怎么回事呢?
先介紹背景知識(shí)一般認(rèn)為,static final常量會(huì)被編譯器執(zhí)行內(nèi)聯(lián)優(yōu)化,即它的值會(huì)被內(nèi)聯(lián)到調(diào)用位置。
這對(duì)于如下方式初始化的字面常量有效:
private static final boolean MY_VALUE = false;
但對(duì)于如下方式初始化的運(yùn)行時(shí)常量無(wú)效:
private static final boolean MY_VALUE = System.getProperty("dsasdkdfskdsdfk") != null;
為什么會(huì)不一樣呢?因?yàn)榈谝环N方式字面量(literal, 硬編碼在代碼里的值,可以是布爾值、數(shù)值、字符串等等)是編譯時(shí)就能確定的,而第二種方式的值是某個(gè)調(diào)用的返回值,直到運(yùn)行的那一刻才確定。
具體的常量?jī)?yōu)化規(guī)則可參考語(yǔ)言規(guī)范:http://docs.oracle.com/javase...
然后我就發(fā)現(xiàn)一個(gè)危險(xiǎn)現(xiàn)象:引用自另一個(gè)jar的常量也會(huì)被內(nèi)聯(lián)!
如果你引用一個(gè)第三方庫(kù)中的常量,然后升級(jí)了這個(gè)庫(kù)的版本,新版本改變了常量的值,那么你的程序就錯(cuò)了!除非你重新編譯你的程序!
有時(shí)候這是很隱蔽的!例如你引用的是Tomcat的一個(gè)常量,然后你直接把程序放在新版本的Tomcat中運(yùn)行!
然后解決當(dāng)前的問(wèn)題服務(wù)器上的問(wèn)題是:用反射強(qiáng)行修改static final變量的值,用反射能取得修改后的值,然而Java調(diào)用直接取得的值卻仍是舊值。
可用如下Test.java MyEnv.java兩個(gè)文件來(lái)重現(xiàn),但是在開發(fā)環(huán)境并沒(méi)有重現(xiàn)出問(wè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; } }
按照語(yǔ)言規(guī)范里的編譯器常量?jī)?yōu)化規(guī)則,這個(gè)常量不會(huì)被內(nèi)聯(lián),所以開發(fā)環(huán)境的執(zhí)行結(jié)果(兩個(gè)都是true)似乎是對(duì)的?
但是JVM有運(yùn)行時(shí)優(yōu)化——當(dāng)代碼頻繁執(zhí)行時(shí),會(huì)觸發(fā)JIT編譯!
我們修改Test.java如下,執(zhí)行了10萬(wà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 { 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í)行結(jié)果是true, false,重現(xiàn)了服務(wù)器的問(wèn)題。原因是JVM在運(yùn)行時(shí)通過(guò)JIT編譯再次內(nèi)聯(lián)了常量。
在我的電腦上,觸發(fā)這個(gè)JIT編譯的閾值是15239,遠(yuǎn)小于10萬(wàn)。(這個(gè)閾值隨時(shí)會(huì)變,只是測(cè)著玩的)
JIT編譯是可以取消的,現(xiàn)在修改Test.java如下,在用反射設(shè)值后,再次執(zhí)行10萬(wàn)次直接取值:
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í)行結(jié)果又是true, true了。
與其說(shuō)是取消了JIT,不如說(shuō)是觸發(fā)了新一次JIT!可以用代碼驗(yàn)證這一推測(cè),這個(gè)就留作思考題了:)
(注意,要想觸發(fā)新的JIT,需要更大量的執(zhí)行次數(shù)。)
結(jié)論:不要修改final變量,會(huì)出問(wèn)題的!
關(guān)于編譯期優(yōu)化的更多知識(shí) https://briangordon.github.io...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/67623.html
摘要:拆解虛擬機(jī)的基本步聚如下首先,要等待到自身成為唯一一個(gè)正在運(yùn)行的非守護(hù)線程時(shí),在整個(gè)等待過(guò)程中,虛擬機(jī)仍舊是可工作的。將相應(yīng)的事件發(fā)送給,禁用,并終止信號(hào)線程。 本文簡(jiǎn)單介紹HotSpot虛擬機(jī)運(yùn)行時(shí)子系統(tǒng),內(nèi)容來(lái)自不同的版本,因此可能會(huì)與最新版本之間(當(dāng)前為JDK12)存在一些誤差。 1.命令行參數(shù)處理HotSpot虛擬機(jī)中有大量的可影響性能的命令行屬性,可根據(jù)他們的消費(fèi)者進(jìn)行簡(jiǎn)...
摘要:四后記理解好對(duì)象不僅能讓我們更好的認(rèn)識(shí)一切皆對(duì)象這個(gè)觀點(diǎn),對(duì)之后學(xué)習(xí)泛型,類型擦除都是很有幫助的,而對(duì)于反射機(jī)制我們只需在適當(dāng)?shù)膱?chǎng)合利用它即可。 一 前言 很多書上都說(shuō),在java的世界里,一切皆對(duì)象。其實(shí)從某種意義上說(shuō),在java中有兩種對(duì)象:實(shí)例對(duì)象和Class對(duì)象。實(shí)例對(duì)象就是我們平常定義的一個(gè)類的實(shí)例: /** * Created by aristark on 3/28/16...
摘要:語(yǔ)言通過(guò)字節(jié)碼的方式,在一定程度上解決了傳統(tǒng)解釋型語(yǔ)言執(zhí)行效率低的問(wèn)題,同時(shí)又保留了解釋型語(yǔ)言可移植的特點(diǎn)。有針對(duì)不同系統(tǒng)的特定實(shí)現(xiàn),,,目的是使用相同的字節(jié)碼,它們都會(huì)給出相同的結(jié)果。 showImg(https://segmentfault.com/img/bVbsjCK?w=800&h=450); 一、面向?qū)ο蠛兔嫦蜻^(guò)程的區(qū)別 面向過(guò)程優(yōu)點(diǎn): 性能比面向?qū)ο蟾?,因?yàn)轭愓{(diào)用時(shí)需要實(shí)...
摘要:語(yǔ)言通過(guò)字節(jié)碼的方式,在一定程度上解決了傳統(tǒng)解釋型語(yǔ)言執(zhí)行效率低的問(wèn)題,同時(shí)又保留了解釋型語(yǔ)言可移植的特點(diǎn)。有針對(duì)不同系統(tǒng)的特定實(shí)現(xiàn),,,目的是使用相同的字節(jié)碼,它們都會(huì)給出相同的結(jié)果。項(xiàng)目主要基于捐贈(zèng)的源代碼。 本文來(lái)自于我的慕課網(wǎng)手記:Java編程中那些再熟悉不過(guò)的知識(shí)點(diǎn),轉(zhuǎn)載請(qǐng)保留鏈接 ;) 1. 面向?qū)ο蠛兔嫦蜻^(guò)程的區(qū)別 面向過(guò)程 優(yōu)點(diǎn): 性能比面向?qū)ο蟾?。因?yàn)轭愓{(diào)用時(shí)需要實(shí)例...
閱讀 540·2019-08-30 15:44
閱讀 959·2019-08-30 10:55
閱讀 2796·2019-08-29 15:16
閱讀 1174·2019-08-29 13:17
閱讀 2867·2019-08-26 13:27
閱讀 630·2019-08-26 11:53
閱讀 2186·2019-08-23 18:31
閱讀 1938·2019-08-23 18:23