從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對象實例,符合預期。

完美收工,哈哈!