聊聊Java的GC機制

NO IMAGE

作者 某人Valar
如需轉載請保留原文鏈接
部分圖片來自百度,如有侵權請聯繫刪除

本文目錄

  • 什麼是GC
  • JVM內存結構簡單介紹
  • 可達性分析與GC Roots
  • 常見的垃圾收集算法

1. 什麼是GC

GC:垃圾回收(Garbage Collection),在計算機領域就是指當一個計算機上的動態存儲器(內存空間)不再需要時,就應該予以釋放,以讓出存儲器,便於他用。這種存儲器的資源管理,稱為垃圾回收。

有一些語言是沒有垃圾回收機制的,像CC++,如果需要釋放無用變量內存空間就由自己來處理。

而其他的一些語言如JavaC#都支持垃圾回收器,Java虛擬機(JVM)或.NET CLR發現內存資源緊張的時候,就會自動地去清理無用對象(沒有被引用到的對象)所佔用的內存空間。

而我們今天主要講Java中的GC。


上面說到JVM會自動清理無用的對象,那麼我們就有了疑問:

  • JVM清理的是哪一塊的對象?
  • 哪些對象會被清理,為什麼清理A而不清理B?
  • JVM又是如何清理的?

這三個問題將分別對應接下來的3節一一解答。

2. JVM內存結構簡單介紹

我們都知道,Java代碼是要運行在虛擬機上的,而虛擬機在執行Java程序的過程中會把所管理的內存劃分為若干個不同的數據區域,這些區域都有各自的用途。
《Java虛擬機規範(Java SE 8)》中描述了JVM運行時內存區域結構如下:

聊聊Java的GC機制

以上是Java虛擬機規範,不同的虛擬機實現可能會各有不同,但是一般會遵守規範

  • 方法區:存儲已被虛擬機加載的類信息、常量、靜態變量等
  • 堆:堆是Java 虛擬機所管理的內存中最大的一塊。唯一目的就是存放對象實例。在虛擬機棧中存放的只是引用,而引用指向的是堆中的對象。GC主要作用的區域
  • 虛擬機棧:局部變量表、操作數棧等。虛擬機棧描述的是Java 方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用於存儲局部變量表、操作棧、動態鏈接、方法返回地址等信息。
  • 本地方法棧:與虛擬機棧類似,是為native方法提供服務的。
  • 程序計數器:記錄當前線程執行的方法執行到了第幾行。如果線程正在執行的是一個Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址。如果正在執行的是Natvie 方法,這個計數器值則為空(Undefined)。

3.可達性分析與GC Roots

3.1 可達性分析

Java中通過可達性分析法來確定某個對象是不是“垃圾”。

該方法的基本思想是通過一系列的“GC Roots”對象作為起點進行搜索,如果在“GC Roots”和一個對象之間沒有可達路徑,則稱該對象是不可達的,不過要注意的是被判定為不可達的對象不一定就會成為可回收對象。被判定為不可達的對象要成為可回收對象必須至少經歷兩次標記過程,如果在這兩次標記過程中仍然沒有逃脫成為可回收對象的可能性,則基本上就真的成為可回收對象了。

注意其本質是通過找出所有活對象來把其餘空間認定為“無用”,而不是找出所有死掉的對象並回收它們佔用的空間.

如下圖,當object5、6、7不存在到GC Roots的引用時,即不可到達GC Roots,則判定他們是不可達的。

聊聊Java的GC機制

3.2 GC Roots

對於哪些對象可以被當成GC Roots網上有很多種說法,有的不夠權威、有的不夠全面。
最終找到了一份eclipse的官方文檔,裡面有如下的介紹:
Garbage Collection Roots (自己翻譯了一下,如有不準確的地方,請指出)

A garbage collection root is an object that is accessible from outside the heap. The following reasons make an object a GC root:

1. System Class (被boostrap 或者系統類加載器加載的系統類)
Class loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.* .
2. JNI Local( 一些用戶定義jni 代碼或者jvm的內部代碼局部變量)
Local variable in native code, such as user defined JNI code or JVM internal code.
3. JNI Global( jni 代碼中的全局變量)
Global variable in native code, such as user defined JNI code or JVM internal code.
5. Thread Block(被阻塞的線程引用的對象)
Object referred to from a currently active thread block.
6. Thread (正在運行的線程)
A started, but not stopped, thread.
7. Busy Monitor(正在等待的線程)
Everything that has called wait() or notify() or that is synchronized.
For example, by calling synchronized(Object) or by entering a synchronized method. 
Static method means class, non-static method means object.
8. Java Local(仍然在線程的棧中的方法的傳入參數或方法內部創建的對象)
Local variable.
For example, input parameters or locally created objects of methods that are still in the stack of a thread.
9.Native Stack(本地方法棧中輸入或輸出參數,例如,用於文件/網絡I/O的方法或反射的參數。)
In or out parameters in native code, such as user defined JNI code or JVM internal code. 
This is often the case as many methods have native parts and the objects handled as method parameters become GC roots.
For example, parameters used for file/network I/O methods or reflection.
10.Finalizable(在回收隊列中的對象)
An object which is in a queue awaiting its finalizer to be run.
11. Unfinalized(覆蓋了finalize方法但是還沒有被放入回收隊列中的對象)
An object which has a finalize method, but has not been finalized and is not yet on the finalizer queue.
12.Unreachable(一個從任何其他根無法訪問的對象,但由Memory Analyzer Tool 標記為根,以便該對象可以包含在分析中)
An object which is unreachable from any other root, 
but has been marked as a root by MAT to retain objects which otherwise would not be included in the analysis.
13. Java Stack Frame
A Java stack frame, holding local variables.
Only generated when the dump is parsed with the preference set to treat Java stack frames as objects.
14. Unknown
An object of unknown root type. Some dumps, such as IBM Portable Heap Dump files, do not have root information. 
For these dumps the MAT parser marks objects 
which are have no inbound references or are unreachable from any other root as roots of this type. 
This ensures that MAT retains all the objects in the dump.

簡單總結下就是:

  • 由系統類加載器(system class loader)加載的對象
  • 活著的線程,包含處於等待或阻塞的線程
  • 當前被調用的方法(Java方法、native方法)的一些參數/局部變量
  • 方法區中靜態變量、常量引用的對象
  • Held by JVM – JVM由於特殊目的為GC保留的對象,但實際上這個與JVM的實現是有關的。可能已知的一些類型是:系統類加載器、一些JVM知道的重要的異常類、一些用於處理異常的預分配對象以及一些自定義的類加載器等。

4. 常見的垃圾回收算法

4.1 Mark-Sweep(標記-清除)算法

這是最基礎的垃圾回收算法,之所以說它是最基礎的是因為它最容易實現,思想也是最簡單的。標記-清除算法分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所佔用的空間。具體過程如下圖所示:

聊聊Java的GC機制

優缺點:

  • 從圖中可以很容易看出標記-清除算法實現起來比較容易
  • 但是有一個比較嚴重的問題就是容易產生內存碎片,碎片太多可能會導致後續過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發新的一次垃圾收集動作。
4.2 Copying(複製)算法

為了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來。它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象複製到另外一塊上面,然後再把已使用的內存空間一次清理掉,這樣一來就不容易出現內存碎片的問題。具體過程如下圖所示:

聊聊Java的GC機制

優缺點:

  • 這種算法實現簡單,運行高效且不容易產生內存碎片
  • 但是卻對內存空間的使用做出了高昂的代價,因為能夠使用的內存縮減到原來的一半。
4.3 Mark-Compact(標記-整理)算法

該算法標記階段和Mark-Sweep一樣,但是在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的內存。具體過程如下圖所示:

聊聊Java的GC機制

優缺點:

  • 標記-整理算法是在標記-清除算法的基礎上,又進行了對象的移動,因此成本更高
  • 但是卻解決了內存碎片的問題。
4.4 Generational Collection(分代收集)算法

分代收集算法是目前大部分JVM的垃圾收集器採用的算法。它的核心思想是根據對象存活的生命週期將內存劃分為若干個不同的區域
一般情況下將堆區劃分為老年代(Tenured Generation)和新生代(Young Generation),在HotSpot中,設計者將方法區納入也納入了GC分代收集,併為其起了一個名字,永久代(PerGen space)

  • 老年代:特點是每次垃圾收集時只有少量對象需要被回收,一般使用的是標記-整理(Mark-Compact)標記-清除(Mark-Sweep)算法。
  • 新生代:特點是每次垃圾回收時都有大量的對象需要被回收,對於新生代都採取複製(Copying)算法。

因為新生代中每次垃圾回收都要回收大部分對象,也就是說需要複製的操作次數較少,但是實際中並不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分為一塊較大的Eden空間(伊甸園,亞當和夏娃偷吃禁果生娃娃的地方,用來表示內存首次分配的區域,再貼切不過)和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象複製到另一塊Survivor空間中,然後清理掉Eden和剛才使用過的Survivor空間。一般來說分配比例為eden 80%、survivor1 10%、survivor2 10%。

  • 永久代:
    方法區和永久代的關係很像Java中接口和類的關係,類實現了接口,而永久代就是HotSpot虛擬機對虛擬機規範中方法區的一種實現方式。

在方法區進行垃圾回收一般”性價比”較低, 因為在方法區主要回收兩部分內容: 廢棄常量和無用的類。 回收廢棄常量與回收其他年代中的對象類似, 但要判斷一個類是否無用需要以下條件:

  1. 該類所有的實例都已經被回收, Java堆中不存在該類的任何實例;
  2. 該類對應的Class對象沒有在任何地方被引用(也就是在任何地方都無法通過反射訪問該類的方法);
  3. 加載該類的ClassLoader已經被回收。

但即使滿足以上條件也未必一定會回收, Hotspot VM還提供了-Xnoclassgc參數控制(關閉CLASS的垃圾回收功能).

參考:cloud.tencent.com/developer/a…

附:HotSpot虛擬機在1.8之後已經取消了永久代,改為元空間,類的元信息被存儲在元空間中,元空間這裡不過多介紹,有興趣的同學可以自己瞭解下。

5. 結語

到此關於Java GC的基本概念、JVM的內存結構、GC回收基本機制都已經介紹的差不多了。有疑問或者發現文章中有錯誤的可以在下方留言交流。像Java垃圾收集器、元空間這裡並沒有詳細的說明,之後有時間的話會單獨拿出來聊聊。

相關文章

左邊敲打IDE!右邊出現了一個世界!!!

聊聊短地址及其原理

KubernetesControllerManager工作原理

懶癌晚期患者的福音到了,高大上的Kotlin序列化插件來了!