java垃圾回收機制(面試)

来源:https://www.cnblogs.com/lzdream/archive/2023/04/26/17354334.html
-Advertisement-
Play Games

1.1堆空間結構 Java 的自動記憶體管理主要是針對對象記憶體的回收和對象記憶體的分配。同時,Java 自動記憶體管理最核心的功能是 堆 記憶體中對象的分配與回收。Java 堆是垃圾收集器管理的主要區域,因此也被稱作 GC 堆。Eden 區、兩個 Survivor 區 S0 和 S1 都屬於新生代,中間一層 ...


1.1堆空間結構

  Java 的自動記憶體管理主要是針對對象記憶體的回收和對象記憶體的分配。同時,Java 自動記憶體管理最核心的功能是 記憶體中對象的分配與回收。Java 堆是垃圾收集器管理的主要區域,因此也被稱作 GC 堆。Eden 區、兩個 Survivor 區 S0 和 S1 都屬於新生代,中間一層屬於老年代,最下麵一層屬於永久代。 

  

  

1.2記憶體分配和回收機制

  當 Eden 區沒有足夠空間進行分配時,虛擬機將發起一次 Minor GC。GC 期間虛擬機又發現 allocation1 無法存入 Survivor 空間,所以只好通過 分配擔保機制 把新生代的對象提前轉移到老年代中去。執行 Minor GC 後,後面分配的對象如果能夠存在 Eden 區的話,還是會在 Eden 區分配記憶體。

  大對象直接進入老年代,大對象就是需要大量連續記憶體空間的對象。大對象直接進入老年代主要是為了避免為大對象分配記憶體時由於分配擔保機制帶來的複製而降低效率。

  長期存活的對象將進入老年代,大部分情況,對象都會首先在 Eden 區域分配。如果對象在 Eden 出生並經過第一次 Minor GC 後仍然能夠存活,並且能被 Survivor 容納的話,將被移動到 Survivor 空間(s0 或者 s1)中,並將對象年齡設為 1。對象在 Survivor 中每熬過一次 MinorGC,年齡就增加 1 歲,當它的年齡增加到一定程度(預設為 15 歲),就會被晉升到老年代中。

  回收機制

  部分收集 (Partial GC):

  • 新生代收集(Minor GC / Young GC):只對新生代進行垃圾收集;
  • 老年代收集(Major GC / Old GC):只對老年代進行垃圾收集。需要註意的是 Major GC 在有的語境中也用於指代整堆收集;
  • 混合收集(Mixed GC):對整個新生代和部分老年代進行垃圾收集。

  整堆收集 (Full GC):收集整個 Java 堆和方法區。 

  

  GC 調優策略中很重要的一條經驗總結是這樣說的:

  將新對象預留在新生代,由於 Full GC 的成本遠高於 Minor GC,因此儘可能將對象分配在新生代是明智的做法,實際項目中根據 GC 日誌分析新生代空間大小分配是否合理,適當通過“-Xmn”命令調節新生代大小,最大限度降低新對象直接進入老年代的情況。

1.3死亡對象判斷方法

  引用計數法:給對象中添加一個引用計數器:有一個地方引用它,計數器就加 1;當引用失效,計數器就減 1;這個方法實現簡單,效率高,但是目前主流的虛擬機中並沒有選擇這個演算法來管理記憶體,其最主要的原因是它很難解決對象之間相互迴圈引用的問題。

  可達性分析演算法:通過一系列的稱為 “GC Roots” 的對象作為起點,從這些節點開始向下搜索,節點所走過的路徑稱為引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連的話,則證明此對象是不可用的,需要被回收。

  

  對象可以被回收,就代表一定會被回收嗎?

  即使在可達性分析法中不可達的對象,也並非是“非死不可”的,這時候它們暫時處於“緩刑階段”,要真正宣告一個對象死亡,至少要經歷兩次標記過程;可達性分析法中不可達的對象被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize 方法。當對象沒有覆蓋 finalize 方法,或 finalize 方法已經被虛擬機調用過時,虛擬機將這兩種情況視為沒有必要執行。被判定為需要執行的對象將會被放在一個隊列中進行第二次標記,除非這個對象與引用鏈上的任何一個對象建立關聯,否則就會被真的回收。

  1.強引用(StrongReference)

  一個對象具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當記憶體空間不足,Java 虛擬機寧願拋出 OutOfMemoryError 錯誤,使程式異常終止,也不會回收強引用的對象。

  2.軟引用(SoftReference)

  如果一個對象只具有軟引用,那就類似於可有可無的生活用品。如果記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體空間不足了,就會回收這些對象的記憶體。

  3.弱引用(WeakReference)

  如果一個對象只具有弱引用,那就類似於可有可無的生活用品。弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的對象,不管當前記憶體空間足夠與否,都會回收它的記憶體。

  4.虛引用(PhantomReference)

  與其他幾種引用都不同,虛引用並不會決定對象的生命周期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。虛引用主要用來跟蹤對象被垃圾回收的活動

  虛引用與軟引用和弱引用的一個區別在於: 虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的記憶體之前,把這個虛引用加入到與之關聯的引用隊列中。程式可以通過判斷引用隊列中是否已經加入了虛引用,來瞭解被引用的對象是否將要被垃圾回收。程式如果發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的記憶體被回收之前採取必要的行動。

  運行時常量池主要回收的是廢棄的常量 字元串常量池中存在字元串 "abc",如果當前沒有任何 String 對象引用該字元串常量的話,就說明常量 "abc" 就是廢棄常量,如果這時發生記憶體回收的話而且有必要的話,"abc" 就會被系統清理出常量池了。

  方法區主要回收的是無用的類。而要判定一個類是否是“無用的類”的條件則相對苛刻許多。類需要同時滿足下麵 3 個條件才能算是 “無用的類” ,滿足可以回收。

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

1.4垃圾收集演算法

  標記-清除演算法:該演算法分為“標記”和“清除”階段:首先標記出所有不需要回收的對象,在標記完成後統一回收掉所有沒有被標記的對象。這種垃圾收集演算法會帶來兩個明顯的問題:效率問題、空間問題(標記清除後會產生大量不連續的碎片)

  標記-複製演算法:將記憶體分為大小相同的兩塊,每次使用其中的一塊。當這一塊的記憶體使用完後,就將還存活的對象複製到另一塊去,然後再把使用的空間一次清理掉。這樣就使每次的記憶體回收都是對記憶體區間的一半進行回收。

  標記-整理演算法:根據老年代的特點提出的一種標記演算法,標記過程仍然與“標記-清除”演算法一樣,但後續步驟不是直接對可回收對象回收,而是讓所有存活的對象向一端移動,然後直接清理掉端邊界以外的記憶體。

  分代收集演算法:當前虛擬機的垃圾收集都採用分代收集演算法,根據對象存活周期的不同將記憶體分為幾塊。一般將 java 堆分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集演算法。

  比如在新生代中,每次收集都會有大量對象死去,所以可以選擇”標記-複製“演算法,只需要付出少量對象的複製成本就可以完成每次垃圾收集。而老年代的對象存活幾率是比較高的,而且沒有額外的空間對它進行分配擔保,所以我們必須選擇“標記-清除”或“標記-整理”演算法進行垃圾收集。

1.5垃圾收集器

  Serial (串列)收集器:最基本、歷史最悠久的垃圾收集器了,它的 “單線程” 的意義不僅僅意味著它只會使用一條垃圾收集線程去完成垃圾收集工作,更重要的是它在進行垃圾收集工作的時候必須暫停其他所有的工作線程,直到它收集結束。簡單而高效(與其他收集器的單線程相比)。Serial 收集器由於沒有線程交互的開銷,自然可以獲得很高的單線程收集效率。Serial 收集器對於運行在 Client 模式下的虛擬機來說是個不錯的選擇。

  ParNew 收集器:Serial 收集器的多線程版本,除了使用多線程進行垃圾收集外,其餘行為和 Serial 收集器完全一樣。新

  Parallel Scavenge 收集器:收集器關註點是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的關註點更多的是用戶線程的停頓時間。所謂吞吐量就是 CPU 中用於運行用戶代碼的時間與 CPU 總消耗時間的比值。

  上面都是新生代採用標記-複製演算法,老年代採用標記-整理演算法。

  Serial Old 收集器:Serial 收集器的老年代版本,它同樣是一個單線程收集器。它主要有兩大用途:一種用途是在 JDK1.5 以及以前的版本中與 Parallel Scavenge 收集器搭配使用,另一種用途是作為 CMS 收集器的後備方案。

  Parallel Old 收集器:Parallel Scavenge 收集器的老年代版本。使用多線程和“標記-整理”演算法。在註重吞吐量以及 CPU 資源的場合,都可以優先考慮 Parallel Scavenge 收集器和 Parallel Old 收集器。

  CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。它非常符合在註重用戶體驗的應用上使用。是 HotSpot 虛擬機第一款真正意義上的併發收集器,它第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工作。CMS 收集器是一種 “標記-清除”演算法實現的,主要優點:併發收集、低停頓。但是它有下麵三個明顯的缺點:

  • 對 CPU 資源敏感;
  • 無法處理浮動垃圾;
  • 它使用的回收演算法-“標記-清除”演算法會導致收集結束時會有大量空間碎片產生。

 

  G1 是一款面向伺服器的垃圾收集器,主要針對配備多顆處理器及大容量記憶體的機器. 以極高概率滿足 GC 停頓時間要求的同時,還具備高吞吐量性能特征.

  • 並行與併發:G1 能充分利用 CPU、多核環境下的硬體優勢,使用多個 CPU來縮短 Stop-The-World 停頓時間。部分其他收集器原本需要停頓 Java 線程執行的 GC 動作,G1 收集器仍然可以通過併發的方式讓 java 程式繼續執行。
  • 分代收集:雖然 G1 可以不需要其他收集器配合就能獨立管理整個 GC 堆,但是還是保留了分代的概念。
  • 空間整合:與 CMS 的“標記-清除”演算法不同,G1 從整體來看是基於“標記-整理”演算法實現的收集器;從局部上來看是基於“標記-複製”演算法實現的。
  • 可預測的停頓:這是 G1 相對於 CMS 的另一個大優勢,降低停頓時間是 G1 和 CMS 共同的關註點,但 G1 除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為 M 毫秒的時間片段內。

  G1 收集器在後臺維護了一個優先列表,每次根據允許的收集時間,優先選擇回收價值最大的 Region(這也就是它的名字 Garbage-First 的由來)

  

  到 jdk8 為止,預設的垃圾收集器是 Parallel Scavenge 和 Parallel Old。從 jdk9 開始,G1 收集器成為預設的垃圾收集器

1.6類文件結構

  JVM 可以理解的代碼就叫做位元組碼(即擴展名為 .class 的文件),它不面向任何特定的處理器,只面向虛擬機。Java 語言通過位元組碼的方式,在一定程度上解決了傳統解釋型語言執行效率低的問題,同時又保留瞭解釋型語言可移植的特點。所以 Java 程式運行時比較高效,而且,由於位元組碼並不針對一種特定的機器,因此,Java 程式無須重新編譯便可在多種不同操作系統的電腦上運行。

  常量池計數器是從 1 開始計數的,將第 0 項常量空出來是有特殊考慮的,索引值為 0 代表“不引用任何一個常量池項”常量池主要存放兩大常量:字面量和符號引用。

  

1.7類載入過程

  

載入

  1. 通過全類名獲取定義此類的二進位位元組流
  2. 將位元組流所代表的靜態存儲結構轉換為方法區的運行時數據結構
  3. 在記憶體中生成一個代表該類的 Class 對象,作為方法區這些數據的訪問入口

驗證

  

準備正式為類變數分配記憶體並設置類變數初始值的階段,這些記憶體都將在方法區中分配。有以下幾點需要註意:

  1. 這時候進行記憶體分配的僅包括類變數( 靜態變數,被 static 關鍵字修飾的變數),而不包括實例變數。實例變數會在對象實例化時隨著對象一塊分配在 Java 堆中。
  2. 這裡所設置的初始值"通常情況"下是數據類型預設的零值(如 0、0L、null、false 等)而不是 111(初始化階段才會賦值)。

  

解析:虛擬機將常量池內的符號引用替換為直接引用的過程,也就是得到類或者欄位、方法在記憶體中的指針或者偏移量。解析動作主要針對類或介面、欄位、類方法、介面方法、方法類型、方法句柄和調用限定符 7 類符號引用進行。

初始化初始化階段是執行初始化方法,是類載入的最後一步,這一步 JVM 才開始真正執行類中定義的 Java 程式代碼(位元組碼)。<clinit> ()方法是編譯之後自動生成的。

卸載即該類的 Class 對象被 GC。卸載類需要滿足 3 個要求:

  1. 該類的所有的實例對象都已被 GC,也就是說堆不存在該類的實例對象。
  2. 該類沒有在其他任何地方被引用
  3. 該類的類載入器的實例已被 GC

1.8類初始化過程

  1.首先,初始化父類中的靜態成員變數和靜態代碼塊,按照在程式中出現的順序初始化;

  2.然後,初始化子類中的靜態成員變數和靜態代碼塊,按照在程式中出現的順序初始化;

  3.其次,初始化父類的普通成員變數和代碼塊,再執行父類的構造方法;

  4.最後,初始化子類的普通成員變數和代碼塊,再執行子類的構造方法;

1.9類載入器

  JVM 中內置了三個重要的 ClassLoader,除了 BootstrapClassLoader 其他類載入器均由 Java 實現且全部繼承自java.lang.ClassLoader

  1. BootstrapClassLoader(啟動類載入器) :最頂層的載入類,由 C++實現,負責載入 %JAVA_HOME%/lib目錄下的 jar 包和類或者被 -Xbootclasspath參數指定的路徑中的所有類。
  2. ExtensionClassLoader(擴展類載入器) :主要負責載入 %JRE_HOME%/lib/ext 目錄下的 jar 包和類,或被 java.ext.dirs 系統變數所指定的路徑下的 jar 包。
  3. AppClassLoader(應用程式類載入器) :面向我們用戶的載入器,負責載入當前應用 classpath 下的所有 jar 包和類。

  每一個類都有一個對應它的類載入器。系統中的 ClassLoader 在協同工作的時候會預設使用 雙親委派模型 。即在類載入的時候,系統會首先判斷當前類是否被載入過。已經被載入的類會直接返回,否則才會嘗試載入。載入的時候,首先會把該請求委派給父類載入器的 loadClass() 處理,因此所有的請求最終都應該傳送到頂層的啟動類載入器 BootstrapClassLoader 中。當父類載入器無法處理時,才由自己來處理。當父類載入器為 null 時,會使用啟動類載入器 BootstrapClassLoader 作為父類載入器。  

  

  雙親委派模型的好處:雙親委派模型保證了 Java 程式的穩定運行,可以避免類的重覆載入(JVM 區分不同類的方式不僅僅根據類名,相同的類文件被不同的類載入器載入產生的是兩個不同的類),也保證了 Java 的核心 API 不被篡改。

如果我們不想用雙親委派模型怎麼辦?

  自定義載入器的話,需要繼承 ClassLoader 。如果我們不想打破雙親委派模型,就重寫 ClassLoader 類中的 findClass() 方法即可,無法被父類載入器載入的類最終會通過這個方法被載入。但是,如果想打破雙親委派模型則需要重寫 loadClass() 方法.打破雙親委派機制的場景有很多:JDBC、JNDI、Tomcat等

JVM白話地址

1.10JVM調優

  所有線程共用數據區大小=新生代大小 + 年老代大小 + 持久代大小。持久代一般固定大小為 64m。所以 java 堆中增大年輕代後,將會減小年老代大小(因為老年代的清理是使用 fullgc,所以老年代過小的話反而是會增多 fullgc 的)。此值對系統性能影響較大,Sun 官方推薦配置為 java 堆的 3/8。

  調整最大堆記憶體和最小堆記憶體通常會將這兩個參數配置成相同的值,其目的是為了能夠在 java 垃圾回收機制清理完堆區後不需要重新分隔計算堆區的大小而浪費資源。

  調整新生代和老年代的比值

  調整 Survivor 區和 Eden 區的比值

  設置年輕代和老年代的大小

  根據實際事情調整新生代和幸存代的大小,官方推薦新生代占 java 堆的 3/8,幸存代占新生代的 1/10。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.介紹 Random庫Python中用於生成隨機數的一個標準庫。電腦沒有辦法產生真正的隨機數,但它可以產生偽隨機數。 偽隨機數是電腦按照一定的運算規則產生的一些數據,只不過這些數據表現為隨機數的形式。電腦中採用梅森旋轉演算法生成為隨機序列,序列中的每一個元素就是偽隨機數,由於電腦不能產生真正 ...
  • go中 for迴圈的坑 在使用for迴圈修改結構體切片中的值時,發現並沒有修改成功。 type Dog struct { name string } func (d *Dog) setNewName(name string) { d.name = name } func main() { d := ...
  • sourceTree合併一次提交的內容 在基於git的開發中,經常遇到不同分支需要合併某一次特定的提交的代碼,而不是合併整個代碼。 場景:A分支是通用分支,B分支是私有化分支,現在A分支修改了一個通用的功能,需要合併到B分支上,功能在一次提交上。B分支只需要這次提交的代碼,對A分支上改動的其他代碼都 ...
  • 異常處理 異常 異常即是一個事件,該事件會在程式執行過程中發生,影響了程式的正常執行。 一般情況下,在Python無法正常處理程式時就會發生一個異常。 異常是Python對象,表示一個錯誤。 當Python腳本發生異常時我們需要捕獲處理它,否則程式會終止執行。 捕獲異常 異常類型捕獲 # 捕獲常規異 ...
  • 教程簡介 Java 8 (又稱為 jdk 1.8) 是 Java 語言開發的一個主要版本。 Java 8 是oracle公司於2014年3月發佈,可以看成是自Java 5 以來最具革命性的版本。Java 8為Java語言、編譯器、類庫、開發工具與JVM帶來了大量新特性。 Java 8入門教程 - 從 ...
  • 教程簡介 XAML是eXtensible Application Markup Language的英文縮寫,相應的中文名稱為可擴展應用程式標記語言,它是微軟公司為構建應用程式用戶界面而創建的一種新的描述性語言。XAML提供了一種便於擴展和定位的語法來定義和程式邏輯分離的用戶界面,而這種實現方式和AS ...
  • 協程 進程和線程 進程 ​ 當運行一個應用程式的時候,操作系統會為這個應用程式啟動一個進程。可以將這個進程看作一個包含了應用程式在運行中需要用到和維護的各種資源的容器。這些資源包括但不限於記憶體地址空間、文件和設備的句柄以及線程 線程 ​ 一個線程是一個執行空間,這個空間會被操作系統調度來運行函數中所 ...
  • 編譯 簡單就是把代碼跑一哈,然後我們的代碼 .java文件 就被編譯成了 .class 文件 反編譯 就是針對編譯生成的 jar/war 包 裡面的 .class 文件 逆向還原回來,可以看到你的代碼寫的啥。 比較常用的反編譯工具 JD-GUI ,直接把編譯好的jar丟進去,大部分都能反編譯看到源碼 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...