《深入理解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技術】,堅持輸出原創。分享技術、品味生活,願你我共同進步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章