【JVM基礎篇】Java的四種垃圾回收算法介紹

文章目錄

  • 垃圾回收算法
    • 垃圾回收算法的歷史和分類
    • 垃圾回收算法的評價標準
    • 標記清除算法
      • 優缺點
    • 復制算法
      • 優缺點
    • 標記整理算法(標記壓縮算法)
      • 優缺點
    • 分代垃圾回收算法(常用)
      • JVM參數設置
      • 使用Arthas查看內存分區
      • 垃圾回收執行流程
      • 分代GC算法內存為什么分年輕代、老年代
    • 文章說明

垃圾回收算法

Java是如何實現垃圾回收的呢?簡單來說,垃圾回收算法要做的有兩件事:

  1. 找到內存中存活的對象
  2. 釋放不再存活對象的內存,使得程序能再次利用這部分空間

在這里插入圖片描述

垃圾回收算法的歷史和分類

  • 1960年John McCarthy發布了第一個GC算法:標記-清除算法。
  • 1963年Marvin L. Minsky 發布了復制算法。

本質上后續所有的垃圾回收算法,都是在上述兩種算法的基礎上優化而來。

在這里插入圖片描述

垃圾回收算法的評價標準

Java垃圾回收過程會通過單獨的GC線程來完成,但是不管使用哪一種GC算法,都會有部分階段需要停止所有的用戶線程。這個過程被稱之為Stop The World簡稱STW,如果STW時間過長(系統假死)則會影響用戶的使用。

如下圖,用戶代碼執行和垃圾回收執行讓用戶線程停止執行(STW)是交替執行的。

在這里插入圖片描述

交替執行過程可以通過如下代碼驗證:

package chapter04.gc;import lombok.SneakyThrows;import java.util.LinkedList;
import java.util.List;/*** STW測試*/
public class StopWorldTest {public static void main(String[] args) {new PrintThread().start();new ObjectThread().start();}
}/*** 打印線程*/
class PrintThread extends Thread{@SneakyThrows@Overridepublic void run() {//記錄開始時間long last = System.currentTimeMillis();while(true){long now = System.currentTimeMillis();// 如果不是垃圾回收影響,這里每次都應該是輸出100System.out.println(now - last);last = now;Thread.sleep(100);}}
}/*** 創建對象線程*/
class ObjectThread extends Thread{@SneakyThrows@Overridepublic void run() {List<byte[]> bytes = new LinkedList<>();while(true){// 最多存放8g,然后刪除強引用,垃圾回收時釋放8gif(bytes.size() >= 80){// 清空集合,強引用去除,垃圾回收器就會去回收對象bytes.clear();}bytes.add(new byte[1024 * 1024 * 100]);Thread.sleep(10);}}
}

代碼運行之前,設置如下JVM參數

在這里插入圖片描述

在這里插入圖片描述

所以判斷GC算法是否優秀,可以從三個方面來考慮

  1. 吞吐量

吞吐量指的是 CPU 用于執行用戶代碼的時間與 CPU 總執行時間的比值,即吞吐量 = 執行用戶代碼時間 /(執行用戶代碼時間 + GC時間)。吞吐量數值越高,垃圾回收的效率就越高

在這里插入圖片描述

  1. 最大暫停時間

最大暫停時間指的是所有在垃圾回收過程中的STW時間最大值。比如如下的圖中,黃色部分的STW就是最大暫停時間,顯而易見上面的圖比下面的圖擁有更少的最大暫停時間。最大暫停時間越短,用戶使用系統時受到的影響就越短。

在這里插入圖片描述

  1. 堆使用效率

不同垃圾回收算法,對堆內存的使用方式是不同的。比如標記清除算法,可以使用完整的堆內存。而復制算法會將堆內存一分為二,每次只能使用一半內存。從堆使用效率上來說,標記清除算法要優于復制算法。

在這里插入圖片描述

上述三種評價標準:堆使用效率、吞吐量,以及最大暫停時間不可兼得

一般來說,堆內存越大,回收對象就越多,最大暫停時間就越長。想要減少最大暫停時間,就要減少堆內存,少量多次,因為每次清理有一些準備工作,因此垃圾回收總時間會上升,吞吐量會降低。

沒有一個垃圾回收算法能兼顧上述三點評價標準,所以不同的垃圾回收算法它的側重點是不同的,適用于不同的應用場景(即垃圾回收算法沒有好與壞,只有是否適合

  • 秒殺場景,購買只有很少的時間,最大暫停時間越短越好
  • 有的場景,程序就在后臺處理數據,暫停時間長一點無所謂,目標是吞吐量高一點

標記清除算法

標記清除算法的核心思想分為兩個階段:

  1. 標記階段,將所有存活的對象進行標記。Java中使用可達性分析算法,從GC Root開始通過引用鏈遍歷出所有存活對象。
  2. 清除階段,從內存中刪除沒有被標記也就是非存活對象

第一個階段,從GC Root對象開始掃描,將對象A、B、C在引用鏈上的對象標記出來:

在這里插入圖片描述

第二個階段,將沒有標記的對象清理掉,所以對象D就被清理掉了。

在這里插入圖片描述

優缺點

優點:實現簡單,只需要在第一階段給每個對象維護標志位(在引用鏈上,標記為1),第二階段刪除標記值為0的對象即可。

缺點

  1. 碎片化問題:由于內存是連續的,所以在對象被刪除之后,內存中會出現很多細小的可用內存單元。如果我們需要的是一個比較大的空間,很有可能這些內存單元的大小過小無法進行分配。如下圖,紅色部分已經被清理掉了,總共回收了9個字節,但是每個都是一個小碎片,無法為5個字節的對象分配空間。

在這里插入圖片描述

  1. 分配速度慢。由于內存碎片的存在,需要維護一個空閑鏈表,極有可能發生每次需要遍歷到鏈表的最后才能獲得合適的內存空間。我們需要用一個鏈表來維護,哪些空間可以分配對象,很有可能需要遍歷這個鏈表到最后,才能發現這塊空間足夠我們去創建一個對象。如下圖,遍歷到最后才發現有足夠的空間分配3個字節的對象了。如果鏈表很長,遍歷也會花費較長的時間。

在這里插入圖片描述

復制算法

復制算法的核心思想是:

  1. 準備兩塊空間From空間和To空間,每次在對象分配階段,只能使用其中一塊空間(From空間)。

對象A首先分配在From空間:

在這里插入圖片描述

  1. 在垃圾回收GC階段,將From中的存活對象復制到To空間。

在垃圾回收階段,如果對象A存活,就將其復制到To空間。然后將From空間直接清空。

在這里插入圖片描述

  1. 將兩塊空間的From和To名字互換,下次依然在From空間上創建對象。

在這里插入圖片描述

完整的復制算法的例子:

1、將堆內存分割成兩塊From空間 To空間,對象分配階段,創建對象。

在這里插入圖片描述

2、GC階段開始,將GC Root搬運到To空間

在這里插入圖片描述

3、將GC Root關聯的對象,搬運到To空間

在這里插入圖片描述

4、清理From空間,并把名稱互換

在這里插入圖片描述

優缺點

優點

  • 吞吐量高,復制算法只需要遍歷一次存活對象復制到To空間即可,比標記-整理算法少了一次遍歷的過程,因而性能較好;但是性能不如標記-清除算法,因為標記清除算法不需要進行對象的移動
  • 不會發生碎片化,復制算法在復制之后就會將對象按順序放入To空間中,所以對象以外的區域都是可用空間,不存在碎片化內存空間。

缺點

  • 內存使用效率低,每次只能讓一半的內存空間來給創建對象使用。

標記整理算法(標記壓縮算法)

標記整理算法是對標記清理算法中容易產生內存碎片問題的一種解決方案

核心思想分為兩個階段:

  1. 標記階段,將所有存活的對象進行標記。Java中使用可達性分析算法,從GC Root開始通過引用鏈遍歷出所有存活對象。
  2. 整理階段,將存活對象移動到堆的一端。清理掉存活對象的內存空間。

在這里插入圖片描述

優缺點

優點:

  • 內存使用效率高,整個堆內存都可以使用,不像復制算法只能使用半個堆內存
  • 不會發生碎片化,在整理階段可以將對象往內存的一側進行移動,剩下的空間都是可以分配對象的有效空間

缺點:

  • 整理階段的效率不高,需要遍歷多次對象,還需要移動對象。整理算法有很多種,比如Lisp2整理算法需要對整個堆中的對象搜索3次,整體性能不佳。可以通過Two-Finger、表格算法、ImmixGC等高效的整理算法優化此階段的性能。

分代垃圾回收算法(常用)

現代優秀的垃圾回收算法,會將上述描述的垃圾回收算法組合進行使用,其中應用最廣的就是分代垃圾回收算法(Generational GC)。分代垃圾回收將整個內存區域劃分為兩塊大區:年輕代、老年代:

在這里插入圖片描述

  • Eden區:對象剛被創建出來的時候放到的地方
  • 幸存者區-S0、幸存者區-S1:用來實現復制算法

可以通過arthas來驗證下內存劃分的情況:

  • 在JDK8中,添加-XX:+UseSerialGC參數使用分代回收的垃圾回收器,運行程序。
  • 在arthas中使用memory命令查看內存,顯示出三個區域的內存情況。

在這里插入圖片描述

  • Eden + survivor 這兩塊區域組成了年輕代。
  • tenured_gen指的是晉升區域,其實就是老年代。

JVM參數設置

可以設置的虛擬機參數如下

參數名參數含義示例
-Xms設置堆的最小和初始大小,必須是1024倍數且大于1MB比如初始大小6MB的寫法: -Xms6291456 -Xms6144k -Xms6m
-Xmx設置最大堆的大小,必須是1024倍數且大于2MB比如最大堆80 MB的寫法: -Xmx83886080 -Xmx81920k -Xmx80m
-Xmn新生代的大小新生代256 MB的寫法: -Xmn256m -Xmn262144k -Xmn268435456
-XX:SurvivorRatio伊甸園區和幸存區的比例,默認為8:如新生代有1g內存,則伊甸園區800MB,S0和S1各100MB比例調整為4的寫法:-XX:SurvivorRatio=4
-XX:+PrintGCDetailsverbose:gc打印GC日志

老年代大小不需要設置,因為新生代設置完之后,老年代的大小就確定了(總的堆內存-新生代內存)

:如果使用其他版本的JDK,或者使用其他回收器,上面的部分參數可能就不會生效

使用Arthas查看內存分區

代碼:

package chapter04.gc;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** 垃圾回收器案例1*/
//-XX:+UseSerialGC  -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio=3  -XX:+PrintGCDetails
public class GcDemo0 {public static void main(String[] args) throws IOException {List<Object> list = new ArrayList<>();int count = 0;while (true){System.in.read();System.out.println(++count);//每次添加1m的數據list.add(new byte[1024 * 1024 * 1]);}}
}

使用arthas的memory展示出來的效果:

在這里插入圖片描述

heap展示的是可用堆。

垃圾回收執行流程

1、分代回收時,創建出來的對象,首先會被放入Eden伊甸園區。

在這里插入圖片描述

2、隨著對象在Eden區越來越多,如果Eden區滿,新創建的對象已經無法放入,就會觸發年輕代的GC,稱為Minor GC或者Young GC。Minor GC會把需要eden中和From需要回收的對象回收,把沒有回收的對象放入To區(算法使用的是復制算法)。Minor GC結束之后**,**Eden區會被清空,后面創建的對象又可以放到Eden區。

在這里插入圖片描述

3、接下來,S0會變成To區,S1變成From區。當eden區滿時再往里放入對象,依然會發生Minor GC。

在這里插入圖片描述

此時會回收eden區和S1(from)中的對象,并把eden和from區中存活的對象放入S0。

注意:每次Minor GC中都會為對象記錄他的年齡,初始值為0,每次GC完加1。

在這里插入圖片描述

4、如果Minor GC后對象的年齡達到閾值(最大15,默認值和垃圾回收器有關),對象就會被晉升至老年代

在這里插入圖片描述

在這里插入圖片描述

5、當老年代中空間不足,無法放入新的對象時,先嘗試minor gc(為啥?**因為young滿了之后,部分對象年齡沒有到15,也被放在了老年區,**minor gc可以清理young區來放新對象)。如果空間還是不足,就會觸發Full GC(停頓時間較長),Full GC會對整個堆進行垃圾回收。如果Full GC依然無法回收掉老年代的對象,那么當對象繼續放入老年代時,就會拋出Out Of Memory異常。

在這里插入圖片描述

下圖中的程序為什么會出現OutOfMemory?

在這里插入圖片描述

從上圖可以看到,Full GC無法回收掉老年代的對象,那么當對象繼續放入老年代時,就會拋出Out Of Memory異常。

【測試代碼】

//-XX:+UseSerialGC  -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio=3  -XX:+PrintGCDetails
public class GcDemo0 {public static void main(String[] args) throws IOException {List<Object> list = new ArrayList<>();int count = 0;while (true){System.in.read();System.out.println(++count);//每次添加1m的數據list.add(new byte[1024 * 1024 * 1]);}}
}

結果如下:

在這里插入圖片描述

老年代已經滿了,而且垃圾回收無法回收掉對象,如果還想往里面放就發生了OutOfMemoryError

在這里插入圖片描述

分代GC算法內存為什么分年輕代、老年代

為什么分代GC算法要把堆分成年輕代和老年代?首先我們要知道堆內存中對象的特性:

  • 系統中的大部分對象,都是創建出來之后很快就不再使用可以被回收,比如用戶獲取訂單數據,訂單數據返回給用戶之后就可以釋放了。
  • 老年代中會存放長期存活的對象,比如Spring的大部分bean對象,在程序啟動之后就不會被回收了。
  • 在虛擬機的默認設置中,新生代大小要遠小于老年代的大小。

分代GC算法將堆分成年輕代和老年代主要原因有

  • 可以通過調整年輕代和老年代的比例來適應不同類型的應用程序,提高內存的利用率和性能。
  • 新生代和老年代使用不同的垃圾回收算法,新生代一般選擇復制算法;老年代可以選擇標記-清除標記-整理算法,由程序員來選擇靈活度較高。
  • 分代的設計中允許只回收新生代(minor gc),如果能滿足對象分配的要求就不需要對整個堆進行回收(full gc),STW時間就會減少。(盡可能做minor gc,少做full gc,盡量降低垃圾回收對程序運行的影響)

文章說明

該文章是本人學習 黑馬程序員 的學習筆記,文章中大部分內容來源于 黑馬程序員 的視頻黑馬程序員JVM虛擬機入門到實戰全套視頻教程,java大廠面試必會的jvm一套搞定(豐富的實戰案例及最熱面試題),也有部分內容來自于自己的思考,發布文章是想幫助其他學習的人更方便地整理自己的筆記或者直接通過文章學習相關知識,如有侵權請聯系刪除,最后對 黑馬程序員 的優質課程表示感謝。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/42197.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/42197.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/42197.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【SpringBoot】IDEA查看spring bean的依賴關系

前因&#xff1a;在研究springcloud config組件時&#xff0c;我發現config-server包下的EnvironmentController可以響應客戶端的請求&#xff0c;但EnvironmentController并不在啟動類所在的包路徑下&#xff0c;所以我推測它是作為某個Bean方法在生效&#xff0c;尋找bean的依…

vue-router 源碼分析——9.別名

這是對vue-router 3 版本的源碼分析。 本次分析會按以下方法進行&#xff1a; 按官網的使用文檔順序&#xff0c;圍繞著某一功能點進行分析。這樣不僅能學習優秀的項目源碼&#xff0c;更能加深對項目的某個功能是如何實現的理解。這個對自己的技能提升&#xff0c;甚至面試時…

DAY1: 實習前期準備

文章目錄 VS Code安裝的插件C/CCMakeGitHub CopilotRemote-SSH收獲 VS Code 下載鏈接&#xff1a;https://code.visualstudio.com 安裝的插件 C/C 是什么&#xff1a;C/C IntelliSense, debugging, and code browsing. 為什么&#xff1a;初步了解如何在VS Code里使用C輸出…

https創建證書

需要下載httpd模塊&#xff1a;yum install httpd -y 前提需要先搭建一個虛擬主機來測試證書創建的效果&#xff0c;以下面www.hehe.com為例&#xff0c;可以參考創建&#xff1a; [rootlocalhost conf.d]# vim vhost.conf <directory /www> allowoverride none requi…

關于小愛同學自定義指令執行

1.前言 之前買了小愛同學音響&#xff0c;一直想讓其讓我的生活變得更智能&#xff0c;編寫一些程序來完成一些自動化任務&#xff0c;但是經過搜索發現&#xff0c;官方開發者平臺不能用了&#xff0c;尋找api階段浪費了我很長時間。最后在github 開源項目發現了倆個比較關鍵…

13.SQL注入-寬字節

SQL注入-寬字節 含義&#xff1a; MySQL是用的PHP語言&#xff0c;然后PHP有addslashes()等函數&#xff0c;這類函數會自動過濾 ’ ‘’ null 等這些敏感字符&#xff0c;將它們轉義成’ ‘’ \null&#xff1b;然后寬字節字符集比如GBK它會自動把兩個字節的字符識別為一個漢…

內容營銷專家劉鑫煒:網站排名需考慮哪些SEO優化技巧?

網站排名的SEO優化技巧包括&#xff1a; 1. 關鍵詞研究&#xff1a;了解目標受眾的搜索關鍵詞&#xff0c;將這些關鍵詞合理地應用在網站的標題、描述、正文和標簽中&#xff0c;有助于提高網站排名。 2. 內容優化&#xff1a;創建高質量、有價值的內容&#xff0c;可以吸引搜…

Qt源碼解析之QObject

省去大部分virtual和public方法后&#xff0c;Qobject主要剩下以下成員&#xff1a; //qobject.h class Q_CORE_EXPORT Qobject{Q_OBJECTQ_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)Q_DECLARE_PRIVATE(QObject) public:Q_I…

STM32-OC輸出比較和PWM

本內容基于江協科技STM32視頻內容&#xff0c;整理而得。 文章目錄 1. OC輸出比較和PWM1.1 OC輸出比較1.2 PWM&#xff08;脈沖寬度調制&#xff09;1.3 輸出比較通道&#xff08;高級&#xff09;1.4 輸出比較通道&#xff08;通用&#xff09;1.5 輸出比較模式1.6 PWM基本結…

MATLAB常用語句總結7

MATLAB總結7&#xff1a;常見錯誤歸納 本篇專門用于記錄一些應試技巧 文章目錄 MATLAB總結7&#xff1a;常見錯誤歸納前言一、一些小定義和小技巧二、蒙塔卡羅求解方法1.函數的定義2.函數引用3.代碼量較少的蒙塔卡羅 三、函數引用與多變量四、矩陣引用五、非線性函數&#xff…

14-39 劍和詩人13 - 頂級大模型測試分析和建議

????? 隨著對高級語言功能的需求不斷飆升&#xff0c;市場上涌現出大量語言模型&#xff0c;每種模型都擁有獨特的優勢和功能。然而&#xff0c;駕馭這個錯綜復雜的生態系統可能是一項艱巨的任務&#xff0c;開發人員和研究人員經常面臨選擇最適合其特定需求的模型的挑戰。…

哈弗架構和馮諾伊曼架構

文章目錄 1. 計算機體系結構 2. 哈弗架構&#xff08;Harvard Architecture&#xff09; 3. 改進的哈弗架構 4. 馮諾伊曼架構&#xff08;Von Neumann Architecture&#xff09; 5. 結構對比 1. 計算機體系結構 計算機體系結構是指計算機系統的組織和實現方式&#xff0c…

Python | Leetcode Python題解之第220題存在重復元素III

題目&#xff1a; 題解&#xff1a; class Solution(object):def containsNearbyAlmostDuplicate(self, nums, k, t):from sortedcontainers import SortedSetst SortedSet()left, right 0, 0res 0while right < len(nums):if right - left > k:st.remove(nums[left]…

Python基礎問題匯總

為什么學習Python&#xff1f; 易學易用&#xff1a;Python語法簡潔清晰&#xff0c;易于學習。廣泛的應用領域&#xff1a;適用于Web開發、數據科學、人工智能、自動化腳本等多種場景。強大的庫支持&#xff1a;擁有豐富的第三方庫&#xff0c;如NumPy、Pandas、TensorFlow等…

Sass 語法

文章目錄 編譯變量 \$嵌套 {} > \~導入 import注釋 // /*\* \**/混入 mixin/include繼承 extend數據類型運算控制 if/for/each/while函數 function媒體查詢 media根發出 at-root警告warn/錯誤error/調試debug 編譯 編譯命令 單文件轉換命令 sass input.scss output.css單…

數學基礎 -- 反函數

反函數技術文檔 反函數的定義 反函數&#xff08;inverse function&#xff09;是指一種將函數的輸出反過來作為輸入&#xff0c;從而恢復原來輸入的函數。具體來說&#xff0c;如果有一個函數 f f f&#xff0c;它把一個值 x x x 映射到一個值 y y y&#xff0c;即 f ( …

68.WEB滲透測試-信息收集- WAF、框架組件識別(8)

免責聲明&#xff1a;內容僅供學習參考&#xff0c;請合法利用知識&#xff0c;禁止進行違法犯罪活動&#xff01; 內容參考于&#xff1a; 易錦網校會員專享課 上一個內容&#xff1a;67.WEB滲透測試-信息收集- WAF、框架組件識別&#xff08;7&#xff09; 右邊這些是waf的…

Mean teacher are better role models-論文筆記

論文筆記 資料 1.代碼地址 2.論文地址 https://arxiv.org/pdf/1703.01780 3.數據集地址 CIFAR-10 https://www.cs.utoronto.ca/~kriz/cifar.html 論文摘要的翻譯 最近提出的Temporal Ensembling方法在幾個半監督學習基準中取得了最先進的結果。它維護每個訓練樣本的標簽…

PCIe驅動開發(1)— 開發環境搭建

PCIe驅動開發&#xff08;1&#xff09;— 開發環境搭建 一、前言 二、Ubuntu安裝 參考: VMware下Ubuntu18.04虛擬機的安裝 三、QEMU安裝 下載網站&#xff1a; https://download.qemu.org 下載文件&#xff1a;qemu-4.1.0-rc5.tar.xz 使用如下命令解壓&#xff1a; tar …

opencv 設置超時時間

經常爬視頻數據&#xff0c;然后用opencv做成圖片 因此設置超時時間很重要 cap.set(cv2.CAP_PROP_FPS, timeout_ms) for idx, row in data.iterrows(): if idx < 400: continue try: # 打開視頻文件 timeout_ms 5000 cap cv2.VideoCapture(row[PLAY_URL]) cap.set(cv2.C…