GC復制存活對象,它內(nèi)存地址變了么?
前言
前些天與一位朋友技術(shù)交流,朋友在招人面試時想到一個問題,JVM垃圾回收時,會復制存活的對象到不同的區(qū)域。比如從新生代復制到老年代,在此過程中,被復制的對象的地址是否變了呢?對他提出的這個問題很感興趣,深入研究了一下,便有了這篇文章。
更新引用是JVM的職責
任何一款JVM的設(shè)計,采用任何一種GC算法進行對象的移動操作時,如何更新對象引用都是JVM的基本職責。也就是說,當移動對象時,必然會涉及到對象引用的變更,只不過這部分操作JVM已經(jīng)幫我們做了。
作為開發(fā)者來說,可以將引用理解為存儲對象的抽象句柄,而不必擔心JVM是如何管理對象存儲的。但如果做技術(shù)研究,好奇底層的實現(xiàn),倒是值得深入研究一下。
當對象的實際地址發(fā)生變化時,簡單來說,JVM會將指向該地址的一個或多個變量所使用的引用地址進行更新,從而達到在“不知不覺”中移動了對象的效果。
JVM規(guī)范中只規(guī)定了引用類型是指向?qū)ο蟮囊?,并沒有限制具體的實現(xiàn)。因此,不同虛擬機的實現(xiàn)方式可能不同。通常有兩種實現(xiàn)形式:句柄訪問和直接指針訪問。
句柄訪問
先來看一張圖,句柄訪問的形式是堆空間維護一個句柄池,對象引用中保存的是對象的句柄位置。在堆中的句柄包含對象的實例數(shù)據(jù)和類型數(shù)據(jù)的真實地址。

這種形式的實現(xiàn)好處很明顯,引用中保存的對象句柄地址相對穩(wěn)定(不變),當GC操作移動對象時只用維護句柄池中存儲的信息即可,特別是多個變量都引用同一個句柄池中的句柄時,可以減少更新變量存儲的引用,同時確保變量的地址不變。缺點就是多了一次中轉(zhuǎn),訪問效率會有影響。
直接指針訪問
直接指針訪問省去了中間的句柄池,對象引用中保持的直接是對象地址。

這種方式很明顯節(jié)省了一次指針定位的開銷,訪問速度快。但是當GC發(fā)生對象移動時,變量中保持的引用地址也需要維護,如果多個變量指向一個地址,需要更新多次。Hot Spot虛擬機便是基于這種方式實現(xiàn)的。
如何查看引用地址?
上面聊了對象引用的實現(xiàn)形式,那么在日常開發(fā)中是否可以通過打印等形式來查看對象的地址嗎?有這樣一個說法,通過對象默認的toString方法打印出來的信息中包含對象的引用地址。下面我們通過一個實例來看看:
Bike bike = new Bike();
System.out.println(bike);
當我們執(zhí)行上述程序時,控制臺會打印出如下信息:
com.secbro2.others.Bike@4dc63996
@后面的字符串是什么?是對象的地址嗎?這種地址的說法其實在坊間流傳了很久。我們先來看Object的toString源碼:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
通過源碼我們會發(fā)現(xiàn),其實@符合后面并不是對象的地址,而只是hashcode的十六進制展現(xiàn)形式而已。
那么,如何打印對象的內(nèi)存地址呢?我們需要依賴一個JOL(Java Object Layout)類庫,在項目中添加如下Maven依賴:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
然后在程序中通過如下方法使用:
String answer = "42";
System.out.println("The memory address is " + VM.current().addressOf(answer));
會發(fā)現(xiàn)打印的內(nèi)容如下:
The memory address is 31856221536
上面的便是真實的內(nèi)存地址,雖然能夠獲取并打印出內(nèi)存地址,但由于不同環(huán)境下的JVM采用了不同的指針壓縮操作。因此,我們不要基于此地址來做一些本機內(nèi)存相關(guān)的操作。但上面的打印,明確的證明了toString方法打印出來的信息并不包括對象的內(nèi)存地址。
鑒于此,基于toString方法打印出來hashCode值只能保證兩個對象的hashcode一樣,卻無法保證兩個引用地址指向同一對象。
小結(jié)
通過與朋友的一個小交流,深挖一下,竟然發(fā)現(xiàn)不少底層的知識點,交流和探索的作用可見一斑??偨Y(jié)來說就是:JVM在GC操作時會自動維護引用地址,變量對應(yīng)的應(yīng)用地址是否變化要看采用的是基于句柄池方式還是直接指針指向的方式。同時,當我們通過toString方法打印時,輸出的內(nèi)容并不包含對象地址,只不過是對象hashcode的十六進制而已。
往期推薦
如果你覺得這篇文章不錯,那么,下篇通常會更好。添加微信好友,可備注“加群”(微信號:zhuan2quan)。
和花一輩子都看不清的人,
注定是截然不同的搬磚生涯。



