目錄
一、JVM內存區域劃分
?二、從一個基本問題開始引入垃圾回收
三、GC作用的區域
三、如何確定一個對象是否可以被當成垃圾進行回收
(1)引用計數法
(2)可達性分析算法
(3)引用的類型
(3.1)強引用(Strong Reference)
(3.2)軟引用(Soft Reference)
(3.3)弱引用(Weak Reference)
(3.4)虛引用(Phantom Reference)
(3.5)終結器引用(Final Reference)
(4)垃圾回收器的大致工作流程
四、垃圾回收算法
(1)標記 - 清除算法(Mark Sweep)
(2)標記 - 整理算法(Mark Compact)
(3)復制算法
五、堆的不同區域(分代垃圾回收)
六、堆的不同區域使用不同的垃圾回收算法
七、垃圾回收器
1. 有哪些垃圾回收器
2. CMS垃圾回收器和G1垃圾回收器
八、其它一些說明
JVM相關參數
一、JVM內存區域劃分
?二、從一個基本問題開始引入垃圾回收
????????從一個基本問題思考,既然要進行垃圾回收,那么可定要從某一個區域(內存區域)進行回收(即,把內存區域中的某些對象當成垃圾,把這些垃圾對象占用的內存區域回收回來,從而釋放內存,避免這個區域的內存被占滿)。
? ? ? ? 通過以上一個基本的問題出發,我們可以明確如下幾個問題:
(1)在哪個區域上進行垃圾回收(換句話說:JVM的哪個區域可以執行垃圾回收這個動作);
(2)在這個可以執行垃圾回收的區域中,如何識別出哪些對象是垃圾;
(3)確定了哪些對象可以被當成垃圾進行回收,那么應該使用什么算法進行回收;
(4)針對這個可以執行垃圾回收的區域,此區域又可以分為哪些不同的小區域,各個小區域應該使用哪種垃圾回收算法;
? ? ? ? 針對以上幾個問題,下面逐一進行說明:
三、GC作用的區域
? ? ? ?JVM(Java虛擬機)的垃圾回收主要是針對堆內存進行的。堆內存是用來存儲對象實例的地方,而垃圾回收的主要任務就是識別并清除不再被任何活躍對象引用的對象,從而釋放它們占用的內存空間。因此,垃圾回收器主要關注的是堆內存中的對象,以確保內存的有效利用和系統性能的提高。
三、如何確定一個對象是否可以被當成垃圾進行回收
? ? ? ? 既然已經知道了,垃圾回收是針對堆內存的,那么如何判斷堆內存中的哪些對象可以被當成垃圾進行回收呢?有如下幾種方法:
(1)引用計數法
????????每個對象都會有一個與之關聯的引用計數器,用來記錄指向該對象的引用數量;當一個對象被引用時,其引用計數加一;當一個對象的引用被釋放時,其引用計數減一。當引用計數為0時,表示沒有任何引用指向該對象,即可被視為垃圾對象,可以被回收。
? ? ? ? 盡管引用計數法簡單直觀,但也存在一些問題,例如無法處理循環引用的情況(如:當兩個或多個對象互相引用導致引用計數不為零)。由于這一缺陷,現代的Java虛擬機一般不再使用引用計數法作為判斷對象可以是否被當成垃圾的依據,而是采用基于可達性分析的算法來判斷對象是否可以被當成垃圾進行回收。
(2)可達性分析算法
? ? ? ? Java虛擬機中的垃圾回收器采用可達性分析算法來探索所有存活的對象。問題來了,如何進行探索呢?
? ? ? ? 是通過掃描堆中的對象,看是否能夠沿著 GC Root 對象為起點的引用鏈找到該對象,找不到,表示可以回收;但是問題又來了,哪些對象可以作為 GC Root對象呢?
? ? ? ? 可以使用 Eclipse(沒錯,就是哪個被IDEA打趴下的Eclipse)提供的一個工具(Memory Analyzer (MAT))進行查看(這個工具需要結合 JDK提供的 jmpa 工具進行使用),MAT分析工具官方下載鏈接:Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation
(3)引用的類型
? ? ? ? 上面提到了<引用>,在Java中,有如下幾種引用類型:
(3.1)強引用(Strong Reference)
????????強引用是最常見的引用類型,在代碼中使用最頻繁。當我們使用new
關鍵字創建一個對象時,默認就是強引用。只要強引用存在,垃圾回收器就不會回收被引用的對象。即使內存不足時,系統也會拋出OutOfMemoryError
異常而不是回收強引用對象。
? ? ? ? 【只有所有GC Roots 對象都不通過【強引用】引用該對象后,該對象才能被垃圾回收】
(3.2)軟引用(Soft Reference)
????????軟引用是一種相對強引用較弱的引用類型。通過SoftReference
類來實現。當內存不足時,Java虛擬機會根據一定的策略來決定是否回收軟引用對象。通常情況下,只有在內存不足且沒有強引用指向該對象時,才會回收軟引用對象。軟引用適用于對內存敏感的緩存等場景。
? ? ? ? 【僅有軟引用引用該對象時,在垃圾回收后,內存仍不足時會再次觸發垃圾回收,回收軟引用對象】
????????【可以配合引用隊列來釋放軟引用自身】
(3.3)弱引用(Weak Reference)
????????弱引用(Weak Reference):弱引用是一種比軟引用更弱的引用類型。通過WeakReference
類來實現。當垃圾回收器進行回收時,無論內存是否充足,只要弱引用對象沒有被強引用指向,就會被回收。弱引用常用于實現對象注冊表、緩存等場景。
? ? ? ? 【僅有弱引用引用該對象時,在垃圾回收時,無論內存是否充足,都會回收弱引用對象】
? ? ? ? 【可以配合引用隊列來釋放弱引用自身】
(3.4)虛引用(Phantom Reference)
????????虛引用是最弱的引用類型之一。通過PhantomReference
類來實現。虛引用的作用是在對象被回收之前,允許程序員在對象被回收時收到一個系統通知。虛引用無法通過引用訪問對象,而是通過ReferenceQueue
來獲取相關通知。虛引用常與引用隊列(ReferenceQueue)一起使用,用于某些特定的清理操作。
? ? ? ? 【必須配合引用隊列使用,主要配合ByteBuffer使用,被引用對象回收時,會將虛引用入隊,由Reference Handler 線程調用虛引用相關方法釋放直接內存】
(3.5)終結器引用(Final Reference)
????????終結器引用是一種比較特殊的引用類型。當對象具有終結器(Finalizer)時,它會被分配給一個終結器引用。終結器引用的主要作用是在對象銷毀前,通過執行終結器方法進行資源釋放和清理。然而,終結器的使用已不推薦,因為它們具有不確定性和性能問題,應盡量避免使用。
? ? ? ? 【無需手動編碼,但其內部配合引用隊列使用,在垃圾回收時,終結器引用入隊(被引用對象暫時沒有被回收),再由Finalizer線程通過終結器引用找到被引用對象并調用它的finalize方法,第二次GC時才能回收被引用對象】
(4)垃圾回收器的大致工作流程
(1)根搜索:垃圾回收器會從一組稱為"GC Roots"的起始點開始遍歷,例如虛擬機棧中的引用、靜態變量等。任何能從GC Roots開始遍歷到的對象都被認為是活躍對象,不會被回收;
(2)可達性分析:從GC Roots出發,遍歷堆中的對象圖,標記所有被引用的對象為活躍對象;
(3)清除階段:遍歷整個堆,將未被標記為活躍對象的對象標記為垃圾,并進行回收。
? ? ? ? 注意:JVM使用了不同的垃圾回收算法,例如標記-清除、標記-整理和復制算法等。這些算法的具體實現細節可能會有所不同,但基本的判斷原則都是通過可達性分析來確定對象是否可回收。
????????此外,Java還提供了finalize()
方法,允許對象在被回收之前執行特定的清理操作。但是,由于finalize()
方法的執行時機不確定且開銷較大,因此在實際應用中,建議使用顯式的資源釋放方式,如使用try-with-resources
語句塊來確保及時釋放資源。
四、垃圾回收算法
? ? ? ? 既然確定了哪些對象可以被當作垃圾進行回收,那么應該使用怎樣的方法進行垃圾回收呢?換句話說,使用什么垃圾回收算法呢?
(1)標記 - 清除算法(Mark Sweep)
示意圖:
標記階段(Marking Phase)
????????從根對象(如全局變量和活躍線程的棧和寄存器)開始,通過遍歷對象之間的引用關系,標記所有能夠被訪問到的對象。在這個階段,所有被標記的對象被視為活動對象,而未被標記的對象則被視為垃圾對象。?
清除階段(Sweeping Phase)
????????標記階段之后,系統會對堆中的所有對象進行線性遍歷,清除未被標記的對象,并將它們所占用的內存空間進行釋放。這樣就完成了對垃圾對象的回收工作。
優點:
- 簡單直觀,能夠有效地回收不再使用的內存對象
缺點:
- 內存碎片化問題和回收效率問題(由于清除階段釋放了大量的內存空間,留下了不連續的內存碎片,可能會導致內存分配時的碎片化問題)
- 此外,標記-清除算法在執行過程中會停止整個應用程序(Stop the world, STW),可能會導致較長的停頓時間,影響了應用程序的響應速度。
????????因此,在實際應用中,往往會結合其他垃圾回收算法,如壓縮算法(Compaction)、分代算法(Generational Collection)等,來解決標記-清除算法存在的問題,以提高內存管理的效率和性能。?
(2)標記 - 整理算法(Mark Compact)
示意圖:
標記-整理算法有如下幾個階段:
標記(Mark)
????????從根對象出發,遍歷整個對象圖,標記所有活躍對象。活躍對象是指仍然被引用的對象,而非垃圾對象。
整理(Compact)
????????將所有活躍對象向內存的一端移動,緊湊排列,以便在后續步驟中形成連續的可用內存空間。
清除(Sweep)
????????從堆的另一端開始,將未被標記的對象視為垃圾,將其回收,并釋放相應的內存空間。
更新引用:
????????在整理過程中,由于對象的位置發生了變化,需要更新所有對對象的引用,確保引用指向正確的內存地址。
優點:
????????標記-整理算法的優點是可以大幅減少內存碎片的產生,提高內存的利用率。
缺點:
????????它需要進行整理和移動對象的操作,可能會引入較大的停頓時間,影響應用程序的響應性能。因此,該算法通常適用于較小的堆內存或對停頓時間要求較低的場景。
(3)復制算法
示意圖:
復制算法有如下幾個步驟:
- 將內存空間分為兩個相等的區域:From區和To區。
- 在From區中分配內存并創建對象。
- 當From區的內存耗盡時,啟動垃圾回收機制。
- 從From區中將存活的對象復制到To區。
- 清空From區中的所有對象,并交換From區和To區的角色。
????????復制算法的優點在于簡單高效,它解決了標記-清除算法和標記-整理算法中會產生的內存碎片問題。但是,由于需要將存活對象復制到另一塊區域,因此復制算法會導致內存利用率降低一半(需要雙倍的內存空間),適用于新生代的內存回收,不適合用于老年代的大規模對象回收。
????????因此,在JVM中,通常會將堆內存劃分為新生代和老年代,新生代主要使用復制算法來進行垃圾回收,而老年代則會采用其他更適合的算法,例如標記-整理算法或標記-清除算法。
五、堆的不同區域(分代垃圾回收)
????????分代回收算法是JVM的一種垃圾回收算法,基于對象的生命周期,將堆內存分為不同的代(Generation),并對不同代采用適合的垃圾回收算法。通常將堆內存劃分為新生代、老年代和持久代(或元數據區),其中新生代又可以進一步劃分為Eden區、Survivor區1和Survivor區2。
????????分代回收算法的主要思想是:大部分對象的生命周期很短,很快就會被回收,而只有少數對象的生命周期很長,需要在堆內存中存活較長時間。因此,我們可以采用不同的垃圾回收算法來針對不同的對象生命周期,以達到更好的性能和效果。
????????在分代回收算法中,新生代通常使用復制算法進行垃圾回收,因為大多數對象的生命周期較短,而老年代則采用標記-整理算法或標記-清除算法,因為老年代中的對象生命周期更長,需要更高效的回收算法。而持久代主要存儲類元數據信息等不會被回收的數據,一般不需要進行垃圾回收。
????????分代回收算法的優點在于根據對象的生命周期采用不同的回收算法,可以更好地平衡垃圾回收的效率和停頓時間(STW),并減少不必要的內存復制和整理操作。但是,分代回收算法需要更多的內存空間來劃分不同的代,而且需要更復雜的垃圾回收機制來管理不同代之間的引用關系,因此在實際應用中需要根據具體情況進行選擇和配置。
以下是學習黑馬視頻-JVM教程截的圖
六、堆的不同區域使用不同的垃圾回收算法
? ? ? ? 通過《五、堆的不同區域(分代垃圾回收)》可知:
- 新生代通常使用復制算法進行垃圾回收,因為大多數對象的生命周期較短;
- 老年代則采用標記-整理算法或標記-清除算法,因為老年代中的對象生命周期更長,需要更高效的回收算法。
- 持久代主要存儲類元數據信息等不會被回收的數據,一般不需要進行垃圾回收。
七、垃圾回收器
Oracle官方文檔:Available Collectors (oracle.com)
1. 有哪些垃圾回收器
?
2. CMS垃圾回收器和G1垃圾回收器
CMS(Concurrent Mark Sweep)垃圾回收器和G1(Garbage-First)垃圾回收器都是Java虛擬機的垃圾回收器,它們在內存管理和垃圾回收策略上有一些區別。
-
并發性能:CMS垃圾回收器是一種以最短停頓時間為目標的垃圾回收器。它使用并發標記和并發清理的方式來實現垃圾回收,可以在垃圾回收過程中與應用程序并發執行,減少停頓時間,提高系統的響應能力。而G1垃圾回收器也具有并發標記和并發清理的特性,但相比CMS,G1進一步改進了并發性能,通過將堆內存劃分為多個小塊(Region),并采用增量式的方式進行垃圾回收,可以更好地控制每次垃圾回收的時間。
-
內存模型:CMS垃圾回收器使用的是分代回收的思想,將堆內存劃分為年輕代和老年代。年輕代使用復制算法進行垃圾回收,老年代使用標記-清除算法。而G1垃圾回收器則是基于分區(Region)的內存模型,將整個堆內存劃分為多個相等大小的區域,并且不再明確區分年輕代和老年代。G1會根據垃圾回收的情況動態地選擇進行回收的區域。
-
碎片整理:CMS垃圾回收器在進行垃圾回收時,不會對整個堆內存進行整理,因此可能會導致堆內存的碎片化問題。而G1垃圾回收器采用了分區的方式,并在垃圾回收過程中進行了部分的碎片整理,可以較好地避免堆內存的碎片化問題。【注意:G1垃圾回收器(1)同時注重吞吐量(Throughput)和低延遲(Low latency),默認的暫停目標是 200 ms;(2)超大堆內存,會將堆劃分為多個大小相等的 Region;(3)整體上是 標記+整理 算法,兩個區域之間是 復制 算法 ;(4)相關JVM參數:< -XX:+UseG1GC >? ?<?-XX:G1HeapRegionSize=size >? <?-XX:MaxGCPauseMillis=time >】
-
停頓時間:CMS垃圾回收器通過并發的方式來減少垃圾回收的停頓時間,但無法完全避免停頓。而G1垃圾回收器則通過控制每次垃圾回收的時間和并發執行的階段,可以更好地控制全局的垃圾回收時間,并且具有更可預測的停頓時間。
綜上所述,CMS垃圾回收器和G1垃圾回收器都是面向低停頓時間的垃圾回收器,但G1相比CMS在并發性能、內存模型、碎片整理和停頓時間上都有進一步的改進和優化。根據具體的應用場景和需求,可以選擇適合的垃圾回收器來進行內存管理。
????????JDK 1.8默認使用的垃圾回收器是ParallelGC,也稱為Parallel Scavenge收集器。這是一種基于標記-復制算法的垃圾回收器,主要針對年輕代進行垃圾回收。ParallelGC使用多個線程并行地進行垃圾回收操作,可以充分利用多核CPU的優勢,以提高垃圾回收的效率。
????????另外,在JDK 1.8中還引入了G1垃圾回收器,它是一種基于分區(Region)的垃圾回收器,相比于ParallelGC具有更好的并發性能和更可預測的停頓時間。但G1不是默認的垃圾回收器,需要通過特定參數進行配置才能啟用。
????????需要注意的是,JDK 1.8中的垃圾回收器并不是固定的,可以通過虛擬機參數進行配置。在實際應用中,需要根據具體的場景和需求來選擇合適的垃圾回收器。