1. 程式人生 > >《深入理解Java虛擬機器》第2版挖的坑終於在第3版中被R大填平了

《深入理解Java虛擬機器》第2版挖的坑終於在第3版中被R大填平了

這是why技術的第34篇原創文章

本週還是在家辦公的一週,上面的圖就是我在家的工位,和上週《Dubbo Cluster叢集那點你不知道的事》這篇文章裡面的第一張圖片比起來,升級了顯示器支撐臂,如果短還可以加長;用上了機械鍵盤,讓指尖享受那一點點來自紅軸的美妙反饋......

還是那句話:工欲善其事,必先利其器。在家辦公,我是認真的。

圖中顯示器下面的兩本書分別是《深入理解Java虛擬機器》的第2版和第3版。也就是本文的主角。

你的手邊有第2版嗎?

來,翻到第57頁。這裡面有個“坑”,看看你當時發現了沒,有沒有在這頁做筆記呢?

沒有也沒關係,我帶你先回顧一下這一頁的內容,再讓你看看我三年前第一次看這書的時候做的筆記。

第2版57頁講了啥?

也許你根本就沒看過《深入理解Java虛擬機器(第2版)》這本書。但是你一定見過位於本書第57頁的示例程式碼:

由於JDK 6常量池位於方法區,JDK 7以後常量池位於堆中,所以用兩個版本的jdk跑上面的程式碼就會出現神奇的事情。甚至用JDK 8來跑,也會出現你想不到的結果。且聽我慢慢道來。

先說一下intern是幹啥的。

該方法的作用是把首次遇到的字串載入到常量池中。

再看一下intern的註釋:

其中標記了☆的紅框翻譯過來就是:對於任意兩個字串 s 和 t,當且僅當 s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true。

回到最開始的程式碼中。引用第三版的描述如下:

這段程式碼在JDK 6中執行,會得到兩個false,而在JDK 7中執行,會得到一個true和一個false。

產生差異的原因是:在JDK 6中,intern()方法會把首次遇到的字串例項複製到永久代的字串常量池中儲存,返回的也是永久代裡面這個字串例項的引用,而由StringBuilder建立的字串例項在Java堆上,所以必然不是同一個引用,將返回false。

而JDK 1.7(以及部分其他虛擬機器,例如JRocki)的intern()實現就不需要再拷貝字串的例項到永久代了,既然字串常量池已經移到了Java堆中,那隻需要在常量池裡記錄一下首次出現的例項引用即可,因此intern()返回的引用和由StringBuilder建立的那個字串例項就是同一個。

對str2比較返回false是**因為“java”這個字串在執行StringBuilder.toString()之前已經出現過,**字串常量池中已經有它的引用了,不符合intern()方法要求“首次出現”的原則,而“計算機軟體”這個字串則是首次出現的,因此返回true。

挖坑不填,坑哭讀者

讀到這裡你有沒有一些不惑呢?有沒有感覺到一絲絲不對呢?

我們再看看原文:

為什麼在JDK 7裡面會返回fasle,上面紅框框起來的部分是關鍵答案:

因為“java”這個字串在執行StringBuilder.toString()之前已經出現過。

這句話就是“坑”,已經出現過?在哪出現的,你倒是告訴我啊!我當時的內心想法和下面的老大哥是一樣一樣的:

我第一次看這本書是在2016年,看這個地方的時候,我就百思不得其解,在哪就出現過了呀?

當時也不知道是在哪個寫的似是而非的部落格裡面找到“java是關鍵字,已存在常量池中”這句“騷話”。還正正經經的抄了上去,雖然是錯誤的描述,雖然字是醜了點......

你當年或者現在看的時候有這個疑惑嗎?

之後我又完整的看過幾次這本書,我清楚的記得,我再一次看到這裡的時候我就覺得**“java是關鍵字,已存在常量池中”這個描述是不對的**,所以我在後面打了一把叉,再次去找了相關資料,找到了sun.misc.version,終於解決了“在哪出現的”這個問題。

第3版註腳填坑

這個2013年(第二版出版那年)挖下的坑,在2016年10月1日,就被R大在知乎上給填上了。R大的這個回答也被作者周志明寫在了2019年底出版的《深入理解Java虛擬機器(第三版)》的註腳裡面:

裡面的RednaxelaFX就是R大,一個把虛擬機器玩到極致,憑一己之力撐起了知乎java半邊天的男人,後面我會詳細介紹一下的。

你只要瞭解到一點就行:他的回答,就是權威。

在R大的這個知乎回答中,幫周志明大大填了這個坑,我強烈建議你一定要去看看,連結如下:

https://www.zhihu.com/question/51102308/answer/124441115

R大幫忙填坑

我這裡只是結合R大的回答和個人的一點點經驗,談談自己的認知。

在2016年我讀這本書的時候,我才剛剛大學畢業,剛從新手村出來。當時的我對於這個問題是絕對沒有任何思路的,必須直接在網上查詢答案。

現在的我,略有一點經驗,再次遇到這個問題,就算沒看書中的描述、R大的回答,我肯定也會想到:在書中的示例裡面,第二個輸出false,說明呼叫main方法之前,肯定在字串常量池裡面已經有了這個“java”字串了。

怎麼驗證一下呢?

我們在main方法的第一行打上一個斷點,debug執行程式後,可以看到Memory,然後過濾出String,如下:

然後雙擊過濾出來的java.lang.String,可以看到下圖:

在這個頁面我們可以繼續過濾:

果然,在程式還沒執行第14行之前,“java”已經出現了。

從這個結果我們可以推斷出:Java標準庫在JVM啟動過程中載入的部分,可能裡面就有類裡有引用“java”字串字面量,這個字面量被初次引用的時候就會被intern,加入到字串常量池中去。

**而到底是哪個類導致了這個“java”字串被intern的呢?**R大主要就是回答了這個問題。

我擷取一下R大最終的答案,具體探索的過程去看他的回答吧,很強很硬核:

我們可以看到sum.misc.Version裡面的launcher_name欄位的值就是“java”:

而根據R大的回答,我們可以找到java.lang.System類:

根據System類的註釋我們可以知道,它是由虛擬機器自動呼叫的。而其initializeSystemClass方法會呼叫sun.misc.Version.init()方法。

到此就真相大白了。

Java標準庫在JVM啟動過程中會呼叫sun.misc.Version的init()方法。所以sun.misc.Version會進行類載入的操作,而類載入的初始化階段時,會對靜態常量欄位進行真正的賦值操作,但是由於sun.misc.Version的launcher_name欄位是final修飾的,所以引用的字串“java”在準備階段就被intern到了字串常量池裡面了。

可以在心裡在默默的複習一下類載入的過程:載入、驗證、準備、解析和初始化這五個階段哦。

另外書中給出的示例程式碼也有一定的侷限性,R大是這樣說的:

其實這事情很簡單:首先,這個行為必然是要針對某個具體的JDK/JRE實現來討論的,因為Java語言規範/JVM規範/Java SE標準庫的JavaDoc(也是Java SE平臺規範的一部分)都沒有、也不會強制指定哪個類裡一定要引用“java”這個字串常量,而且它必須是第一個使得“java”被intern的類 --- 規定這個也太無聊了。

比如這個示例我在JDK8u212-b03上跑出來,就是兩個true:

在這個版本里面,sun.misc.Version的launcher_name變成了“openjdk”:

那麼根據我們之前的猜測,把程式成下面這樣的,效果就是一樣的了:

萬變不離其宗,現在你知道為什麼這裡用openjdk返回也是false了吧。

知其然,還要知其所以然。

R大與周志明之間的“愛恨情仇”

R大是誰?

我先上一張《深入理解java虛擬機器(第二版)》背面的一張圖吧,R大給這本書寫過推薦語:

莫樞(RednaxelaFx)Oracle HotSpot VM編譯器團隊工程師。(現在他已經不在Oracle了。據網上公開資料,R大是前阿里巴巴技術專家,前Oracle JVM核心開發,前Azul核心開發,現就職於Databricks)

再看一下他的知乎主頁:https://www.zhihu.com/people/rednaxelafx/answers

你去知乎上只搜RednaxelaFX(甚至直接用搜索引擎搜尋),就能搜到很多結果,我隨便擷取一個片段。

在【有哪些頂級水平的中國程式設計師?】這個話題下,有一個回答只是@一下R大的ID,沒有多說一個字,就獲得了258個贊,評論中也滿是讚美的語言,乾貨多,就是他的特點:

他與《深入理解Java虛擬機器》的作者周志明大大,在2010年到2011年間,在iteye上已經有過多次深度交流,比如下面的吐槽:

比如下面的調侃:

玩歸玩,鬧歸鬧,周志明也直言閱讀了R大的很多文章,受益良多:

並且在書裡的致謝章節專門謝謝了R大:

說這麼多,我想要表達的觀點其實就是一個:

R大是一個寶藏啊,他樂於分享和交流,憑藉一己之力推動了國內jvm的學習和研究,如果你想要了解虛擬機器、編譯原理和程式語言方面的相關知識,他是一個你繞不過的人。他值得被更多的程式設計師知道。

如果你之前不知道,但看了我這篇文章後知道了他,我的目的就達到了。

他在知乎上認認真真碼字,用心的對待每一個回答,他是一個"碼"宗強者,恐怖如斯,但是從他的各種回答、部落格文章中,你可以感覺到謙遜、細緻、系統、耐心、專業、嚴謹.....就像一個評論說的:

在技術圈日益浮躁的今天,感覺他就是主席所說的那種:一個純粹的人,一個有道德的人,一個脫離了低階趣味的人,一個有益於人民的人。

我們做程式的,要向他學習,向他致敬。

最後再附上一個R大的資料合集連結吧,全是寶藏,待你去發掘:https://zhuanlan.zhihu.com/p/25042028

最後說一句

才疏學淺,難免會有紕漏,如果你發現了錯誤的地方,還請你留言給我指出來,我對其加以修改。

如果你覺得文章還不錯,你的轉發、分享、點贊、留言就是對我最大的鼓勵。

感謝您的閱讀,我堅持原創,十分歡迎並感謝您的關注。

以上。

歡迎關注公眾號【why技術】,堅持輸出原創。分享技術、品味生活,願你我共同進步。

預覽