GC基本原理學習(Garbage Collected)

引言

Java的內存動態分配和垃圾收集的問題,都交給了JVM來處理。注意,將JVM運行數據區(虛擬機棧【棧幀】,程序計數器,堆內存)粗略的分爲棧和堆(所有線程共享),回收的是堆中的對象實例。不是棧中的引用類型。
繪製的內存圖
那麼JVM是如何處理的?
從三個問題來分析:
1. 哪些內存需要回收?
2. 什麼時候進行回收?
3. 如何回收?

注:現代收集器基本採用分代收集算法,堆分爲:新生代和老年代。

1. 哪些內存需要回收?什麼時候回收?

1.1 瞭解下對象的創建:

  1. 通過new 關鍵字。
  2. JVM遇到new指令,檢查是否能在常量池中定位到一個類的符號引用。
  3. 檢查是否已被加載,解析,初始化過。
  4. 沒有,則執行相應的類加載過程。
  5. 類加載檢查通過後,爲新生對象分配內存。(類加載後確定對象所需內存大小)
  6. 從Java堆中劃分出一塊確定的內存。

1.2 若爲“死亡”的對象,則需要回收,如何判斷對象是否存活?

1.2.1 引用計數算法

  • 給對象添加一個引用計數器
  • 每當有一個地方引用它時,計數器指加1
  • 當引用失效時,計數器指減1
  • 任何時刻計數器爲0的對象就是需要回收的
    Java虛擬機沒有采用這種方法來管理內存,主要原因是它難以解決對象之間相互循環引用的問題。(python中採用)

1.2.2 可達性分析算法

  • 用一個“GC Roots”的對象(指一系列中的其中一個並非某一種)作爲起始點
  • 從該節點向下搜索,搜索走過的路徑稱爲引用鏈
  • 若一個對象沒有與任何引用鏈相連,即不可達
  • 證明該對象是不可用,死亡
    Java,C#採用此方法。

1.2.3 哪些可作爲GC Roots的對象?

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中JNI引用的對象

1.2.4 無論哪種算法都是與“引用”有關,下面分析引用的4種類型,強度依次減弱。

  • 強引用:Java中普遍存在的(如:Object obj = new Object()),只要強引用還在,垃圾收集器永遠不會回收掉被引用的對象實例。
  • 軟引用:用於有用但非必需的對象。在系統要發生內存溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。[SoftReference]
  • 弱引用:用於非必需對象。被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。[WeakReference]
  • 虛引用:不影響生存時間,唯一目的就是能在這個對象被收集器回收時收到一個系統通知。[PhantomReference]

1.2.5 詳解,真正的”死亡”對象(兩次標記後進行回收

事實上,不可達的對象,也並非“非死不可”,這時它們處於“緩刑”階段,真正死亡至少要經歷兩次標記過程:

1.2.5.1 第一次標記
  • 對對象進行可達性分析後發現沒有與GC Roots相連接的引用鏈
  • 篩選是否有必要執行finalize()方法[沒有覆蓋finalize()方法或finalize()方法已經被JVM調用過,視爲“無需執行”]
1.2.5.2 第二次標記【需要執行finalize(),通過篩選】
  • 將該對象放置F-Queue的隊列中
  • JVM自動建立一個低優先級的Finalizer線程去執行它(會觸發執行,但不一定等待它運行結束)
  • GC對F-Queue中的對象進行標記(對象可通過finalize()拯救自己,重新關聯引用鏈等)
    不建議使用finalize()

1.3 上面講的是需要回收的堆內存,關於回收方法區(或HotSpot虛擬機中的永久代),效率低

1.3.1 廢棄常量

  • 與堆類似,沒有任何對象和其它地方引用的常量
  • 隨內存回收,被系統清理出常量池

1.3.2 無用的類

  • 該類所有的實例都已經被回收(堆中無該類的實例)
  • 加載該類的ClassLoader已經被回收
  • 該類對應的java.lang.Class對象沒有在任何地方被引用,無法通過反射訪問該類的方法。

2. 如何回收?【垃圾收集算法】

2.1 標記-清除[Mark-Sweep]算法

  • 標記出所有需要回收的對象
  • 統一回收(清除)所有被標記的對象
    缺點:效率不高;標記清除後會產生大量不連續的內存碎片。

2.2 複製[Copying]算法

  • 將可用內存按容量劃分爲大小相等的兩塊
  • 每次只使用其中的一塊
  • 當使用的這塊內存用完了,則將還存活的對象複製到另一塊上面
  • 把使用過的那塊內存一次性清理掉
    優點:實現簡單,運行高效,可按順序分配內存。
     缺點:內存直接縮小爲原來一半,代價太大;對象存活率較高時,效率變低。

2.3 標記-整理[Mark-Compact]算法

  • 標記出所有需要回收的對象
  • 讓所有存活的對象都向一端移動(整理)
  • 清理掉存活對象端以外的內存
    適合堆中的老年代的垃圾收集

2.4 分代收集[Generational Collection]算法(商業虛擬機主要採用方法)

  • 根據對象存活週期的不同將內存劃分爲幾塊
  • 一般分爲新生代和老年代
  • 不同年代採用最適當的收集算法
  • 新生代,一般複製算法
  • 老年代,一般“標記-清理”或“標記-整理”
    具體如何回收,需依據具體的垃圾收集器實現

關於內存泄露

  1. OutOfMemoryError異常,java堆溢出:-Xms ,-Xmx
    對象不斷被創建,並且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,那麼在對象數量到達最大堆的容量限制後就會產生內存溢出異常。
  2. VM Stack溢出。-Xss
  3. 常量池溢出。-XX:PermSize,-XX:MaxPermSize

參考:《深入理解Java虛擬機》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章