Java面試題超詳細整理《JVM篇》

JVM由那些部分組成,執行流程是什麼?

JVM的由以下幾部分組成:

類載入器(ClassLoader): Java的動態類載入功能由ClassLoader子系統處理。它載入,連結。並在執行時(而非編譯時)首次引用類時初始化類檔案。

執行時資料區(Runtime Data Area): Java虛擬機器在執行Java程式的過程中會把它管理的記憶體分為若干個不同的資料區域。

執行引擎(Execution Engine): 分配給執行時資料區的位元組碼將由執行引擎執行,執行引擎讀取位元組碼並逐段執行。(位元組碼執行引擎編譯成機器碼後才可在物理機上執行)

本地庫介面(Native Interface): JNI將與本機方法庫進行互動,並提供執行引擎所需的本機庫

本地庫(Native Libraries): 本機庫的集合,執行引擎執行時需要

Java程式碼執行流程:

-> java源程式透過編譯器(javac。exe)對javac檔案(。java)進行編譯

–> 生成位元組碼檔案(。class)

—> 類載入器把位元組碼載入到記憶體,放入執行時資料區的方法區內

——-> 執行引擎讀取位元組碼並逐段執行

————> 解釋執行(對位元組碼指令進行逐行的解釋)編譯執行(將熱點程式碼編譯成機器指令)

————-> 在作業系統(Windows,Linux等)上執行

Java面試題超詳細整理《JVM篇》

簡述java類載入機制?

Java中的所有類,都需要由類載入器裝載到JVM中才能執行。類載入器本身也是一個類,而它的工作就是把class檔案從硬碟讀取到記憶體中,並對資料進行校驗,解析和初始化,最終形成可以被虛擬機器直接使用的java型別。

Java類的載入是動態的,它並不會一次性將所有類全部載入後再執行,而是保證程式執行的基礎類(像是基類)完全載入到jvm中,至於其他類,則在需要的時候才載入。

當程式主動使用某個類時,如果該類還未被載入到記憶體中,則JVM會透過

載入、連線、初始化

3個步驟來對該類進行初始化。如果沒有意外,JVM將會連續完成3個步驟,所以有時也把這個3個步驟統稱為類載入或類初始化。

載入:透過一個類的全限定名獲取定義此類的二進位制位元組流,並將這個位元組流所代表的靜態儲存結構轉換成方法區中的執行時資料結構,並在堆中生成一個代表這個類的java.lang.Class物件,作為方法區類資料的訪問入口,這個過程需要類載入器參與。

連線過程:驗證-》準備-》解析

① 驗證(Verify):目的在於確保Class檔案的位元組流中包含資訊符合當前虛擬機器要求,保證被載入類的正確性,不會危害虛擬機器自身安全,主要包括四種驗證,檔案格式驗證,元資料驗證,位元組碼驗證,符號引用驗證

②準備(Prepare):為類變數分配記憶體並且設定該類變數的預設初始值,即零值

③解析:將常量池內的符號引用轉換為直接引用的過程

初始化:對靜態變數和靜態程式碼塊執行初始化工作

載入class檔案的方式:

①從本地系統中直接載入

②透過網路獲取,典型場景:Web Applet

③從zip壓縮包中讀取,成為日後jar、war格式的基礎

④執行時計算生成,使用最多的是:動態代理技術

初始化階段就是執行類構造器方法<clinit>() 的過程 此方法不需定義,是javac編譯器自動收集類中的所有類變數的賦值動作和靜態程式碼塊中的語句合併而來(當代碼中包含static變數時,<clinit>() 自動生成,如果沒有靜態程式碼快則不會生成)。<clinit>()方法中的指令按語句在原始檔中出現的順序執行<clinit>()不同於類的構造器。(關聯:構造器是虛擬機器視角下的<init>()) 若該類具有父類,JVM會保證子類的<clinit>()執行前,父類的<clinit>()已經執行完畢 虛擬機器必須保證一個類的<clinit>()方法在多執行緒下被同步加鎖

Java面試題超詳細整理《JVM篇》

類載入器的分類

類載入器:透過類的許可權定名獲取該類的二進位制位元組流的程式碼塊。

JVM支援兩種型別的類載入器 ,分別為引導類載入器(Bootstrap ClassLoader)和自定義類載入器(User-Defined ClassLoader)。

從概念上來講,自定義類載入器一般指的是程式中由開發人員自定義的一類類載入器,但是Java虛擬機器規範卻沒有這麼定義,而是將所有派生於抽象類ClassLoader的類載入器都劃分為自定義類載入器,所以ExtClassLoader 和 AppClassLoader 都屬於自定義載入器。

四者之間是包含關係,不是上層和下層,也不是子父類的繼承關係:

Java面試題超詳細整理《JVM篇》

啟動類載入器(Bootstrap ClassLoader):用來載入java核心類庫,無法被java程式直接引用。

擴充套件類載入器(extensions class loader):它用來載入 Java 的擴充套件庫。Java 虛擬機器的實現會提供一個擴充套件庫目錄。該類載入器在此目錄裡面查詢並載入 Java 類。

系統類載入器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來載入Java 類。一般來說,Java 應用的類都是由它來完成載入的。可以透過ClassLoader.getSystemClassLoader()來獲取它。

使用者自定義類載入器,透過繼承 java.lang.ClassLoader類的方式實現。

什麼是雙親委派模型?

如果一個類載入器收到了類載入的請求,它首先不會自己去載入這個類,而是把這個請求委派給父類載入器去完成,每一層的類載入器都是如此,這樣所有的載入請求都會被傳送到頂層的啟動類載入器中,只有當父載入無法完成載入請求(它的搜尋範圍中沒找到所需的類)時,子載入器才會嘗試去載入類。

Java面試題超詳細整理《JVM篇》

總結就是: 當一個類收到了類載入請求時,不會自己先去載入這個類,而是將其委派給父類,由父類去載入,如果此時父類不能載入,反饋給子類,由子類去完成類的載入。

作用:

雙親機制避免了類的重複載入

保護程式安全,防止核心API被隨意篡改

說一下 JVM 執行時資料區

Java 虛擬機器在執行 Java 程式的過程中會把它所管理的記憶體區域劃分為若干個不同的資料區域。這些區域都有各自的用途,以及建立和銷燬的時間,有些區域隨著虛擬機器程序的啟動而存在,有些區域則是依賴執行緒的啟動和結束而建立和銷燬。Java 虛擬機器所管理的記憶體被劃分為如下幾個區域:

Java面試題超詳細整理《JVM篇》

執行緒私有的:程式計數器、虛擬機器棧、本地方法棧

執行緒共享的:堆、方法區

程式計數器(Program Counter Register):當前執行緒所執行的位元組碼的行號指示器,位元組碼解析器的工作是透過改變這個計數器的值,來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能,都需要依賴這個計數器來完成;

Java 虛擬機器棧(Java Virtual Machine Stacks):每個方法在執行的同時都會在Java 虛擬機器棧中建立一個棧幀(Stack Frame)用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊;棧幀就是Java虛擬機器棧中的下一個單位。

本地方法棧(Native Method Stack):與虛擬機器棧的作用是一樣的,只不過虛擬機器棧是服務 Java方法的,而本地方法棧是為虛擬機器呼叫 Native 方法服務的;Native 關鍵字修飾的方法是看不到的,Native 方法的原始碼大部分都是 C和C++ 的程式碼

Java 堆(Java Heap):Java 虛擬機器中記憶體最大的一塊,是被所有執行緒共享的,幾乎所有的物件例項都在這裡分配記憶體;java堆是垃圾收集器管理的主要區域,因此也被成為“GC堆”。

方法區(Methed Area):用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯後的程式碼等資料。雖然 Java 虛擬機器規範把法區描述為堆的個邏輯部分,但是它卻有個別名叫做 Non-Heap(堆),的應該是與 Java 堆區分開來。

執行時常量池:

運時常量池是法區的部分。Class 件中除了有類的版本、欄位、法、接等描述資訊外,還有常量池表(於存放編譯期成的各種字量和符號引)既然運時常量池是法區的部分,然受到法區記憶體的限制,當常量池法再申請到記憶體時會丟擲 OutOfMemoryError 錯誤。

JDK1.8 運時常量池在元空間,字串常量池在堆中。

異常相關:

Java面試題超詳細整理《JVM篇》

程式計數器: 記憶體區域中唯一一個在Java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域。

虛擬機器棧與本地方法棧: 在Java虛擬機器棧和本地方法棧中,規定了兩個異常狀況:如果執行緒請求的棧深度大於棧所允許的深度,將丟擲StackOverflowError異常;如果棧可以動態擴充套件,並且擴充套件時無法申請到足夠的記憶體,就會丟擲OutOfMemoryError異常。

堆: 如果在堆中沒有記憶體完成例項分配,並且堆也無法再擴充套件時(記憶體大小超過“-xmx"所指定的最大記憶體時),將會丟擲OutOfMemoryError異常。

方法區: 方法區的大小決定了系統可以儲存多少個類,如果系統定義了太多的類(比如說載入大量第三方jar包),導致方法區溢位,虛擬機器會丟擲 OutOfMemoryError 異常。

介紹下Java虛擬機器棧?

Java虛擬機器是執行緒私有的,它的生命週期和執行緒相同。 虛擬機器棧描述的是Java方法執行的記憶體模型: 每個方法在執行的同時都會建立一個棧幀(StackFrame)用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊

解析棧幀:

區域性變量表:是用來儲存我們臨時8個基本資料型別、物件引用地址、returnAddress型別。(returnAddress中儲存的是return後要執行的位元組碼的指令地址。)

運算元棧:運算元棧就是用來操作的,例如程式碼中有個 i = 6*6,他在一開始的時候就會進行操作,讀取我們的程式碼,進行計算後再放入區域性變量表中去

動態連結:如果被呼叫的方法在編譯期無法被確定下來,也就是說,只能夠在程式執行期將呼叫的方法的符號轉換為直接引用,由於這種引用轉換過程具備動態性,因此也被稱之為動態連結。

方法返回地址:在方法退出後都返回到該方法被呼叫的位置,正常的話就是return呼叫者的pc計數器的值,不正常的話返回異常表中的對應資訊

Java面試題超詳細整理《JVM篇》

對於虛擬機器棧來說不存在垃圾回收問題

Java 虛擬機器棧會出現兩種錯誤: StackOverFlowError 和 OutOfMemoryError 。

StackOverFlowError : 若 Java 虛擬機器棧的記憶體不允許動態擴充套件,那麼當執行緒請求棧的深度超過當前 Java 虛擬機器棧的最深度的時候,就丟擲 StackOverFlowError 錯誤。

OutOfMemoryError : 若 Java 虛擬機器堆中沒有空閒記憶體,並且垃圾回收器也法提供更多記憶體的話。就會丟擲 OutOfMemoryError 錯誤。

擴充套件:那麼法/函式如何調?

Java 棧可類資料結構中棧,Java 棧中儲存的主要內容是棧幀,每次函式調都會有個對應的棧幀被壓 Java 棧,每個函式調結束後,都會有個棧幀被彈出。

Java 法有兩種返回式: return 語句、丟擲異常。不管哪種返回式都會導致棧幀被彈出。

一個方法呼叫另一個方法,會建立很多棧幀嗎?

會建立。如果一個棧中有動態連結呼叫別的方法,就會去建立新的棧幀,棧中是由順序的,一個棧幀呼叫另一個棧幀,另一個棧幀就會排在呼叫者下面

遞迴的呼叫自己會建立很多棧幀嗎?

答:遞迴的話也會建立多個棧幀,就是在棧中一直從上往下排下去

介紹下Java堆嗎?

java堆(Java Heap)是java虛擬機器所管理的記憶體中最大的一塊,是被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時建立。此記憶體區域的唯一目的就是存放物件例項,乎所有的物件例項以及陣列都在這分配記憶體。

java堆是垃圾收集器管理的主要區域,因此也被成為“GC堆”。從垃圾回收的度,由於現在收集器基本都採分代垃圾收集演算法,所以 Java 堆還可以細分為:新生代和老生代,再細緻點可分為:Eden 空間、From Survivor、To Survivor 空間等

Java面試題超詳細整理《JVM篇》

根據Java虛擬機器規範的規定,Java堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可。當前主流的虛擬機器都是可擴充套件的(透過 -Xmx 和 -Xms 控制)。如果堆中沒有記憶體可以完成例項分配,並且堆也無法再擴充套件時,將會丟擲OutOfMemoryError異常。

為什麼要將永久代替換為元空間呢?

JDK1.8以前使用永久代(方法區),JDK1.8以後使用元空間

整個永久代有個 JVM 本身設定固定大小上限,法進調整,而JVM載入的class的總數,方法的大小等都很難確定,因此對永久代大小的指定難以確定。太小的永久代容易導致永久代記憶體溢位,太大的永久代則容易導致虛擬機器記憶體緊張,空間浪費。元空間使的是直接記憶體,受本機可記憶體的限制,雖然元空間仍舊可能溢位,但是原來出現的率會更。

元空間溢位時會得到如下錯誤: java。lang。OutOfMemoryError: MetaSpace

你可以使 -XX MaxMetaspaceSize 標誌設定最元空間,預設值為 unlimited,這意味著它只受系統記憶體的限制。 -XX MetaspaceSize 調整標誌定義元空間的初始如果未指定此標誌,則 Metaspace 將根據運時的應程式需求動態地重新調整。

什麼是直接記憶體

直接記憶體(Direct Memory)並不是虛擬機器執行時資料區的一部分,也不是Java虛擬機器中定義的記憶體區域。但是這部分記憶體也被頻繁地使用,而且也可能導致 OutOfMemoryError 異常出現。

直接記憶體可以看成是物理記憶體和Java虛擬機器記憶體的中間記憶體,他可以直接使 Native 函式庫直接分配堆外記憶體,然後透過個儲存在 Java 堆中的 DirectByteBuffer 物件作為這塊記憶體的引進操作。這樣就能在些場景中顯著提效能,因為避免了在 Java 堆和 Native 堆之間來回複製資料。

本機直接記憶體的分配不會受到 Java 堆的限制,但是,既然是記憶體就會受到本機總記憶體以及處理器定址空間的限制。

堆疊的區別是什麼?

Java面試題超詳細整理《JVM篇》

堆:主要用來儲存物件和陣列,物理地址分配不連續、記憶體大小不確定、執行緒共享

棧:用來存放運算元棧,物理地址分配連續、記憶體在編譯期確定、執行緒私有

說下Java物件的建立過程

載入類元資訊,判斷類元資訊(載入、連結、初始化)是否存在

為物件分配記憶體

處理併發問題

初始化分配到的空間,屬性的預設初始化(零值初始化)

設定物件頭資訊

執行init方法初始化(屬性顯示初始化、程式碼塊中的初始化、構造器初始化)

Java面試題超詳細整理《JVM篇》

為物件分配記憶體:

類載入完成後,接著會在Java堆中劃分一塊記憶體分配給物件。記憶體分配根據Java堆是否規整,有兩種方式:

指標碰撞:如果Java堆的記憶體是規整,即所有用過的記憶體放在一邊,而空閒的的放在另一邊。分配記憶體時將位於中間的指標指示器向空閒的記憶體移動一段與物件大小相等的距離,這樣便完成分配記憶體工作。

空閒列表:如果Java堆的記憶體不是規整的,則需要由虛擬機器維護一個列表來記錄那些

處理併發問題:

採用CAS配上失敗重試保證更新的原子性

在Eden區給每個執行緒分配一塊區域TLAB - 透過設定 -XX:+UseTLAB引數來設定(區域加鎖機制),

物件的訪問定位

控制代碼訪問: 棧的區域性變量表中,記錄的物件的引用,然後在堆空間中開闢了一塊空間,也就是控制代碼池。 特點: reference中儲存穩定控制代碼地址,物件被移動(垃圾收集時移動物件很普遍)時只會改變控制代碼中例項資料指標即可,reference本身不需要被修改

Java面試題超詳細整理《JVM篇》

直接指標(HotSpot採用):

直接指標是區域性變量表中的引用,直接指向堆中的例項,在物件例項中有型別指標,指向的是方法區中的物件型別資料。 特點: 節省了指標定位的開銷,但是在物件被移動時reference本身需要被修改。

Java面試題超詳細整理《JVM篇》

簡述Java垃圾回收機制

在java中,程式設計師是不需要顯示的去釋放一個物件的記憶體的,而是由虛擬機器自行執行。在JVM中,有一個垃圾回收執行緒,它是低優先順序的,在正常情況下是不會執行的,只有在虛擬機器空閒或者當前堆記憶體不足時,才會觸發執行,掃面那些沒有被任何引用的物件,並將它們新增到要回收的集合中,進行回收。

優點:JVM的垃圾回收器都不需要我們手動處理無引用的物件了,這個就是最大的優點

缺點:程式設計師不能實時的對某個物件或所有物件呼叫垃圾回收器進行垃圾回收

垃圾收集GC(Gabage Collection),記憶體處理是程式設計人員容易出現問題的地方,忘記或者錯誤的記憶體回收會導致程式或系統的不穩定甚至崩潰,Java 提供的 GC 功能可以自動監測物件是否超過作用域從而達到自動回收記憶體的目的,Java 語言沒有提供釋放已分配記憶體的顯示操作方法。

垃圾回收器的原理是什麼?

對於GC來說,當程式設計師建立物件時,GC就開始監控這個物件的地址、大小以及使用情況。通常,GC採用有向圖的方式記錄和管理堆(heap)中的所有物件。透過這種方式確定哪些物件是"可達的",哪些物件是"不可達的"。當GC確定一些物件為"不可達"時,GC就有責任回收這些記憶體空間。

有什麼辦法手動進行垃圾回收?

程式設計師可以手動執行System.gc(),通知GC執行,但是Java語言規範並不保證GC一定會執行

JVM中怎麼判斷物件是可以被回收的?

垃圾收集器在做垃圾回收的時候,首先需要判定的就是哪些記憶體是需要被回收的,哪些物件是存活的,是不可以被回收的;哪些物件已經死掉了,需要被回收。一般有兩種方法來判斷:

引用計數器法:為每個物件建立一個引用計數,有物件引用時計數器 +1,引用被釋放時計數-1,當計數器為 0 時就可以被回收。它有一個缺點不能解決迴圈引用的問題;(python中使用)

可達性分析演算法:從 GC Roots 開始向下搜尋,搜尋所走過的路徑稱為引用鏈。當一個物件到 GC Roots 沒有任何引用鏈相連時,則證明此物件是可以被回收的。(Java中使用)

可以作為GC Root的物件:

虛擬機器棧(棧幀中的本地變量表)中引用的物件。

方法區中類靜態屬性引用的物件。

方法區中常量引用的物件。

本地方法棧中 Native 方法引用的物件。

JVM 垃圾回收演算法有哪些?

標記-清除演算法:標記無用物件,然後進行清除回收。缺點:效率不高,無法清除垃圾碎片。

複製演算法:按照容量劃分二個大小相等的記憶體區域,當一塊用完的時候將活著的物件複製到另一塊上,然後再把已使用的記憶體空間一次清理掉。缺點:記憶體使用率不高,只有原來的一半。

標記-整理演算法:標記無用物件,讓所有存活的物件都向一端移動,然後直接清除掉端邊界以外的記憶體。

分代演算法:根據物件存活週期的不同將記憶體劃分為幾塊,一般是新生代和老年代,新生代基本採用複製演算法,老年代採用標記整理演算法。

標記-清除演算法

標記-清除演算法(Mark-Sweep)是一種常見的基礎垃圾收集演算法,它將垃圾收集分為兩個階段:

標記階段:標記出可以回收的物件。

*

清除階段:回收被標記的物件所佔用的空間。

標記-清除演算法之所以是基礎的,是因為後面講到的垃圾收集演算法都是在此演算法的基礎上進行改進的。

優點:實現簡單,不需要物件進行移動。

缺點:標記、清除過程效率低,產生大量不連續的記憶體碎片,提高了垃圾回收的頻率。

Java面試題超詳細整理《JVM篇》

複製演算法:

為了解決標記-清除演算法的效率不高的問題,產生了複製演算法。它把記憶體空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾收集時,遍歷當前使用的區域,把存活物件複製到另外一個區域中,最後將當前使用的區域的可回收的物件進行回收。

優點:按順序分配記憶體即可,實現簡單、執行高效,不用考慮記憶體碎片。

缺點:可用的記憶體大小縮小為原來的一半,物件存活率高時會頻繁進行復制

Java面試題超詳細整理《JVM篇》

標記-整理演算法

在新生代中可以使用複製演算法,但是在老年代就不能選擇複製演算法了,因為老年代的物件存活率會較高,這樣會有較多的複製操作,導致效率變低。標記-清除演算法可以應用在老年代中,但是它效率不高,在記憶體回收後容易產生大量記憶體碎片。因此就出現了一種標記-整理演算法(Mark-Compact)演算法,與標記-整理演算法不同的是,在標記可回收的物件後將所有存活的物件壓縮到記憶體的一端,使他們緊湊的排列在一起,然後對端邊界以外的記憶體進行回收。回收後,已用和未用的記憶體都各自一邊。

優點:解決了標記-清理演算法存在的記憶體碎片問題。

缺點:仍需要進行區域性物件移動,一定程度上降低了效率。

Java面試題超詳細整理《JVM篇》

分代收集演算法:

當前商業虛擬機器都採用 分代收集 的垃圾收集演算法。分代收集演算法,顧名思義是根據物件的 存活周 期 將記憶體劃分為幾塊。一般包括 年輕代 、 老年代 和 永久代 ,如圖所示: (後面有重點講解)

Java面試題超詳細整理《JVM篇》

分割槽演算法:

一般來說,在相同條件下,堆空間越大,一次GC時所需要的時間就越長,有關GC產生的停頓也越長。為了更好地控制GC產生的停頓時間,將一塊大的記憶體區域分割成多個小塊,根據目標的停頓時間,每次合理地回收若干個小區間,而不是整個堆空間,從而減少一次GC所產生的停頓。

分代演算法將按照物件的生命週期長短劃分成兩個部分,分割槽演算法將整個堆空間劃分成連續的不同小區間。每一個小區間都獨立使用,獨立回收。這種演算法的好處是可以控制一次回收多少個小區間

Java面試題超詳細整理《JVM篇》

新生代、老年代、永久代的區別

在 Java 中,堆被劃分成兩個不同的區域:新生代 ( Young )、老年代 ( Old )。而新生代 ( Young )又被劃分為三個區域:Eden、From Survivor、To Survivor。這樣劃分的目的是為了使 JVM 能夠更好的管理堆記憶體中的物件,包括記憶體的分配以及回收。

新生代中一般儲存新出現的物件,所以每次垃圾收集時都發現大批物件死去,只有少量物件存活,便採用了

複製演算法

,只需要付出少量存活物件的複製成本就可以完成收集。

老年代中一般儲存存活了很久的物件,他們存活率高、沒有額外空間對它進行分配擔保,就必須採用

“標記-清理”或者“標記-整理” 演算法。

永久代就是JVM的方法區。在這裡都是放著一些被虛擬機器載入的類資訊,靜態變數,常量等資料。這個區中的東西比老年代和新生代更不容易回收。

垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(FullGC)。如果你仔細檢視垃圾收集器的輸出資訊,就會發現永久代也是被回收的。這就是為什麼正確的永久代大小對避免Full GC是非常重要的原因。

Minor GC、Major GC、Full GC是什麼?

Minor GC是新生代GC,指的是發生在新生代的垃圾收集動作。由於java物件大都是朝生夕死的,所以Minor GC非常頻繁,一般回收速度也比較快。(一般採用複製演算法回收垃圾)

Major GC是老年代GC,指的是發生在老年代的GC,通常執行Major GC會連著Minor GC一起執行。Major GC的速度要比Minor GC慢的多。(可採用標記清楚法和標記整理法)

Full GC是清理整個堆空間,包括年輕代和老年代,因為Full GC是清理整個堆空間所以Full GC執行速度非常慢,在Java開發中最好保證少觸發Full GC

Minor GC、Major GC、Full GC的觸發條件

Minor GC 觸發條件一般為:

eden區滿時,觸發MinorGC。

新建立的物件大小 > Eden所剩空間時觸發Minor GC

Major GC和Full GC 觸發條件一般為: Major GC通常是跟full GC是等價的 1. 每次晉升到老年代的物件平均大小>老年代剩餘空間

MinorGC後存活的物件超過了老年代剩餘空間

永久代空間不足

執行System.gc()

CMS GC異常

堆記憶體分配很大的物件

為什麼新生代要分Eden和兩個 Survivor 區域?

如果沒有Survivor,Eden區每進行一次Minor GC,存活的物件就會被送到老年代。老年代很快被填滿,觸發Major GC.老年代的記憶體空間遠大於新生代,進行一次Full GC消耗的時間比Minor GC長得多,所以需要分為Eden和Survivor。

Survivor的存在意義,就是減少被送到老年代的物件,進而減少Full GC的發生。設定兩個Survivor區解決了碎片化問題(使用了複製演算法),剛剛新建的物件在Eden中,經歷一次Minor GC,Eden中的存活物件就會被移動到第一塊survivor space S0,Eden被清空;等Eden區再滿了,就再觸發一次Minor GC,Eden和S0中的存活物件又會被複制送入第二塊survivor spaceS1。(每次只會使用 Eden 和其中的一塊 Survivor 區域來為物件服務,無論什麼時候,總是有一塊 Survivor 區域是空閒著的。)

Java面試題超詳細整理《JVM篇》

Survivor的預篩選保證,只有經歷15次Minor GC還能在新生代中存活的物件,才會被送到老年代。

Java面試題超詳細整理《JVM篇》

Java堆老年代( Old ) 和新生代 ( Young ) 的預設比例?

新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以透過引數 –XX:NewRatio來指定 )

新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區域,Edem 和倆個Survivor 區域比例是 = 8 : 1 : 1 ( 可以透過引數 –XX:SurvivorRatio 來設定 ),

說一下 JVM 有哪些垃圾回收器?

Java面試題超詳細整理《JVM篇》

7種經典的垃圾收集器:

按新生代老年代來分:

新生代回收器:Serial、ParNew、Parallel Scavenge

老年代回收器:Serial Old、CMS、Parallel Old

整堆回收器:G1

按序列、並行來分:

序列回收器:Serial、Serial old

並行回收器:ParNew、Parallel Scavenge、Parallel old

併發回收器:CMS、G1

Java面試題超詳細整理《JVM篇》

Serial收集器(複製演算法): 新生代單執行緒收集器,標記和清理都是單執行緒,優點是簡單高效;

Serial Old收集器 (標記-整理演算法): 老年代單執行緒收集器,Serial收集器的老年代版本;

ParNew收集器 (複製演算法): 新生代收並行集器,實際上是Serial收集器的多執行緒版本,在多核CPU環境下有著比Serial更好的表現;

Parallel Scavenge收集器 (複製演算法): 新生代並行收集器,追求高吞吐量,高效利用 CPU。吞吐量= 使用者執行緒時間/(使用者執行緒時間+GC執行緒時間),高吞吐量可以高效率的利用CPU時間,儘快完成程式的運算任務,適合後臺應用等對互動相應要求不高的場景;

Parallel Old收集器 (標記-整理演算法): 老年代並行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;

CMS(Concurrent Mark Sweep)收集器(標記-清除演算法): 老年代並行收集器,以犧牲吞吐量為代價來獲得最短回收停頓時間的垃圾回收器,具有高併發、低停頓的特點,追求最短GC回收停頓時間。對於要求伺服器響應速度的應用上,這種垃圾回收器非常適合。

G1(Garbage First)收集器 ( 標記整理 + 複製演算法來回收垃圾 ): Java堆並行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基於“標記-整理”演算法實現,也就是說不會產生記憶體碎片。此外,G1收集器不同於之前的收集器的一個重要特點是:G1回收的範圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的範圍僅限於新生代或老年代。

JVM系類學習筆記

,https://blog。csdn。net/Lzy410992/article/details/119448063