天天看點

java虛拟機之gc

引用:

java 和 c++ 之間有一堵 由動态記憶體配置設定和垃圾回收技術所圍成的高牆。牆外的人想進入 而牆内的人卻想出來。

java虛拟機之gc

回收記憶體的位置

記憶體回收的位置主要在 jvm 的堆和方法區中(線程共享的區域)。而程式計數器、虛拟機棧和本地方法棧都是線程獨立的,随線程而生 随線程而滅,當線程結束或方法結束後記憶體自然就回收了。

回收機制 (可達性分析法)

  • GC Root節點
    • 本地方法棧和虛拟機棧中引用的對象。
    • 方法區中靜态屬性 static 引用的對象和方法區中常量 final 引用的對象。
  • 引用類型
    • 強引用(肯定不回收)
    • 軟引用(記憶體溢出前進行回收)
    • 弱引用(肯定回收)
    • 虛引用(隻是一種引用類型,不對生存時間構成影響,唯一目的就是能在這個對象被回收時收到一個系統通知)
  • 對象自我拯救的逃脫機制

    可達性分析檢索時,會将那些不在GC Root引用鍊上的對象進行第一次标記并篩選。 篩選的條件是:對象是否有必要執行 finalize() 方法。

    判斷條件是:當對象沒有覆寫 finalize()或者finalize()方法已被執行,jvm則認為沒有必要執行。

    如果判斷對象有必要執行 finalize()方法,則将對象放入一個叫做F-Queue的隊列中,稍後會有虛拟機建立一個低優先級的 Finalize 線程執行它(這裡的執行僅僅指揮觸發它,而不一定等它執行完畢)。finalize() 方法是對象逃脫的最後一個機會。之後GC會對F-Queue隊列中的對象進行第二次的标記,若此時對象還未與GC Root引用鍊建立聯系,那麼這個對象基本上就真的被回收了。

    注意:任何一個對象的 finalize() 方法都隻會被系統執行一次。

  • 方法區的回收

    方法區主要回收:廢棄的常量和無用的類這兩部分内容。

    • 廢棄常量:沒有任何對象引用常量池中的常量。
    • 無用的類:
      1. 該類的所有執行個體已被回收,也就是堆中不存在該類的任何執行個體。
      2. 加載該類的

        ClassLoad

        類加載器已被回收。
      3. 該類所對應的

        Class

        對象已沒有在任何地方引用,也就是說不能再任何地方通過反射來擷取該類的執行個體。

垃圾回收算法

  • 标記-清除

    分為标記和清除兩個階段

    也就是對GC 第二次标記過的那些不在GC Root引用鍊上的對象,執行清除操作。

    缺點是:會産生很多不連續的記憶體碎片,在之後的運作中無法配置設定大對象。

  • 标記-整理

    對标記-清除之後的産生的大量不連續的記憶體碎片,進行整理重排序使存活的對象向一端移動,進而産生連續的大空間。

  • 複制

    最初始時 是将記憶體分為大小相等的兩個區域,假定為A和B,每次使用的時候隻使用其中的一個區域A,當發生GC時,将A中存活的對象複制到B區域中,接着清除掉A區域後再将B區域中的對象複制到A區域中。

  • 分代收集

    目前的商業虛拟機采用的都是分代收集的算法,這種算法并沒有提出新的思想,隻是根據對象存活周期的不同将記憶體分為幾塊,一般是将記憶體分為新生代和老年代。在新生代中使用

    複制

    算法,老年代中試用

    标記整理

    算法。

    新生代中的複制算法經過IBM的研究表明,新生代中的對象朝生夕死,将記憶體分按1:1進行配置設定浪費。而是将新生代分為一塊較大的

    Eden

    空間 E 和兩塊較小的

    Survivor

    空間 S1 和 S2(預設是按照

    8:1:1

    的比例進行配置設定)。每次使用的時候 隻使用 E 和 S1 大小為新生代的90%,當發生GC的時候,将兩塊空間中存活的對象複制到S2中。

但是,誰都不能保證每次GC時隻有不多于90%的對象存活,也就是說S2 空間不足以存放存活的對象,這時就需要其他記憶體(這裡指老年代)進行配置設定擔保。

但是還有個問題,誰又能保證,即使有老年代做了擔保,就一定能保證一定能存放存活的對象呢?這裡的問題稍後進行解釋…

HotSpot的算法實作

  • 枚舉根節點

    GC時需要進行可達性分析找到GC Root的引用鍊,這裡有兩個問題,第一 如何建立GC Root引用鍊?GC Root全局性的引用,分布在執行上下文中,如果要逐個檢查裡面的引用,必定為消耗很長的時間。第二:GC 停頓。可達性分析時,必須要在一個能夠確定一緻性的快照中進行,可就是說在可達性分析時,整個執行系統看起來向被當機在一個時間點上,不可以再出現不斷變化的引用關系。

    其實,在類加載的時候,虛拟機使用一組稱為OopMap的資料結構,将對象的位置進行記錄。這樣GC在掃描時,直接查找OopMap就可以得知。

  • 安全點

    GC也不是說在程式執行的任何地方都可以執行的,而是在特定的位置,也就是安全點。

    當需要GC時,隻是設定一個GC的辨別,線程執行時主動輪詢這個辨別(辨別的位置和安全點重合),當判斷中斷辨別為真時,自己中斷挂起。

  • 安全區域

    針對安全點而言,當需要GC時,設定一個辨別,線程主動輪詢這個辨別。

    但是當線程處于 sleep 或 Blocked 狀态時,線程是無法響應虛拟機的請求,也就無法走到就近的安全點。

    安全區域:指在一段代碼片段中,引用關系不會發生變化。在這個區域的任何位置開始GC都是安全的。

    當安全區域中的線程執行完成,離開安全區域時,會詢問GC是否完成,若GC完成了,則線程繼續執行。否則,就必須等待,直到GC完成 發出可以離開的指令。

在這裡有個疑問:安全點線程的中斷是什麼方式的中斷。中斷的線程又是如何接收GC完成的信号,難到使用的是喚醒所有安全點中被挂起的線程? orn

垃圾收集器

JDK7 update 14之後的Hotspot使用的都是G1收集器。

詳情稍後補充。

記憶體配置設定和回收政策

往大方向來講,主要是在堆上進行配置設定。

1. 對象優先在新生代(eden)上配置設定,當新生代沒有足夠空間事,jvm進行一次Minor GC,新生代采用複制算法收集記憶體。

2. 大對象直接進入老年代。指那些超長的字元串或數組。

3. 長期存活的對象進入老年代。對象每熬過一次GC年齡增長一歲。預設15歲之後,進入老年代。

4. 動态年齡的判斷

當新生代中相同年齡所有對象大小的總和 大于 新生代空間的一半,則年齡大于或等于該年齡的對象都可以直接進入老年代。

5. 空間配置設定擔保

當發生Minor GC(新生代GC)之前,虛拟機會檢查老年代的最大可用連續空間是否大于新生代所有對象的總空間。如果大于,那麼Minor GC可以確定是安全的。如果不成立,虛拟機會檢查HandlePromotionFailure設定值是否允許擔保失敗。若允許擔保失敗,那麼會繼續檢查 老年代最大可用連續控件是否大于曆代晉升到老年代對象的平均大小,如果大于,将會嘗試進行Minor GC,盡管是否風險的;如果小于或者設定為不允許擔保失敗,那此時要改為先進行一次Full GC(老年代 GC)。