for (int i = -130; i < 130; i++) { System.out.println(i + ">" + (Integer.valueOf(i) == Integer.valueOf(i))); }以前不知道所以也不會去用 Integer.valueOf(int) 來產生 Integer 物件,只知道用 new Integer(int),那這兩個 method 有什麼差別嗎?先看看上面程式的執行結果。
-130>false -129>false -128>true -127>true ... 126>true 127>true 128>false 129>false奇怪了,在 -128 到 127 之間的 Integer 相等(不正常),在這範圍外的 Integer 不相等(這才正常),見鬼了!
Google 之後,得到的結論,java.lang.Integer 用了 cache。
先複習一下,用兩個等號來比較物件時,除非是同一個物件,否則就算是同樣的值,也是不相等的。
Integer a = new Integer(100); Integer b = new Integer(100); System.out.println("a == b > " + (a == b)); // a == b > false Integer c = Integer.valueOf(100); Integer d = Integer.valueOf(100); System.out.println("c == d > " + (c == d)); // c == d > truea 與 b 不相等可以理解,為什麼 c 和 d 卻相等呢?
來看看 Source code 吧(好像是打從學 java 第一次看 JDK 的 原始碼)。
public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); }這是 java.lang.Integer 的 valueOf(int),謎底揭曉,原來為了效能與記憶體空間,JVM 將 -128 到 127 之間的 Integer 在透過 valueOf(int) 產生時,是使用 cache 的 Integer 物件,但是若是用 new Integer(int) 則不會使用 cache 物件。
由於是用 cache 物件,所以在 -128 到 127 之間的 valueOf(int) 都會得到相同的物件,也就因此用兩個等號時會相等。
那這個 cache 範圍可以修改嗎?可以的,只要加上 -XX:AutoBoxCacheMax=<size> 這樣的 VM option 就好了。
再執行一次最上面的測試程式,可以發現 128 和 129 從 false 變成 true 了,不過不幸的是,目前只能改正數的值,不能改負數的值。
-130>false -129>false -128>true -127>true ... 126>true 127>true 128>true 129>true
再往下挖一點,將 -XX:AutoBoxCacheMax=<size> 加到 Tomcat 的 catalina.bat 裡。
... rem --------------------------------------------------------------------------- set JAVA_OPTS=%JAVA_OPTS% -XX:AutoBoxCacheMax=150 rem Guess CATALINA_HOME if not defined ...爆了,Tomcat 起不來,錯誤訊息是 Tomcat 不認識 AutoBoxCacheMax,這這這...又是什麼情況啊!
Unrecognized VM option 'AutoBoxCacheMax=150' Could not create the Java virtual machine.這年頭沒有 Google 怎麼寫程式?
以下已經超出我可裡理解的範圍了,起因在於 Java Hotspot,一般預設是 Java Hotspot Client VM,可以從以下兩個指令看出來。
C:\Users\Neil>java -version java version "1.6.0_45" Java(TM) SE Runtime Environment (build 1.6.0_45-b06) Java HotSpot(TM) Client VM (build 20.45-b01, mixed mode, sharing) C:\Users\Neil>java -client -version java version "1.6.0_45" Java(TM) SE Runtime Environment (build 1.6.0_45-b06) Java HotSpot(TM) Client VM (build 20.45-b01, mixed mode, sharing)Google 說只要改用 Server VM 就可以讓 Tomcat 使用 -XX:AutoBoxCacheMax。
當然 Google 會順便告訴我們怎麼改用 Server VM,開啟 <JDK>
-client KNOWN -server KNOWN -hotspot ALIASED_TO -client -classic WARN -native ERROR -green ERROR改變 -client 與 -server 的順序後存檔。
-server KNOWN -client KNOWN -hotspot ALIASED_TO -client -classic WARN -native ERROR -green ERROR再執行一次 java -version 檢查是否生效。
C:\Users\Neil>java -version java version "1.6.0_45" Java(TM) SE Runtime Environment (build 1.6.0_45-b06) Java HotSpot(TM) Server VM (build 20.45-b01, mixed mode)最後再重起 Tomcat 就大功告成了。
-----------------------------
最後說一下為什麼會遇到這個問題,既然我都是用 new Integer(int) 的話,那是誰用了 valueOf(int)?是 Hibernate!
我的 Primary Key 的型別剛好是 Integer,請 Hibernate 從資料庫取回物件時,Hibernate 用了 valueOf(int)。
然後方便的程式壞習慣用太久就會出亂子的我,在某一天不小心用了兩個等號去比較兩個物件 Primary Key 是否相等,我以為 Java 會替我 Unboxing,但在兩個等號的運算時,得兩邊分別是 primitive type 和 Object,才會使用 unboxing,如果兩邊都是 Object,就不會用 unboxing,而是看 reference。
再加上我的本機測試資料少少的,所以在 -128 到 127 的庇佑下,本機測試沒問題,在正式機上就沒這麼好講話了。
---
---
---
沒有留言:
張貼留言