歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> JVM-對象的存活與死亡

JVM-對象的存活與死亡

日期:2017/3/1 9:17:50   编辑:Linux編程

當Java虛擬機進行垃圾收集的時候,那麼它必須要先判斷對象,是否還存活,如果存活就不能對它進行回收。所以判斷一個對象是否存活是Java虛擬機必須要實現的。

1.對象是否存活
  1)引用計數器:給對象添加一個引用計數器,每當有一個地方引用他時,計數器值就加一,當引用失效時,計數器值就減一。任何時刻計數器為零的對象就是不可在被使用的。

  分析:客觀的說,引用計數器算法(Reference Counting)的實現簡單,判定效率很高,在大部分情況下,都是一個不錯的算法。但是,主流的Java 虛擬機裡面沒有選用引用計數算法來管理內存,其中最主要的原因是他很難解決u對象之間相互循環引用的問題。例如:objA.instance = objB;及 objB.instance = objA;除此之外兩個對象再無其他引用,實際上這兩個對象已經不可能再被訪問,但是他們因為互相引用著對方,導致他們的引用計數都不為零,於是引用計數算法無法通知GC收集器回收他們。

  2)可達性分析算法
    在主流的商用程序語言(Java , C# 等)的主流實現中都是使用可達性分析(Reachability Analysis)來判定對象是否是存活的。

    基本思想:通過一系列稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots 沒有任何引用鏈相連(用圖論的話來說就是從GC Roots 到這個對象不可達)時,則證明此對象是不可引用的。

   在Java 語言中,可作為GC Roots的對象包括下面幾種:
    a.虛擬機棧(棧幀中的本地變量表)中引用的對象
    b.方法區中類靜態屬性引用的對象
    c.方法區中常量引用的對象
    d.本地方法棧中JIT(即一般說的Native方法)引用的對象

2.引用


  引用分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reerence)、虛引用(Phanton Reference)4種,這4種引用強度依次逐漸減弱。

  強引用就是指在程序代碼之中普遍存在的,類似“Object obj = new Object();”這類的引用,只有強引用還存在,垃圾收集器就永遠不會回收掉引用的對象。

  軟引用是用來描述一些還有用但是並非必須的對象。對於軟引用關聯著的對象,在系統將要發生內存溢出異常之前,將會把這些對象列入回收范圍之中進行第二次回收。如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。JDK1.2後,提供了SoftReference類來實現軟引用。

  弱引用也是用來描述非必須對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象.JDK1.2後提供了WeakReerence。

  虛引用也稱為幽靈引用或者幻影引用,也是最弱的一種引用關系。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。JDK1.2後,提供了phantonReference類來實現虛引用。

3.垃圾收集算法
  a) 垃圾-清除算法
  算法分為"標記"和"清除"兩個階段: 首先標記出所有需要回收的對象,在標記完成後,統一回收所有被標記的對象。

  不足:i. 效率問題:標記和清除兩個過程的效率都不高
   ii. 空間問題:標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致在程序運行過程中需要分配對象時,無法找到足夠的連續的內存而不得不提前觸發另一次垃圾收集動作。

  b) 復制算法
  將可用內存按容量劃分為大小相等的兩快,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另一塊上面,然後再把已使用過的內存空間一次清空。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
  現在的商業虛擬機都采用這種收集算法回收新生代。

  c) 標記-整理算法
  標記過程和"標記-清除"算法一樣,但是後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。

  d) 分代收集算法
  當前商業虛擬機的垃圾收集都采用"分代收集"(Denarationl Collection)算法。

  根據對象存活周期的不同將內存劃分為幾塊。一般是把Java堆分為新生代和老年代。這樣就可以根據各個年代的特點采用最合適的收集算法。在新生代中,每次垃圾收集時,都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高,沒有額外空間對它進行配置擔保,就必須使用"標記-整理"算法來進行回收。

4.HotSpot的算法實現
  1) 枚舉跟節點
  GC停頓:可達性分析工作必須在一個能確保一致性的快照中進行。"一致性"是指在整個執行系統看起來就像被凍結在某個時間上,不可以出現分析過程中對象引用關系還在不斷變化的情況,該點不滿足的話分析結果准確性就無法得到保證。這點是導致GC進行時必須停頓所有Java執行線程的其中一個重要的原因,即使是號稱幾乎不會發生GC停頓的CMS收集起中,枚舉根節點時也是必須停頓的。

  在HotSpot的實現中,是使用一組稱為OopMap的數據結構來達到這個目的的,在類加載完成的時候,HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來,在JIT編譯過程中,也會在特定的位置記錄下棧和寄存器中哪些位置是引用。這樣GC在掃描時就可以得知這些信息類。

  2) 安全點
  HotSpot沒有為每條指令都生成OopMap,只是在"特定位置"記錄類這些信息,這些位置稱為安全點(Safepoint),即程序執行時並非所有地方都能停頓下來開始GC,只有到達安全點時才能暫停。

  Safepoint選定既不能太少以至於GC等待時間太長,也不能過於頻繁以至於過分增大運行的負荷。所以安全點的選定基本上是一程序"是否有讓程序長時間執行的特征"為標准選定的,因為每條指令執行的時間都非常短暫,程序不太可能因為指令流長度這個原因而過長時間運行,"長時間執行"的最明顯的特征就是指令序列復用,例如方法調用、循環跳轉、異常跳轉等,所以具有這些功能的指令才會產生Safeponit。

  如何在GC發生發生時讓所有線程(不包括執行JNI調用的線程)都在最近的安全點上才停下來。有兩種方案可供選擇:
  I.搶先式中斷(Preemtive Suspension):不需要線程的執行代碼主動去配合,在GC發生時,首先把所有線程全部中斷,如果發現有線程中斷的地方不再安全點上,就恢復線程讓它跑到安全點上。(現在幾乎沒有虛擬機實現采用搶先式中斷來暫停線程響應GC事件)

  II.主動式中斷(Boluntary Suspension):思想是當GC需要中斷線程的時候,不直接��線程操作,僅僅簡單地設置一個標志,各個線程執行是主動去輪詢這個標志,發現中斷標志為真時就自己中斷掛起。輪詢標志的地方和安全點是重合的。另外再加上創建對象需要分配內存的地方。

  3) 安全區域(Safe Regin)
  安全區域是指在一端代碼片段中,引用關系不會發生變化。在這個區域的任意地方開始GC都是安全的,我們可以把Safe Regin看作是被擴展了的Safepoint。

Copyright © Linux教程網 All Rights Reserved