JVM 垃圾收集算法及垃圾收集器
1.對象的創建
在類加載檢查通過后,虛擬機將為新生對象分配內存,對象所需內存的大小在類加載完成后就已經確定了。為對象分配空間等同于把一塊確定大小的內存從Java堆中劃分出來。有如下兩種方式:
-
指針碰撞(Bump the Pointer):假設Java堆中內存是絕對規整的,所有用過的內存都放在一邊,空閑的內存放在一邊,中間放著一個指針作為分界點的指示器,那所分配內存就僅僅是把那個指針向空閑的那邊挪動一段與對象大小相等的距離,這種分配方式稱為“指針碰撞”;
-
空閑列表(Free List):如果Java堆中的內存不是規整的,已使用的內存和空閑的內存相互交錯,那就沒辦法簡單的進行指針碰撞了,虛擬機就必須維護一個列表,記錄哪些內存塊是 可用的,在分配內存的時候從列表中找到一塊足夠大的空間劃分給對象實例,并更新列表上的記錄,這種分配方式稱為“空閑列表”。
Java堆是否規整由所采用的垃圾收集器是否帶有 壓縮整理功能 決定。
2.對象的內存布局
HotSpot虛擬機的對象頭包括兩部分信息:
-
第一部分用于存儲對象自身的運行時數據,這部分數據的長度在32位和64位虛擬機(未開啟壓縮指針)中分別為32位、64位,稱為“Mark Word”;
-
第二部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例;
-
第三部分是對其填充,并不是必然存在的,僅僅起到占位符的作用。對象的大小必須是8字節的整數倍,而對象頭部分正好是8字節的整數倍,因此,當對象實例數據部分沒有對其時,就需要通過對其填充來補全。
3.對象的訪問定位
目前主流的訪問方式有 使用句柄 和 直接指針 兩種:
-
使用句柄訪問,那么Java堆中就會劃分出一塊內存來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址;
- 優勢:reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是普遍的行為)時只改變句柄中的實例數據指針,而reference本身不需要修改;
-
使用直接指針訪問,那么Java堆對象的布局中就必須考慮如何放置訪問類型數據的相關信息,而reference中存儲的直接就是對象地址。
- 優勢:速度更快,節省了一次指針定位的時間開銷,因為對象的訪問在Java中非常頻繁
4.判斷對象是否存活
1)引用計數算法(Reference Counting)
-
含義:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器值為0 的對象就是不可能再被使用的;
-
缺陷:很難解決對象之間相互循環引用的問題。
2)可達性分析算法(Reachability Analysis)
-
含義:通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。
-
在Java語言中,可作為GC Roots的對象包括以下幾種:
-
虛擬機棧(棧幀中的本地變量表)中引用的對象;
-
方法區中類靜態屬性引用的對象;
-
方法區中常量引用的對象;
-
本地方法棧中Native方法引用的對象。
-
-
一個對象真正的死亡,至少要經過兩次標記過程:如果對象在進行可達性分析后發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記并且進行一次篩選,篩選的條件是【此對象是否有必要執行finalize()方法】。當對象沒有覆蓋finalize()方法,或著finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況都視為“沒有必要執行”。如果這個對象被判定為有必要執行finalize()方法,那么這個對象將會放置在一個叫做F-Queue的隊列中,并在稍后由一個虛擬機自動建立的,低優先級的Finalizer線程去執行它。finalize()方法是對象逃脫死亡的最后一次機會,稍后GC將會對F-Queue中的對象進行第二次標記,如果對象要在finalize()中成功拯救自己——只要重新與應用鏈上的任何一個對象建立關聯即可,那在第二次標記時它將被移出“即將回收”集合;如果對象沒有逃脫,那就會被回收。
5.回收方法區
-
永久代的垃圾收集主要回收兩部分內容:廢棄常量和無用的類。
-
類需同時滿足以下3個條件才算為“無用的類”:
-
該類所有的實例都已經被回收,也就是在Java堆中不存在該類的任何實例;
-
加載該類的ClassLoader已經被回收;
-
該類對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法。
-
6.垃圾收集算法
1)標記-清除算法(Mark-Sweep)
- 含義:分為“標記”和“清除”兩個階段,首先標記所有需要回收的對象,在標記完成后統一回收所有被標記的對象.
- 缺陷:
- 效率問題:標記和清除兩個過程的效率都不高;
- 空間問題:標記清除后會產生大量不連續的內存碎片,空間碎片太多可能會導致以后在程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不觸發另一次垃圾收集動作。
2) 復制算法(Copying)
-
含義:為了解決效率問題,它將可用的內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完時,就將還存活的對象復制到另一塊上面,然后再把已使用過的內存空間一次清理掉。這樣使得每次都是對整半個區進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可。
-
缺陷:當對象存活率較高時就要進行較多的復制操作,效率將會降低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不直接選用復制算法
-
常用來回收新生代。
-
將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活的對象一次性復制到另外一塊Survivor空間上,最后清理掉Eden和剛才使用過的的Survivor空間。
-
HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,當Survivor空間不足時,需要依賴老年代進行分配擔保(Handle Promotion)。如果另一塊Survivor空間沒有足夠空間存放上一次新生代收集下來的存活對象時,這些對象將直接通過分配擔保機制進入老年代。
3)標記-整理算法(Mark-Compact)
-
含義:標記過程和“標記-清除”算法一樣,而清除過程是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存。
-
常用來回收老年代。
4)分代收集算法(Generational Collection)
- 含義:根據對象存活周期的不同將內存劃分為幾塊,一般是把Java堆劃分為新生代和老年代。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,選用復制算法回收; 老年代中,因為對象的存活率高,沒有額外的空間對其進行分配擔保,就選用**“標記-清理”或者“標記-整理”算法**進行回收。
7.垃圾收集器
-
并行:指多條垃圾收集線程并行工作,但此時用戶線程仍然處于等待狀態。
-
并發:指用戶線程與垃圾收集線程同時執行(但不一定是并行的可能會交替執行),用戶線程在繼續執行,而垃圾收集線程運行在另一個CPU上。
1)Serial收集器
-
單線程收集器,“單線程”的意義不僅僅說明只會使用一個CPU或一條縣城收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集完成。
-
是虛擬機運行在Client模式下的默認新生代收集器。
-
優點 :簡單而高效(相較于其他收集器的單線程),對于限定單個CPU的環境來說,Serial收集器由于沒有線程交互的開銷,可以獲得最高的單線程收集效率。
2)ParNew收集器
-
Serial收集器的多線程版本,除了使用多線程進行垃圾收集以外,其余行為包括Serial收集器可用的控制參數、收集算法、Stop the World、對象分配原則、回收策略等都與Serial收集器一樣。
-
是虛擬機運行在Server模式下的首選新生代收集器,除了Serial收集器外,只能與CMS收集器配合使用 。
-
默認開啟的收集線程數與CPU的數量相同,在CPU非常多的環境下,可以使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數。
3)Parallel Scavenge收集器
-
新生代收集器(使用復制算法),并行的多線程收集器。
-
提供兩個參數用于控制吞吐量,分別是控制最大垃圾收集停頓時間
4)Serial Old收集器
- Serial收集器的老年代版本,單線程收集器,使用‘標記-整理’算法,給Client模式下的虛擬機使用。
5) Parallel Old收集器
- Parallel Scavenge收集器的老年代版本,多線程收集器,使用‘標記-整理’算法
6) CMS收集器
-
并發收集器,使用‘標記-清除’算法。
-
運作分為四個步驟:
- 初始標記(initial mark)
- 僅僅只標記GC Roots能直接關聯到的對象
- 并發標記(concurrent mark)
- 進行GC Roots Traces的過程
- 重新標記(remark)
- 修正并發標記期間因用戶線程繼續運作而導致標記產生變動的那一部分對象的標記記錄
- 并發清除(concurrent sweep)
- 初始標記(initial mark)
7)G1收集器
-
面向服務端的垃圾收集器
-
具備的特點如下:
- 并行和并發
- 分代收集
- 空間整合
- 從整體上是基于“標記-整理”算法實現的收集器,從局部(兩個Region之間)來看是基于“復制‘算法”實現的。這兩種算法意味著G1運行期間不會產生內存空間碎片,收集后能提供規整的可用內存,這種特性在分配大對象時不會因為無法找到連續內存空間而提前出發下一次GC 。
- 可預測的停頓
- 降低停頓時間是G1和CMS共同的關注點,但G1除了停頓時間外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。
總結
以上是生活随笔為你收集整理的JVM 垃圾收集算法及垃圾收集器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: echarts:基于上一篇我来给大家讲讲
- 下一篇: 父爱无声