從JVM堆記憶體分析驗證深淺拷貝
- 2019 年 12 月 20 日
- 筆記
在重寫性能測試框架的過程中,遇到一個問題,每個執行緒都要收集一些統計數據,但是在我之前的框架Demo裡面有一種情況:單一的threadbase執行緒任務,多執行緒並發。我是直接使用的這個對象,如果每個執行緒threadbase包含統計資訊的話,多執行緒執行一個任務肯定會出現不安全的情況,如果加鎖又會導致「多執行緒」失去意義。故而採用了創建任務時將對象按照執行緒數拷貝一份,保證每個執行緒執行的threadbase對象都是獨立綁定的。
順便學習了一番Java深淺拷貝對象的知識,發現又學到了一點,關於深淺拷貝的原理和演示從程式碼級別來講已經很多了,加之之前學到了Java堆記憶體相關的一些技巧,我決定使用記憶體分析來演示一下深淺拷貝。
關於概念性的問題,大家可以自行搜索,這裡不便多說,直接開搞。
拷貝對象a分三種方式:1、直接創建另外一個對象a1=a;2、淺拷貝一個對象a2;3、深拷貝一個對象a3。
理論來家,1隻會在堆記憶體有一個A對象實例,2會有兩個A對象實例,3會有三個A對象實例。如果該對象還包含了對象B的話,那麼1隻會產生一個B實例,2也只會產生一個B實例,3會產生兩個對象B實例。
到底是不是這樣呢,實踐是檢驗整理的唯一標準。
下面是我的測試程式碼:
package com.fun; import com.fun.frame.SourceCode; import java.io.Serializable; public class AR extends SourceCode { public static void main(String[] args) { waitForKey("1"); HeapDumper.dumpHeap("1",true); Tttt clone = tttt.clone(); waitForKey("2"); HeapDumper.dumpHeap("2",true); Tttt tttt1 = deepClone(tttt); HeapDumper.dumpHeap("3",true); waitForKey("3"); } static class Tttt implements Cloneable, Serializable { public Sss sss = new Sss(); private static final long serialVersionUID = 4989553780571753462L; public Tttt clone() { try { return (Tttt) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } } static class Sss implements Cloneable, Serializable { private static final long serialVersionUID = -5487147719650620894L; public Sss clone() { try { return (Sss) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } } }
我用了三次暫停,分別標記了三個節點,然後通過使用jconsole
中的mbean
工具類獲取堆記憶體的轉儲文件,其他方法請參考往期文章如何獲取JVM堆轉儲文件,大家也可以在程式碼中直接使用 dump工具類的方法,參考往期文章獲取JVM轉儲文件的Java工具類。
下面是jconsole
的操作介面:

p0是生成轉儲文件的文件名,不能跟已存在的文件重複,不然會報錯。dumpheap的默認路徑是當前項目的工作路徑。
使用jhat -port 8001 1
這樣的命令即可啟動一個服務,然後訪問http://localhost:8001/
看到堆記憶體使用情況。
查看訪問是先找到想要查看的類名,點擊之後選擇instance
實例選項即可看到實例個數。
下面是Ttt對象實例的情況,按照1、2、3排列。



果然1、2、3個Ttt對象實例,符合預期。
下面是Sss對象實例的情況:



果然1、1、2個Sss對象實例,符合預期。
完美收工,哈哈!
- 鄭重聲明:文章禁止第三方(騰訊雲除外)轉載、發表,事情原委測試窩,首頁抄我七篇原創還拉黑,你們的良心不會痛嗎?