一、概述
程序在運行過程中是不斷申請內存,釋放內存,如果程序只是申請沒有釋放就會引起內存泄漏內存不足等問題。在C語言、C++中,程序員需要手動的釋放內存,如果程序員粗心忘記回收,就會導致程序bug,在Java中,JVM提供自動回收內存機制GC(內存回收器),減少程序員的工作量和減低由於人為導致內存問題。
二、基本原理
將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命周期特征進行分析后,按照新生代、舊生代的方式來對對象進行收集,以盡可能的縮短GC對應用造成的暫停。
2.1 內存類型
GC回收中的內存分為新生代和老年代, 新生代與老年代的比例的值為 1:2 ( 該值可以通過參數 –XX:NewRatio 來指定 ) 。
-
新生代
新生代是剛剛被釋放的內存存放的地方,分為 Eden、From Survivor、To Survivor三個區(8:1:1) ,一般情況,from Survior和to Survivor有一個空閑(復制算法決定的)。
-
老年代
主要存放程序中年齡較大和需要占用大量連續內存空間的對象。
2.2 GC類型
GC根據回收的內存分為兩種類型:新生代 GC(Minor GC)和老年代 GC(Major GC / Full GC)
-
新生代 GC(Minor GC):指發生在新生代的垃圾收集動作,因為 Java 對象大多都具
備朝生夕滅的特性,所以 Minor GC 非常頻繁,一般回收速度也比較快。 -
老年代 GC(Major GC / Full GC):指發生在老年代的 GC,出現了 Major GC,經常
會伴隨至少一次的 Minor GC(但非絕對的,在 ParallelScavenge 收集器的收集策略里
就有直接進行 Major GC 的策略選擇過程) 。MajorGC 的速度一般會比 Minor GC 慢 10
倍以上。 -
Minor GC觸發機制:
當年輕代滿時就會觸發Minor GC,這里的年輕代滿指的是Eden代滿,Survivor滿不會引發GC -
Full GC觸發機制:
當年老代滿時會引發Full GC,Full GC將會同時回收年輕代、年老代,當永久代滿時也會引發Full GC,會導致Class、Method元信息的卸載
2.3對象回收流程
-
當Minor GC被觸發的時候,new Generation被進行,可能觸發1、2、3、4四種情況。
1)經過minorGC后,eden區的數據如果還存活,執行1或者4。當eden的對象比較大或者survicor已經滿了,執行4,直接永久代;否則操作1,進入則標記回收次數,數據進入survior區,進入from還是to,由survicor決定;
2)survicor區的回收一般是使用標記復制算法,存活的數據從from進入to,或者to進入from,同時GC回收標記次數會jia1,當標記次數達到一定次數,觸發3,對象進入老年代;
-
當full GC時,會觸發所有的GC操作,老年代的自我回收是5。
三、回收算法
3.1、根搜索算法
這個算法的基本思想是通過一系列稱為“GC Roots”的對象作為起始點,從這些節點向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈(即GC Roots到對象不可達)時,則證明此對象是不可用的。 也叫可達性分析算法(找到一個可以作為入口的對象,順藤摸瓜標記).
目前Java中可以作為GC ROOT的對象有:
-
虛擬機棧中引用的對象(本地變量表)
-
方法區中靜態屬性引用的對象
-
方法區中常量引用的對象
-
本地方法棧中引用的對象(Native對象)
3.2、標記 - 清除算法
該算法是基於根搜索算法上實現的,搜索到的對象添加標記,回收沒有標記的對象。
會導致內存碎片問題。
3.3、復制算法(在2上改進)
根據根搜索標記內存,把標記的內存復制到另一塊空白的區域,標記完了清除沒有移動的內存碎片,保證了剩下的內存是連續的,缺點需要的內存空間比較大(一倍),空間浪費;適合用在新生代的回收,由於新生代的GC回收比較頻繁,內存的使用時間還是比較多的,這樣做也提高效率。
3.4、標記 - 整理算法(在2上改進)
在2的基礎上,對內存碎片進行整理,整理成一個連續的內存,缺點是沒有標記復制算法效率高,優點沒有占用多余內存。適合老年代GC次數低的GC收集器。
3.5、引用計數法
- 引用計數法就是如果一個對象沒有被任何引用指向,則可視之為垃圾。這種方法的缺點就是不能檢測到環的存在。
- 首先需要聲明,至少主流的Java虛擬機里面都沒有選用引用計數算法來管理內存。
什么是引用計數算法:
- 給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值加1;
- 當引用失效時,計數器值減1.任何時刻計數器值為0的對象就是不可能再被使用的。
那為什么主流的Java虛擬機里面都沒有選用這種算法呢?
其中最主要的原因是它很難解決對象之間相互循環引用的問題。
引用計數法和根搜索算法可以參考下面文章
四、引用和可達性級別
4.1、引用類型
參考https://www.jianshu.com/p/fcc09b2eb006
不管是什么算法,都會根據引用的不同類型進行不同的操作,java的引用類型中分為四種:
-
強引用:
通常我們通過new來創建一個新對象時返回的引用就是一個強引用,若一個對象通過一系列強引用可到達,它就是強可達的(strongly reachable),那么它就不被回收;
String s = new String();
-
弱引用:內存不足回收
弱引用簡單來說就是將對象留在內存的能力不是那么強的引用。使用Weak Reference,垃圾回收器會幫你來決定引用的對象何時回收並且將對象從內存移除。
被下一個GC周期直接回收WeakReference<Zoo> weakZoo = new WeakReference<Zoo>(zoo); weakZoo.get();
-
軟引用(緩存場景)SoftReference:
SoftReference<Zoo> weakZoo = new SoftReference<Zoo>(zoo);
軟引用和弱引用的區別在於,若一個對象是弱引用可達,無論當前內存是否充足它都會被回收,而軟引用可達的對象在內存不充足時才會被回收,因此軟引用要比弱引用“強”一些;
內存不足時被回收
-
虛引用PhantomReference:
PhantomReference<Zoo> weakZoo = new PhantomReference<Zoo>(zoo);
虛引用是Java中最弱的引用,那么它弱到什么程度呢?它是如此脆弱以至於我們通過虛引用甚至無法獲取到被引用的對象,虛引用存在的唯一作用就是當它指向的對象被回收后,虛引用本身會被加入到引用隊列中,用作記錄它指向的對象已被回收。
4.2、可達性級別
根據引用類型,對可達性的級別也進行划分。
- 強可達
- 弱可達
- 軟可達
- 幻想可達
- 不可達
五、主流的收集器
5.1、新生代收集器:
-
Serial (-XX:+UseSerialGC)
Serial收集器是一款年輕代的垃圾收集器,使用標記-復制垃圾收集算法,是一種串行收集器。它是一款發展歷史最悠久的垃圾收集器。Serial收集器只能使用一條線程進行垃圾收集工作,並且在進行垃圾收集的時候,所有的工作線程都需要停止工作,等待垃圾收集線程完成以后,其他線程才可以繼續工作。
-
ParNew(-XX:+UseParNewGC)
ParNew垃圾收集器是Serial收集器的多線程版本,使用標記-復制垃圾收集算法,,是一種並行收集器。為了利用CPU多核多線程的優勢,ParNew收集器可以運行多個收集線程來進行垃圾收集工作。這樣可以提高垃圾收集過程的效率。
-
ParallelScavenge(-XX:+UseParallelGC)
Parallel Scavenge收集器是是一款年輕代的收集器,它使用標記-復制垃圾收集算法。和ParNew一樣,它也會一款多線程的垃圾收集器,但是它又和ParNew有很大的不同點。
Parallel Scavenge收集器和其他收集器的關注點不同。其他收集器,比如ParNew和CMS這些收集器,它們主要關注的是如何縮短垃圾收集的時間。而Parallel Scavenge收集器關注的是如何控制系統運行的吞吐量。這里說的吞吐量,指的是CPU用於運行應用程序的時間和CPU總時間的占比,吞吐量 = 代碼運行時間 / (代碼運行時間 + 垃圾收集時間)。如果虛擬機運行的總的CPU時間是100分鍾,而用於執行垃圾收集的時間為1分鍾,那么吞吐量就是99%。
5.2、老年代收集器:
-
SerialOld(-XX:+UseSerialOldGC)
Serial Old收集器是Serial收集器的老年代版本,它也是一款使用"標記-整理"算法的單線程的垃圾收集器。這款收集器主要用於客戶端應用程序中作為老年代的垃圾收集器,也可以作為服務端應用程序的垃圾收集器,當它用於服務端應用系統中的時候,主要是在JDK1.5版本之前和Parallel Scavenge年輕代收集器配合使用,或者作為CMS收集器的后備收集器。
-
ParallelOld(-XX:+UseParallelOldGC)
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用"標記-整理"算法,多核,自適應調整。這個收集器是在JDK1.6版本中出現的,所以在JDK1.6之前,新生代的Parallel Scavenge只能和Serial Old這款單線程的老年代收集器配合使用。Parallel Old垃圾收集器和Parallel Scavenge收集器一樣,也是一款關注吞吐量的垃圾收集器,和Parallel Scavenge收集器一起配合,可以實現對Java堆內存的吞吐量優先的垃圾收集策略。
-
CMS(-XX:+UseConcMarkSweepGC) 並發收集器(標記清除)
CMS收集器是目前老年代收集器中比較優秀的垃圾收集器。CMS是Concurrent Mark Sweep,從名字可以看出,這是一款使用"標記-清除"算法的並發收集器。CMS垃圾收集器是一款以獲取最短停頓時間為目標的收集器。由於現代互聯網中的應用,比較重視服務的響應速度和系統的停頓時間,所以CMS收集器非常適合在這種場景下使用。
5.3、適用所有
-
G1 收集器 ( -XX:+UseG1GC )
G1是一款面向服務端應用的垃圾收集器,是從JDK1.7開始支持的,是一款並發收集器,目標是替換其他收集器。這款收集器是使用標記復制算法,取消了新生代和老年代的物理划分,同時還有個和CMS一樣的優點:可預測停頓。