java垃圾收集與內存分配策略筆記

什麼是垃圾收集(Garbage Colletion)?

程序員不必手動回收不用的對象,而是由JVM自動回收不再使用的對象,清除它們佔用的
內存。
gc需要解決的3個問題:
* 那些內存需要回收?
* 什麼時候需要回收?
* 如何回收?

gc管理的內存區域

Java內存運行時區域中程序技術器、虛擬機棧、本地方法棧這三個區域是線程私有的.
隨着方法的進入、退出,棧的棧幀進行入棧和出棧操作。每一個棧幀分配多少內存基本
是在編譯生成Class文件後確定了,因此不要過多考慮幾個區域的內存分配和回收問題。
主要考慮的是Java堆區和方法區(包括運行時常量池)。因爲對象的創建和銷燬必須在
運行時才能確定,因此這些對象使用的內存的分配和回收都是在運行時動態分配和銷燬
的。

JVM對堆中的對象的管理?

引用計數算法(Reference Counting)

給對象添加一個引用計數器,當有一個引用時,計數器加1。當計數器爲0時,該對象就是
沒有使用的對象。
弊端:無法解決循環引用的問題。A、B、C三個對象,A引用B,B引用C,C引用A。除此之外,
這3個對象就沒有其他引用。實際上這3個對象已經不會再被訪問了,但是引用計數算法
卻無法回收他們。

根搜索算法(GC Roots Tracing)

通過一系列被稱爲”GC Roots”的點作爲起始向下搜索,當一個對象到GC Roots沒有任何
引用鏈(Reference Chain)相連,則證明此對象是不可用的。
在Java中,GC Roots包括:
1. 在虛擬機棧(幀中的本地變量)中的引用對象
2. 方法區中的類靜態屬性引用的對象
3. 方法區中的常量引用的對象
4. JNI(native方法)中的引用

根搜索算法判斷對象是否存活與引用有關。java將引用分爲四類:**強引用、軟引用、
弱引用、虛引用**,這四種引用強度依次逐步減弱。

根搜索算法中不可達的對象並非“非死不可”,這時候它們暫時處於“緩刑”階段,
真正宣告一個對象死亡,至少要經歷兩次標記過程。
如果通過根搜索後發現沒有與GC Roots相連的引用鏈相連。它將會被第一次標記並且
會進行篩選,當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬
機調用過,虛擬機將這兩種情況都視爲沒有必要執行finalize()方法。

如果這個對象被判定爲有必要執行finalize()方法,那麼這個對象將會被放置在一個
名爲F-Queue的隊列之中,並在稍後有一條虛擬機自動建立、低優先級的 finalize的線
程去執行。虛擬機會觸發finalize()方法,但並不承諾等待它執行結束。finalize()
方法是對象逃脫死亡的最後一次F-Queue中的對象機會。GC將會對F-QUEUEF-Queue中的
對象進行第二次小規模的標記,如果某個對象重新與GC Roots引用鏈上的對象建立關聯
關係,那麼第二次標記時它將被移除F-Queue。
任何一個對象的finalize()方法都只會被系統自動調用一次,如果對象面臨下一次回收
,它的finalize()方法不會被再次執行。

JVM對方法區的管理

方法區即永久代,在這一個內存區域進行GC的目的是回收廢棄常量和無用類
判定廢棄常量的方式與判定對象是否存活的方式類似。
判定無用類的3個條件:
1. 該類所有的實例都已經被GC,也就是JVM中不存在該Class的任何實例
2. 加載該類的ClassLoader已經被GC
3. 該類對應的java.lang.Class對象沒有在任何地方被引用。不能通過反射訪問該類的
方法。

在大量使用反射、動態代理、CGLib等bytecode框架、動態生成JSP以及OSGi這類頻繁
自定義ClassLoader的場景都需要JVM具備類卸載的支持以保證永久代不會溢出。

是否對類進行回收可以使用-XX:+ClassUnloding參數進行控制,還可以使用
-verbose:class或者-XX:TraceClassLoaing 、 -XX:+TraceClassUnLoading查看
類加載、卸載信息

垃圾收集算法的實現

標記-清除(Mark-Sweep)

算法分成”標記”和”清除”兩個階段。先標記處需要回收的對象,然後回收所有需要回收
的對象。
標記清除算法是最基礎的收集算法,後續的收集算法都是基於這種思路優化後得到的。

缺點:

  1. 效率問題:標記和清除的過程效率都不高
  2. 空間問題:標記清理之後會產生大量不連續的內存碎片。空間碎片太多,可能導致
    後續使用後無法找到足夠的連續內存而提前出發另一次的垃圾收集。

標記-複製(Mark-Copying)

爲了解決效率和內存碎片的問題,提出了一種”Mark-Copying”的收集算法,可以將
內存劃分爲兩塊,每次使用其中一塊,當半區的內存用完了,可以將還存活的對象
複製到另外一塊上,然後就把原來整塊內存空間一次清理掉。這種方法的優點是每次
內存回收都是對整個半區的回收,內存分配時也是在連續的半個內存區域上分配,不用
考慮內存碎片等複雜情況,只需要移動堆頂指針,按順序分配內存。這種算法實現簡單,
運行高效,但是閒置了一半的內存。
商業虛擬機中用標記-複製算法回收新生代。由於新生代只能怪的對象,存活時間
很短,不需要按1:1的比例來劃分內存,而是將內存分爲一塊較大的eden和2塊較小的
survivor空間,每次使用eden和其中一塊survivor。當回收時將eden和survivor中還
存活的對象一次拷貝到另一個survivor空間上,然後清理掉eden和正在使用survivor。
虛擬機默認eden和survivor的大小比例是8:1,該比值可以通過-XX:SurvivorRatio調
整。

缺點:

如果對象存活率較高,survivor區不夠用,就需要依賴其他內存區域(譬如老年代)進行分配擔保(Handle Promotion).

標記-壓縮(Mark-Compact)

先標記需要回收的對象,然後將所有存活的對象一端移動,然後清理需要回收的對象。

缺點:

  1. 複製收集算法在對象存活率高的時候,效率有所下降
  2. 需要額外的內存空間進行分配擔保,當對象存活率高導致survivor區不夠用,以後
    可以將存活時間較長的對象放到其他區域。

內存分代

將堆區分爲新生代和老年代,方法區分爲永久代

垃圾收集器

  1. Serial收集器
      單線程收集器,收集時會暫停所有工作線程(我們將這件事情稱之爲Stop TheWorld,下稱STW),使用複製收集算法,虛擬機運行在Client模式時的默認新生代
    收集器。

  2. ParNew收集器
      ParNew收集器就是Serial的多線程版本,除了使用多條收集線程外,其餘行爲包括算法、STW、對象分配規則、回收策略等都與Serial收集器一模一樣。對應的這種收集器是虛擬機運行在Server模式的默認新生代收集器,在單CPU的環境中,ParNew收集器並不會比Serial收集器有更好的效果。

  3. Parallel Scavenge收集器
      Parallel Scavenge收集器(下稱PS收集器)也是一個多線程收集器,也是使用複製算法,但它的對象分配規則與回收策略都與ParNew收集器有所不同,它是以吞吐量最大化(即GC時間佔總運行時間最小)爲目標的收集器實現,
    允許較長時間的STW換取總吞吐量最大化

  4. Serial Old收集器
      Serial Old是單線程收集器,使用標記-壓縮算法,是老年代的收集器,上面三種都是使用在新生代收集器。

  5. Parallel Old收集器
      老年代版本吞吐量優先收集器,使用多線程和標記-壓縮算法,
    JVM 1.6提供,在此之前,新生代使用了PS收集器的話,老年代除
    Serial Old外別無選擇,因爲PS無法與CMS收集器配合工作。

  6. CMS(Concurrent Mark Sweep)收集器
      CMS是一種以最短停頓時間爲目標的收集器,使用CMS並不能達到GC效率最高(總體GC時間最小),但它能儘可能降低GC時服務的停頓時間,這一點對於實時或者高交互性應用(譬如證券交易)來說至關重要,這類應用對於長時間STW一般是不可容忍的(因此CMS不可以與PS配合使用)。CMS收集器使用的是標記-清除算法,也就是說它在運行期間會產生空間碎片,所以虛擬機提供了參數開啓
    CMS收集結束後再進行一次內存壓縮。

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