垃圾回收

垃圾回收

標籤(空格分隔): Java算法


那些對象須要回收

JVM內存結構主要包括: 方法區, 堆區, 程序計數器, 本地方法區, 虛擬機棧. 其中的程序計數器, 本地方法區, 虛擬機棧這三個區域是的生命週期隨線程生滅, 因此不須要過多考慮這方面的GC問題.數據結構


引用

在JDK1.2以後, Java對引用的概念進行了補充, 整體分爲四類: 強引用, 軟引用, 弱引用, 虛引用. 這四種引用強度逐漸減弱.多線程

  • 強引用 : 指在代碼中廣泛存在的, 相似 Object object = new Object();這類的引用, 只有強引用還存在, GC就永遠不會收集被引用的對象.
  • 軟引用 : 指一些還有用但而且非必須的對象. 直到內存空間不夠時(拋出OutofMemoryError以前), 纔會被垃圾回收. 採用SoftReference類來實現軟引用.
  • 弱引用 : 用來描述非必須對象. 當垃圾收集器工做時就會回收掉此類對象. 採用WeakReference類來實現弱引用.
  • 虛引用 : 一個對象是否有虛引用的存在, 徹底不會對其生存空間構成影響, 惟一目的就是能在這個對象被回收的時候收到一個系統通知, 採用PhantomRenference類實現.

如何判斷對象已死


1. 引用計數法

爲每個對象的的數據結構添加一個引用計數器, 用於統計指向該對象的引用的數量, 每當多一個引用的時候, 其引用計數器就加一, 當引用再也不指向該地址以後計數器減一. 一旦計數器的值爲0,則表示沒有引用指向該對象, 則對象已經死亡 ( <<尋夢環遊記>> 說的: 人真正的死亡是全部活着的人的都忘了你的時候).佈局

優勢:線程

  1. 效率很高.

缺點:3d

  1. 每一個對象都須要在其原有的數據結構之上添加一個引用計數器的屬性.
  2. 循環引用的話就永遠不會被標記爲死亡.( 對象A引用了對象B, 對象B也引用了對象A )

2. 可達性分析.

目前JVM的主流垃圾回收器採起的可達性分析算法, 這個算法的實質在於將一系列GC Roots做爲初始的存活對象的集合(Live Set), 而後從該集合出發, 探索全部能過被集合直接或間接引用的對象, 而且將其加入集合之中, 這個過程就是標記過程. 最終, 未被探索到的對象即是死亡, 是能夠回收的.代理

什麼是GC Roots, 其實就是由堆外指向堆內的引用.code

  1. JMM虛擬機棧 棧幀中的局部變量.
  2. JMM方法區中的靜態屬性, 常量引用對象.
  3. JMM本地方法區中的, JNI中引用的對象.

可達性分析, 能夠解決循環引用問題, 可是自身也存在一些問題, 好比說在多線程的狀況下, 其餘線程可能會更新已經訪問過的對象的引用, 形成漏刪. 解決方案是 進行兩次可達性分析, 若是兩次某對象都被標記則進行刪除.對象


如何刪除無效對象.


1. 標記清除算法

由名可得: 標記->清除. 獲得須要清除的對象以後就直接進行清除.blog

優勢: 速度快
缺點: 屢次GC以後, 形成大量的碎片空間. 對於須要連續存儲的較大對象沒法存儲, OutofMemoryError


2. 標記整理算法

由名可得: 標記->清除->移動整理. 對標記清除算法的一次改進, 可是由於移動操做, 因此時間成本較高.

優勢: 沒有內存碎片.
缺點: 時間成本較高.


3. 複製算法.

將可用的內存按容量分爲大小兩塊, 每次只是用其中一塊, 當這一塊的內存用完了, 就將還存活着的對象複製到另外一塊內存上, 而後再把已使用的內存空間清理掉.

此處輸入圖片的描述

每次當內存分配的時候空間不夠的時候, 都進行復制算法進行內存整理.

優勢: 實現簡單, 效率高. 解決了標記清除算法致使的內存碎片問題.
缺點: 代價太大, 將內存縮小了一半. 效率隨對象的存活率升高而下降.

3.1 HotSpot虛擬機的改良算法

  1. 弱代理論
  • 分代垃圾收集基於弱代理論, 具體描述以下:
  • 大多分配了內存的對象並不會存活太長時間, 在處於年輕時代的時候就會死掉.
  • 不多有對象會從老年代變爲年輕代.
  • 其中IBM研究代表: 新生代中的98%的對象都是 朝生夕死 ; 因此不須要按照1:1比例來劃份內存.
  1. Hotspot虛擬機新生代內存佈局以及算法.
  • 新生代內存分配一塊較大的Eden和兩塊較小的Survivor空間.
  • 每次使用Eden和其中Survivor空間.
  • 回收時將EdenSurvivor空間中存活的對象一次性的複製到另外一塊Survivor空間上.
  • 最後清理掉Eden和使用過的Survivor空間.

Hotspot默認EdenSurvivor的大小比例是8:1 .

分配擔保:
若是另外一塊Survivor空間沒有足夠的內存來存放上一次新生代收集下來的存活對象, 那麼這些對象則直接經過擔保機制進入老年代.


4. 分代收集算法

當前商業虛擬機的垃圾收集器都是採用分代收集算法, 根據對象存活週期的不一樣將內存劃分爲幾塊. 通常把Java堆分爲新生代, 老年代. JVM根據各個年代的特色採用不一樣的手機算法.

  • 新生代中, 每次進行垃圾回收都會發現大量對象死去, 只有少許存活, 所以比較適合複製算法. 只須要付出少許的存活對象的複製成本就能夠完成收集.
  • 老年代中, 由於對象的存活率比較高, 沒有額外的空間進行分配擔保, 因此比較適合標記-清理, 標記-整理算法來進行回收.

此處輸入圖片的描述

Java堆是JVM所管理的內存的最大的一塊, 也是GC主要的工做區. 其主要分爲兩個區 年輕代老年代, 其中年輕代又分爲EdenSurvivor區, 其中Survivor區又分爲FROMTo兩個區. 可能這個時候你們又會有疑問, 爲何須要Survivor區, 爲何Survivor還要分爲兩個區?

Eden區 : IBM表示有98%的對象是朝生夕死, 因此針對這一現狀, 大多數狀況下, 對象會在新生代Eden區中進行分配, 當Eden區沒有足夠的空間進行分配的時候, 虛擬機會發起一次GC. 經過此次GC以後,Eden會被清空, Eden區中絕大部分的對象會被回收, 而那些無需回收的存貨對象, 將會進到SurvivorFROM區(若FROM不夠, 則直接進入Old區).

Survivor區: 至關因而Eden區和Old區的一個緩衝, 相似於咱們交通中的黃燈. Survivor又分爲兩個區, 一個爲From區, 一個是To區. 每次執行Minor GC 會將Eden區和FROM存活的對象放到SurvivorTo區(若是To則直接進入Old區).

不是新生代到老年代麼, 直接中EdenOld很差了麼,爲何要這麼複雜. 若是沒有Survivor區, Eden區每一次GC, 存活的對象就會被送到老年代, 老年代很快就會被填滿, 而雖然有不少對象雖然一次沒有被消滅掉,可是也存活不了太屢次, 這個時候將其移入Old區會很快的將其填滿.

因此Survivor的存在乎義就是減小被送到老年代的對象, 進而減小GC的發生. Survivor的篩選保證, 只有經歷16此的GC還能再新生代存活的對象,纔會被送到老年代.

Old區佔據着2/3的堆內存空間,只有在Marjor GC的時候纔會進行清理, 每次GC都會出發stop-the-world. 內存越大, SWT的時間也越長, 因此內存也不只僅是越大越好, 因爲複製算法的對象存活率較高的老年代會進行不少次的複製操做, 效率很低, 因此老年代這裏採用的是 標記整理算法.


除了上述所說, 在內存擔保機制的狀況下, 沒法安置的對象也會直接進入老年代, 如下幾種狀況也會進入老年代.

  • 大對象: 指須要大量連續內存空間的對象, 這部分對象不管是不是"朝生夕死", 都會直接進入到老年代, 這樣作主要是爲了不在Eden區以及兩個Survivor區之間發生大量的內存複製, 當你的系統有很是多的"朝生夕死"的大對象的時候, 就值得注意了.
  • 長期存活對象: 虛擬機給每一個對象定義了一個對象年齡(Age)計數器. 正常狀況下的對象會不斷的在SurvivorFROMTo之間進行移動, 對象在每經歷一次Minor GC, 年齡就會自增1, 當年齡增長到15歲的時候, 就會被移入老年代. 這裏的15是能夠進行自定義的.
  • 動態對象年齡: 虛擬機並不重視對象的年齡必須大於15歲, 纔會放入老年區, 若是Survivor空間中相同年齡全部對象的總和大於Survivor空間的通常, 年齡大於等於該年齡的對象就能夠直接進入老年區.
相關文章
相關標籤/搜索