在開始本文的正文之前,我們下面來看看下面這段代碼:
Java中Integer類的IntegerCache的作用
包名:java.lang
文件名:Integer.java
方法名:IntegerCache
方法的代碼如下:
private static class IntegerCache { static final int high; static final Integer cache[]; static { final int low = -128; // high value may be configured by property int h = 127; if (integerCacheHighPropValue != null) { // Use Long.decode here to avoid invoking methods that // require Integer's autoboxing cache to be initialized int i = Long.decode(integerCacheHighPropValue).intValue(); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - -low); } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache() {} }我們在代碼中看到,low為-128,high為127,這樣的話,在Java編程中,如果要使用-128――127這個區間的對象的話,是直接使用這個Cache中的對象的。
上面是段簡單的介紹,幫助大家理解下IntegerCache,下面開始本文的正文:
引言
5 年前,我在Hungarian 上發表了一篇關於JDK 中如何改變IntegerCache 的文章。這種做法其實是深入進Java 運行時,在實際並沒有使用的場景。當你開發這些研究代碼時,你才能更好的理解反射是如何工作的,以及Integer 類是如何實現的。
Integer 類有一個私有的嵌套內,名為IntegerCache ,包含了值從-127 到128 的Integer 對象。
當代碼需要從int 類型封箱成Integer 對象,而且值在這個範圍內時,那麼Java 運行時會使用這個緩存,而不是創建一個新的Integer 對象。這主要是處於性能優化的考慮,我們必須牢記在心的是很多int 值在程序中很多時候都處於這個範圍內(例如數組的下標索引)。
這樣做的副作用是,很多時候,使用等號操作符來比較兩個Integer 對象時,只要值在範圍內都是有效的。這在單元測試中很典型。而在運行模式下,當數值大於128 時,代碼執行會失敗。
使用反射來訪問IntegerCache 類時會導致一些奇怪的副作用,注意這會影響到整個的JVM。如果一個Servlet 重新定義了小的Integer 緩存值,那麼所有運行在同一個Tomcat 下的其他Servlet 也遭遇同樣問題。
在Lukas Eder 和Sitepoint 上面還有其他一些文章描述此問題。
現在我已經開始在玩弄Java 9 的早期發布版本,在我腦海裡我一直要做的就是對新的Java 版本進行各種實驗。在開始之前,讓我們先看看在Java 8 中的做法。
在Lukas 的文章中,我將他的示例代碼貼在此處:
import java.lang.reflect.Field;import java.util.Random;public class Entropy { public static void main(String[] args) throws Exception { // Extract the IntegerCache through reflection Class << ? > clazz = Class.forName( "java.lang.Integer$IntegerCache"); Field field = clazz.getDeclaredField("cache"); field.setAccessible(true); Integer[] cache = (Integer[]) field.get(clazz); // Rewrite the Integer cache for (int i = 0; i < cache.length; i++) { cache[i] = new Integer( new Random().nextInt(cache.length)); } // Prove randomness for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } }}此代碼通過反射方式訪問IntegerCache,然後使用隨機值對緩存進行填充(淘氣!)。
我們嘗試在Java 9 中執行相同的代碼,別指望有什麼樂趣。當有人嘗試違反它時會發現Java 9 的限制更加嚴格。
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field static final java.lang.Integer[] java.lang.Integer$IntegerCache.cache accessible: module java.base does not "opens java.lang" to unnamed module @1bc6a36e
程序拋出了異常,這個異常在Java 8 中是不會發生的。相當於說對像是不可範文的,因為java.base 模塊的原因,這是JDK 的組成部分,在每個Java 程序啟動時被自動的導入,不允許打開未命名的模塊。這個異常是在當我們嘗試設置字段可訪問屬性時拋出的。
我們在Java 8 可輕鬆訪問的對象,現在在Java 9 中不能訪問了,因為新的模塊系統對此進行了保護。代碼只能訪問字段、方法和其他用反射能訪問的信息,只有當類在相同的模塊中,或者模塊打開了包用於反射方式訪問。這個可以通過module-info.java 模塊定義文件來實現:
module myModule { exports com.javax0.module.demo; opens com.javax0.module.demo;}這個模塊java.base 不用不用自行打開用於反射訪問,特別是未命名模塊更不需要。如果我們創建了一個模塊並進行命名,那麼錯誤信息將包含模塊的名稱。
我們能否在程序裡打開模塊呢? java.lang.reflect.Module模塊有一個addOpens 的方法可以做到。
可行嗎?
對開發者來說壞消息是:不可行。它只能在另外一個模塊中打開一個模塊中的包,並且包已經在該模塊中通過調用這個方法打開過。這種方法只能讓模塊傳遞給另外的模塊權利,前提是另外的模塊已經以某種方式打開過相同的包,而不能打開沒有打開過的包(譯者註:很難理解,不是嗎?)。
但與此同時好消息是:Java 9 不像Java 8 那麼容易被破解。最少這個漏洞被關閉了。看起來Java 開始往專業級發展,而不僅僅是個玩具(譯者註:誰說Java 是個玩具了?)。不久的將來你可以非常嚴肅的將RPG 和COBOL 語言的項目遷移到Java 上了。 (很抱歉,我開玩笑的)
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對武林網的支持。
本文翻譯自:https://dzone.com/articles/hacking-the-integercache-in-java-9