之前看周志明的《深入理解java虛擬機》總感覺有點腦袋暈暈的感覺,最近又拿起書來看了看。感覺思路清晰了不少,於是寫了個課件,但轉念一想這些總歸是個人的理解難免會存在一定的局限性於是就把課件寫成筆記,讓它成為眾矢之的,從中想學到寫自己未曾注意到或者沒想到的東西,這本身就是個不斷進步的過程...
Java內存划分
程序計數器:當前線程所執行的字節碼的行號指示器
Java虛擬機棧:描述Java方法執行的內存模型,每個方法被執行的時候都會同時創建一個棧幀用於存儲局部變量表、操作棧、動態鏈接、方法出口等信息。
本地方法棧:為虛擬機使用的native方法服務。
Java堆:被所有線程共享的一塊內存區域,在虛擬機啟動時創建。所有的對象實例以及數組都要在堆上分配。
方法區:線程共享的內存區域,存儲已被虛擬機加載的類信息、常量、靜態變量即時編譯器編譯后的代碼數據等(這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載)。
StackOverflowError:虛擬機棧、本地方法棧 。
OutOfMemoryError:Java堆溢出、運行時常量池溢出、方法區溢出、本機直接內存溢出。
垃圾收集
GC需要完成的三件事:
那些內存需要回收?
什么時候回收?
如何回收?(垃圾收集算法,忽略)
哪些內存需要回收?
GC應該回收這樣一些對象,這些對象沒有任何引用指向即對象已死。 Java使用根搜索算法判斷對象是否存活。基本思路是:通過一系列的名為“GC Roots”的對象作為起始點,當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。(個人理解:也就是說在GCRoot中被調用的對象就是活對象) Java語言里,可作為GC Roots的對象包括下面幾種: 虛擬機棧(棧幀中的本地變量表)中的引用的對象。 方法區中的類靜態屬性引用的對象。 方法區中的常量引用的對象。 本地方法棧中Native方法的引用的對象。
什么時候回收?(回收堆區)
GC要回收一個對象至少要經歷兩次標記過程: 根搜索后發現沒有與GC Roots相連接的引用鏈,進行第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。(當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況都視為“沒有必要”) 如果這個對象被判定為有必要執行finalize()方法,那么這個對象將會被放置在一個名為F-Queue的隊列之中,並在稍后由一條由虛擬機自動建立的、低優先級的Finalizer線程去執行(但虛擬機並不承諾等待它運行完成)。finalize()方法是對象逃脫死亡命運的最后一次機會,稍后GC將對F-Queue隊列中的對象進行第二次小規模的標記。
例:請分析本類的內存存儲結構以及關注內存回收問題
public class FinalizeEscapeGC{ public static FinalizeEscapeGC SAVE_HOOK = null; // 靜態屬性引用,作為GC Roots
public void isAlive () { System.out.println("yes,i am still alive :)"); } protected void finalize() throws Throwable { super.finalize(); System.out.println("called finalize method."); FinalizeEscapeGC.SAVE_HOOK = this; // 建立引用鏈
} public static void main(String[] args) throws Throwable { SAVE_HOOK = new FinalizeEscapeGC(); // 建立引用鏈
SAVE_HOOK = null; // 剪短引用鏈
System.gc(); Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("no, i am dead :("); } // 任何一個對象的finalize()方法都只會被系統自動調用一次
SAVE_HOOK = null; System.gc(); Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("no, i am dead :("); } } }
......(此處省略實戰內容)
2012-07-25