【JVM】垃圾回收算法

垃圾回收

垃圾回收(Garbage Collecting ,GC),顧名思義就是釋放垃圾佔用的空間,防止內存泄露。有效的使用可使用的內存,對內存堆中已經死亡或者長時間沒有使用的對象進行清除和回收。垃圾回收機制就是java虛擬機(JVM)垃圾回收器提供的一種用於在空閒時間不定時回收無任何對象引用的對象佔據內存空間的一種機制。java

判斷對象是否存活的算法

一、引用計數法

引用計數法的作法是爲每一個對象添加一個引用計數器,用來統計指向該對象的引用個數,當有地方引用該對象時計數器加1,當引用失效時計數器減1,用對象計數器是否爲0來判斷對象是否可被回收。一旦某個對象的引用計數器爲0,則說明該對象已經死亡,即可以回收了。算法

(1)具體實現
若是有一個引用,被賦值爲某一對象,那麼該對象的引用計數器 + 1 。若是一個指向某一對象的引用,被賦值爲其餘值,那麼將該對象的引用計數器 - 1 。也就是說,咱們須要截獲全部的引用更新操做,而且相應的增減目標對象的引用計數器。多線程

(2)缺點
除了須要額外的空間來存儲計數器,以及繁瑣的更新操做,引用計數法還有一個重大的漏洞——沒法處理循環引用對象。ide

舉個例子,假設對象 a 與 b 相互引用,除此以外沒有其餘引用指向 a 或者 b 。在這種狀況下,a 和 b 實際上已經死了,但因爲它們的引用計數器皆不爲0,因此這些循環引用對象所佔據的空間將不可回收,從而形成了內存泄露。
在這裏插入圖片描述ui

二、可達性分析

目前Java虛擬機的主流垃圾回收器採起的是可達性分析算法。可達性分析算法的實質在於將一系列GC Roots 做爲初始的存活對象合集(live set),而後從該合集出發,探索全部可以被該集合引用到的對象,並將其加入到該集合中,這個過程也稱之爲標記(Mark)。最終,未被探索到的對象即是死亡的,是能夠回收的。spa

![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200707181157852.png =500px)
可達性分析能夠解決引用計數法不能解決的循環引用問題。還拿上面例子說明,即使對象 a 和 b 相互引用,只要從GC Roots出發沒法到達 a 或者 b ,那麼可達性分析便不會將它們加入存活對象合集中。線程

(1)GC Roots
暫時理解爲由堆外指向堆內的引用,通常來講,GC Roots包括如下幾種:指針

  • Java方法棧幀中的局部變量
  • 已加載類的靜態變量
  • JNI handles
  • 已啓動且未中止的Java線程

(2)缺點對象

在多線程環境下,其餘線程可能會更新已經訪問過的對象中的引用,從而形成誤報(將引用設置爲null),或者漏報(將引用設置爲未被訪問過的對象)。blog

若是發生了誤報,Java虛擬機最多損失了部分垃圾回收的機會。漏報比較麻煩,由於垃圾回收器可能回收事實上仍被引用的對象內存。一旦從原引用訪問已經被回收了的對象,則頗有可能會直接致使Java虛擬機崩潰。

經常使用垃圾收集(GC)算法

一、標記-清除算法

分爲兩個步驟:第一就是標記,也就是標記全部須要回收的對象;第二就是清理,標記完成後進行統一的回收帶有標記的對象佔據的內存空間。這個算法效率不高,並且在標記清除以後會產生大量的內存不連續的內存碎片,當程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而形成內存空間浪費。
在這裏插入圖片描述

缺點
(1)形成內存碎片。因爲Java虛擬機的堆中對象必須是連續分佈的,所以可能出現總空閒內存足夠,可是沒法分配的極端狀況。
(2)分配效率低。若是是一塊連續的內存空間,能夠經過指針加法來作分配。對於空閒列表,Java虛擬機則須要逐個訪問列表中的項,來查找可以放入新建對象的空閒內存。

二、複製算法

複製算法是將內容容量劃分紅大小相等的兩塊,每次只使用其中的一塊。當一塊內存用完以後,就將還存活的對象複製到另外一塊上面,而後再把已使用的內存空間一次性清理。這樣使得每次都對其中一塊進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只是這種算法的代價就是將內存縮小爲原來的一半了。
在這裏插入圖片描述

三、標記-整理算法(或叫壓縮算法)

標記整理算法和標記清除算法很類似,顯著的區別是:標記清除算法只對不存活的對象進行處理,剩餘存活對象不作任何處理,因此形成了內存碎片的問題;而標記整理算法對不存活的對象進行清除,還對存活的對象進行從新整理,所以不會產生內存不連續的現象。
在這裏插入圖片描述

分代收集算法

分代收集算法是目前大部分JVM的垃圾收集器採用的算法。其核心思想是根據對象存活的生命週期將內存劃分爲若干個不一樣的區域。而後根據不一樣的區域採用合適的收集算法,它自己並非一個新的收集算法。在jdk1.7以前,對JVM分爲三個區域:新生代,老年代,永久代。

(1)新生代(複製算法)

新生代的目標就是儘量快速的收集掉那些生命週期較短的對象,通常狀況下新生成的或者朝生夕亡的對象通常都是首先存放在新生代裏。
由於新生代會頻繁的進行GC清理,因此採用的是複製算法,先標記出存活的實例,而後清除掉無用實例,將存活的實例根據年齡(每一個實例被經歷一次GC後年齡會加1)拷貝到不一樣的年齡代。
在這裏插入圖片描述

(2)老年代(標記整理或標記清除算法)

老年代通常存放的是一些生命週期較長的對象,好比是新生代中經歷了N次垃圾回收後仍然存活的對象都進入了老年代。
這塊內存區域通常大於年輕代,GC發生的次數也比年輕代要少。
在老年代中由於對象存活率較高,沒有額外的空間對它分配擔保,就必須使用標記清除或標記整理。

(3)永久代

永久代主要存放靜態文件,如java類,方法等,永久代對垃圾回收沒有顯著影響。
方法區主要回收的內容有:廢棄的常量,無用的類,對於廢棄的常量能夠經過引用的可達性分析判斷,可是對於無用類須要同時知足如下三個條件:
一、該類的全部實例都已經被回收了
二、加載該類的ClassLoader已經被回收了
三、該類對於的java.lang.Class 對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法。

什麼時候觸發GC(垃圾收集)?

  1. 執行System.gc()的時候
  2. 老年代空間不足,一次Full GC以後,而後不足 會觸發Java.outofmemoryError.java heap space
  3. 永久代空間不足,永生代或者永久代,java.outofMemory PerGen Space
  4. minor 以後 survivor 放不下,放入老年代,老年代也放不下,觸發FullGC,或者新生代有對象放入老年代,老年代放不下,觸發FullGC
  5. 新生代晉升爲老年代的時候,老年代剩餘空間低於新生代晉升爲老年代的速率,會觸發老年代回收
  6. new 一個大對象,新生代放不下,直接到老年代,空間不夠,觸發FullGC

如何避免頻繁的GC?

  1. 不要頻繁的new對象
  2. 不要顯式的調用system.gc()
  3. 不要用String+ ,使用StringBuilder
  4. 不要使用Long ,Integer,儘可能使用基本類型
  5. 少用靜態變量,不會回收
  6. 可使用null進行回收
相關文章
相關標籤/搜索