標籤:釋放 roo 理解 minor 強引用 引用計數器 判斷 殺進程 基礎
JVM記憶體中的各個地區都會回收嗎?
首先我們知道 Java 棧和本地方法棧在方法執行完成後對應的棧幀就立刻出棧銷毀,兩者的回收率可以認為是100%;Java 堆中的對象在沒有被引用後,即使用完成後會被回收;方法區中的資料一般不會回收,只有在同時滿足:所有執行個體被回收、載入該類的類載入器被回收、Class對象無法通過任何途徑訪問(包括反射)時才會回收;而程式計數器主要是記錄指令執行的資訊,在 HostSpot 虛擬機器中是不會被回收的;
對於堆中的記憶體回收機制如下:
一、何時觸發對象的回收
- 對象沒有被引用時
- 範圍發生未捕獲的異常
- 程式在範圍正常執行完畢
- 程式執行了System.exit()
- 程式被意外終止(被殺進程)
二、檢測垃圾
1、引用計數法(JDK1.2之前)
當這個類被載入到記憶體以後,就會產生方法區,堆棧、程式計數器等一系列資訊,當建立對象的時候,為這個對象在堆棧空間中指派至,同時會產生一個引用計數器,同時引用計數器+1,當有新的引用的時候,引用計數器繼續+1,而當其中一個引用銷毀的時候,引用計數器-1,當引用計數器被減為零的時候,標誌著這個對象已經沒有引用了,可以回收了。但是如果有兩個對象相互引用,則這種方式無法檢測。
2、根搜尋法
把所有的參考關聯性看作一張圖,從一個 GC Root 節點開始搜尋,搜尋完成後沒有被引用到的節點,即無用的節點,將其標記為垃圾。
可作為 GC Root 節點的對象有
- Java 棧中引用的對象
- 方法區中靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中引用的對象
對於引用在 Java 中存在四種參考型別
強引用:直接 new 出來的對象是強引用,是使用最普遍的引用。如果一個對象具有強引用,那記憶體回收行程絕不會回收它。當記憶體空間不足,Java虛擬機器寧願拋出OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的對象來解決記憶體不足的問題。
軟引用:通過類 SoftReference 進行封裝。當JVM中的記憶體不足的時候,記憶體回收行程會釋放那些只被軟引用所指向的對象,如果記憶體還不足,才會拋出OutOfMemory錯誤。使用軟引用對象時要判斷是否還存活。軟引用非常適合於建立緩衝,當記憶體不足的時釋放緩衝。
弱引用:通過類 WeakReference 進行封裝。如果一個對象的所有引用都是弱引用,則這個對象會被回收。弱引用解決了對象在存活關係上的耦合關係。弱引用在集合中最常見,尤其是在雜湊表中,雜湊表的介面允許使用任何對象作為鍵,當一個索引值被放入雜湊表中後,雜湊表就有了這些鍵和值對象的引用。如果是強引用,只要雜湊表本身還存活則鍵和值對象不會被回收,弱引用就是為瞭解決這個問題。Java 中提供了 WeakHashMap 來滿足這個需求。
虛引用:通過類 PhantomReference 進行封裝,主要用於檢測對象是否已經清除。
三、記憶體回收
在 JDK1.2 之前是通過引用計數法來標記垃圾,在之後是使用根搜尋法來標記垃圾。當垃圾標記後有三種方式來回收垃圾:
1、標記-清除
標記-清除演算法是當垃圾被標記後,直接回收其所佔空間。這種方式不需要進行對象的移動,在存活對象較多的情況下效率很高,但是會造成記憶體片段。
2、複製
複製演算法是在搜尋垃圾過程中將存活對象複製到一塊新的空間中,然後再清除垃圾。這種方式在存活對象比較少的情況下效率很高,但是需要一塊記憶體空間用於對象的移動。
3、標記-整理
標記-整理演算法與標記-清除演算法一樣對對象進行清除,但是在標記-清除演算法的基礎上要將所有存活對象往同一地區移動,並更新對應的引用。這種方式成本較高,但是解決了記憶體片段的問題。
四、堆中分代的記憶體回收策略
因為不同對象的生命週期是不一樣的,所以根據對象的生命週期不同可以採取不同的回收演算法,以便提高回收效率。
1、年輕代
在年輕代的記憶體中按照 8:1:1 的比例分為一個 Eden 區和兩個 Survivor 區。大部分對象在 Eden 中產生,當 Eden 區滿後觸發 Minor GC(不一定等Eden區滿了才觸發),將 Eden 區和非空閑 Survivor 區存活對象複製到另一個閒置 Survivor 區中。年輕代 minor GC 就是在兩個 Survivor 區之間相互複製存活對象,直到一個 Survivor 區滿後將存活對象複製到年老代中。
2、年老代
年老代存放對象的生命週期較長,因為對象在年輕代經曆了多次回收後仍倖存。年老代與年輕代的記憶體比大概是1:2,當年老代也滿了,將觸發 Major GC 即 Full GC,對整個堆(包括新生代、舊生代和持久代)進行記憶體回收。
3、持久代
即方法區,存放的是常量、位元組碼檔案資訊,相對較為穩定,因為不會頻繁建立對象。如果持久代滿了後,觸發Full GC。
五、GC的類型
在年輕代的 GC 被稱為 Minor GC,在年老代的 GC 被稱為 Major GC,另外還有 Full GC 是指回收整個堆(包括新生代、舊生代和持久代)。
導致 Minor GC 的情況:
導致 Major GC 的情況:
導致 Full GC 的情況:老年代已滿、持久代已滿、調用 System.gc()、上次 GC 後堆的各域分配策略動態變化
上述的 Minor GC、Major GC 及 Full GC 我的理解是這僅僅是個稱呼,來代表 GC 作用的地區。而關於 GC 的類型,有以下幾種:
Serial GC:年輕代單線程收集器,使用複製演算法,在 JDK1.3 之前廣泛使用。當進行記憶體回收時,需要中斷所有使用者線程。
SerialOld GC:年老代單線程收集器,使用標記-整理演算法,是 Serial GC 的老年代版本。
ParNew GC:年輕代多線程收集器,使用複製演算法。可以理解為 Serial GC 的多線程版。在單 CPU 環境下效率遠低於 Serial GC。
ParallelScavenge GC:並行收集器,使用複製演算法,追求高輸送量。
ParallelOld GC:並行收集器,使用複製演算法,是 ParallelScavenge GC 年老代版本。
CMS GC(Concurrent Mark Sweep GC):高並發,佔用 CPU 較高,使用標記-清理演算法。
[Java]理解JVM之四:記憶體回收機制