深入理解Java記憶體模型
Java虛擬機器的執行時記憶體按照型別可分為5部分:Java方法區、Java棧、Native方法區、Java堆和程式計數器。 其中棧和程式計數器不能跨執行緒訪問。
程式計數器:是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器。每條執行緒都需要有一個獨立的程式計數器,各條執行緒之間計數器互不影響,獨立儲存,而且永遠不會發生Out of Memory問題,其它四種記憶體區域都可能出現OOM現象。
Java虛擬機器棧(Java Virtual Machine Stacks)也是執行緒私有的,它的生命週期與執行緒相同, 跟C語言類似不使用new關鍵字的變數存放在棧裡。在棧裡可能出現StackOverflowException或者OOM異常。
本地方法棧: 就是Native層用C語言編寫的棧(沒呼叫new),其它跟Java棧類似。
Java堆:所有的java隊形例項、陣列都在堆上new出來,是垃圾收集器(Garbage Collection)管理的主要區域。Java堆可以處在物理不連續的記憶體空間中, 但邏輯上要是連續的。在實現時,既可以實現成固定大小的,也可以是可擴充套件的,不過當前主流的虛擬機器都是按照可擴充套件來實現的(通過-Xmx和-Xms控制)。在日常程式設計中遇到的OOM異常大都發生在Java堆。
方法區(Method Area)與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼
上面是JVM的記憶體模型, 那麼一個Java物件在記憶體裡分為幾部分呢?
答案:物件在記憶體中儲存的佈局可以分為3塊區域:物件頭(Header)、例項資料(Instance Data)和對齊填充(Padding)。
那麼JVM是如何回收記憶體的呢?
一、引用計數法: 當指向的Java物件的引用計數為0時, 在下次GC回收時會被釋放。
考慮問題: 如果出現相互引用, 即A指向B,B指向A, 那麼GC能回收嗎?
執行結果:public static class ReferenceCountingGC{ public Object instance=null; private final int ONE_MB=1024*1024; private byte[]bigSize=new byte[1*ONE_MB]; //只為了佔空間 public static void testGC(){ ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; //假設在這行發生GC,objA和objB是否能被回收? long begin = Runtime.getRuntime().totalMemory(); System.out.println("befor:" + begin); System.gc(); long after = Runtime.getRuntime().totalMemory(); System.out.println("after:" + after); System.out.println("diff:" + (after-begin)); //判斷到底釋放了多少 } }
befor:16252928
after:16318464
diff:65536
GC前後差了64K位元組, 而objA和objB都大於1M位元組, 現實沒有被釋放! 所以程式設計中切忌不要相互強引用!!!
二、可達性分析演算法
基本思路就是通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個物件不可達)時,則證明此物件是不可用的。如圖所示,物件object 5、object 6、object 7雖然互相有關聯,但是它們到GC Roots是不可達的,所以它們將會被判定為是可回收的物件。
在Java語言中,可作為GC Roots的物件包括下面幾種:虛擬機器棧(棧幀中的本地變量表)中引用的物件;方法區中類靜態屬性引用的物件;方法區中常量引用的物件;本地方法棧中JNI(即一般說的Native方法)引用的物件。
關於記憶體基礎知識就說這麼多, 下一篇準備寫如何計算Java物件的大小。