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類加載機制?
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>()方法在多執行緒下被同步加鎖

類加載器的分類
類加載器:通過類的權限定名獲取該類的二進制位元組流的代碼塊,
JVM支持兩種型別的類加載器 ,分別為引導類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader),
從概念上來講,自定義類加載器一般指的是程式中由開發人員自定義的一類類加載器,但是Java虛擬機規范卻沒有這么定義,而是將所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器,所以ExtClassLoader 和 AppClassLoader 都屬于自定義加載器,
四者之間是包含關系,不是上層和下層,也不是子父類的繼承關系:

- 啟動類加載器(Bootstrap ClassLoader):用來加載java核心類別庫,無法被java程式直接參考,
- 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫,Java 虛擬機的實作會提供一個擴展庫目錄,該類加載器在此目錄里面查找并加載 Java 類,
- 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載Java 類,一般來說,Java 應用的類都是由它來完成加載的,可以通過ClassLoader.getSystemClassLoader()來獲取它,
- 用戶自定義類加載器,通過繼承 java.lang.ClassLoader類的方式實作,
什么是雙親委派模型?
如果一個類加載器收到了類加載的請求,它首先不會自己去加載這個類,而是把這個請求委派給父類加載器去完成,每一層的類加載器都是如此,這樣所有的加載請求都會被傳送到頂層的啟動類加載器中,只有當父加載無法完成加載請求(它的搜索范圍中沒找到所需的類)時,子加載器才會嘗試去加載類,

總結就是: 當一個類收到了類加載請求時,不會自己先去加載這個類,而是將其委派給父類,由父類去加載,如果此時父類不能加載,反饋給子類,由子類去完成類的加載,
作用:
- 雙親機制避免了類的重復加載
- 保護程式安全,防止核心API被隨意篡改
說一下 JVM 運行時資料區
Java 虛擬機在執行 Java 程式的程序中會把它所管理的記憶體區域劃分為若干個不同的資料區域,這些區域都有各自的用途,以及創建和銷毀的時間,有些區域隨著虛擬機行程的啟動而存在,有些區域則是依賴執行緒的啟動和結束而建立和銷毀,Java 虛擬機所管理的記憶體被劃分為如下幾個區域:

執行緒私有的:程式計數器、虛擬機堆疊、本地方法堆疊
執行緒共享的:堆、方法區
-
程式計數器(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虛擬機規范中沒有規定任何OutOfMemoryError情況的區域,
- 虛擬機堆疊與本地方法堆疊: 在Java虛擬機堆疊和本地方法堆疊中,規定了兩個例外狀況:如果執行緒請求的堆疊深度大于堆疊所允許的深度,將拋出StackOverflowError例外;如果堆疊可以動態擴展,并且擴展時無法申請到足夠的記憶體,就會拋出OutOfMemoryError例外,
- 堆: 如果在堆中沒有記憶體完成實體分配,并且堆也無法再擴展時(記憶體大小超過“-xmx"所指定的最大記憶體時),將會拋出OutOfMemoryError例外,
- 方法區: 方法區的大小決定了系統可以保存多少個類,如果系統定義了太多的類(比如說加載大量第三方jar包),導致方法區溢位,虛擬機會拋出 OutOfMemoryError 例外,
介紹下Java虛擬機堆疊?
Java虛擬機是執行緒私有的,它的生命周期和執行緒相同, 虛擬機堆疊描述的是Java方法執行的記憶體模型: 每個方法在執行的同時都會創建一個堆疊幀(StackFrame)用于存盤區域變數表、運算元堆疊、動態鏈接、方法出口等資訊,
決議堆疊幀:
- 區域變數表:是用來存盤我們臨時8個基本資料型別、物件參考地址、returnAddress型別,(returnAddress中保存的是return后要執行的位元組碼的指令地址,)
- 運算元堆疊:運算元堆疊就是用來操作的,例如代碼中有個 i = 6*6,他在一開始的時候就會進行操作,讀取我們的代碼,進行計算后再放入區域變數表中去
- 動態鏈接:如果被呼叫的方法在編譯期無法被確定下來,也就是說,只能夠在程式運行期將呼叫的方法的符號轉換為直接參考,由于這種參考轉換程序具備動態性,因此也被稱之為動態鏈接,
- 方法回傳地址:在方法退出后都回傳到該方法被呼叫的位置,正常的話就是return呼叫者的pc計數器的值,不正常的話回傳例外表中的對應資訊

對于虛擬機堆疊來說不存在垃圾回收問題
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虛擬機規范的規定,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 堆的限制,但是,既然是記憶體就會受到本機總記憶體??以及處理器尋址空間的限制,
堆疊的區別是什么?
| 對比 | JVM堆 | JVM堆疊 |
|---|---|---|
| 物理地址方面 | 堆的物理地址分配對物件是不連續的,因此性能慢些,在GC的時候也要考慮到不連續的分配,所以使用了各種垃圾回收演算法 | 堆疊使用的是資料結構中的堆疊,先進后出的原則,物理地址分配是連續的,所以性能快, |
| 記憶體分配方面 | 堆因為是不連續的,所以分配的記憶體是在運行期確認的,因此大小不固定,一般堆大小遠遠大于堆疊, | 堆疊是連續的,所以分配的記憶體大小要在編譯期就確認,大小是固定的, |
| 存放的內容方面 | 堆存放的是物件的實體和陣列,因此該區更關注的是資料的存盤 | 堆疊存放:區域變數,運算元堆疊,回傳結果,該區更關注的是程式方法的執行, |
| 程式的可見度 | 堆對于整個應用程式都是共享、可見的, | 堆疊只對于執行緒是可見的,所以也是執行緒私有,他的生命周期和執行緒相同, |
堆:主要用來存盤物件和陣列,物理地址分配不連續、記憶體大小不確定、執行緒共享
堆疊:用來存放運算元堆疊,物理地址分配連續、記憶體在編譯期確定、執行緒私有
說?下Java物件的創建程序
- 加載類元資訊,判斷類元資訊(加載、鏈接、初始化)是否存在
- 為物件分配記憶體
- 處理并發問題
- 初始化分配到的空間,屬性的默認初始化(零值初始化)
- 設定物件頭資訊
- 執行init方法初始化(屬性顯示初始化、代碼塊中的初始化、構造器初始化)

為物件分配記憶體:
類加載完成后,接著會在Java堆中劃分一塊記憶體分配給物件,記憶體分配根據Java堆是否規整,有兩種方式:
- 指標碰撞:如果Java堆的記憶體是規整,即所有用過的記憶體放在一邊,而空閑的的放在另一邊,分配記憶體時將位于中間的指標指示器向空閑的記憶體移動一段與物件大小相等的距離,這樣便完成分配記憶體作業,
- 空閑串列:如果Java堆的記憶體不是規整的,則需要由虛擬機維護一個串列來記錄那些
處理并發問題:
- 采用CAS配上失敗重試保證更新的原子性
- 在Eden區給每個執行緒分配一塊區域TLAB - 通過設定 -XX:+UseTLAB引數來設定(區域加鎖機制),
物件的訪問定位
- 句柄訪問: 堆疊的區域變數表中,記錄的物件的參考,然后在堆空間中開辟了一塊空間,也就是句柄池,
特點: reference中存盤穩定句柄地址,物件被移動(垃圾收集時移動物件很普遍)時只會改變句柄中實體資料指標即可,reference本身不需要被修改
- 直接指標(HotSpot采用):
直接指標是區域變數表中的參考,直接指向堆中的實體,在物件實體中有型別指標,指向的是方法區中的物件型別資料,
特點: 節省了指標定位的開銷,但是在物件被移動時reference本身需要被修改,
簡述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)是一種常見的基礎垃圾收集演算法,它將垃圾收集分為兩個階段:
- 標記階段:標記出可以回收的物件,
* 清除階段:回收被標記的物件所占用的空間,標記-清除演算法之所以是基礎的,是因為后面講到的垃圾收集演算法都是在此演算法的基礎上進行改進的,
優點:實作簡單,不需要物件進行移動,
缺點:標記、清除程序效率低,產生大量不連續的記憶體碎片,提高了垃圾回收的頻率,
復制演算法:
為了解決標記-清除演算法的效率不高的問題,產生了復制演算法,它把記憶體空間劃為兩個相等的區域,每次只使用其中一個區域,垃圾收集時,遍歷當前使用的區域,把存活物件復制到另外一個區域中,最后將當前使用的區域的可回收的物件進行回收,
優點:按順序分配記憶體即可,實作簡單、運行高效,不用考慮記憶體碎片,
缺點:可用的記憶體大小縮小為原來的一半,物件存活率高時會頻繁進行復制
標記-整理演算法
在新生代中可以使用復制演算法,但是在老年代就不能選擇復制演算法了,因為老年代的物件存活率會較高,這樣會有較多的復制操作,導致效率變低,標記-清除演算法可以應用在老年代中,但是它效率不高,在記憶體回收后容易產生大量記憶體碎片,因此就出現了一種標記-整理演算法(Mark-Compact)演算法,與標記-整理演算法不同的是,在標記可回收的物件后將所有存活的物件壓縮到記憶體的一端,使他們緊湊的排列在一起,然后對端邊界以外的記憶體進行回收,回收后,已用和未用的記憶體都各自一邊,
優點:解決了標記-清理演算法存在的記憶體碎片問題,
缺點:仍需要進行區域物件移動,一定程度上降低了效率,
分代收集演算法:
當前商業虛擬機都采用 分代收集 的垃圾收集演算法,分代收集演算法,顧名思義是根據物件的 存活周 期 將記憶體劃分為幾塊,一般包括 年輕代 、 老年代 和 永久代 ,如圖所示: (后面有重點講解)
磁區演算法:
一般來說,在相同條件下,堆空間越大,一次GC時所需要的時間就越長,有關GC產生的停頓也越長,為了更好地控制GC產生的停頓時間,將一塊大的記憶體區域分割成多個小塊,根據目標的停頓時間,每次合理地回收若干個小區間,而不是整個堆空間,從而減少一次GC所產生的停頓,
分代演算法將按照物件的生命周期長短劃分成兩個部分,磁區演算法將整個堆空間劃分成連續的不同小區間,每一個小區間都獨立使用,獨立回收,這種演算法的好處是可以控制一次回收多少個小區間
新生代、老年代、永久代的區別
在 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 區域是空閑著的,)
Survivor的預篩選保證,只有經歷15次Minor GC還能在新生代中存活的物件,才會被送到老年代,
Java堆老年代( Old ) 和新生代 ( Young ) 的默認比例?
新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過引數 –XX:NewRatio來指定 )
新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區域,Edem 和倆個Survivor 區域比例是 = 8 : 1 : 1 ( 可以通過引數 –XX:SurvivorRatio 來設定 ),
說一下 JVM 有哪些垃圾回收器?

7種經典的垃圾收集器:
按新生代老年代來分:
- 新生代回收器:Serial、ParNew、Parallel Scavenge
- 老年代回收器:Serial Old、CMS、Parallel Old
- 整堆回收器:G1
按串行、并行來分:
- 串行回收器:Serial、Serial old
- 并行回收器:ParNew、Parallel Scavenge、Parallel old
- 并發回收器:CMS、G1
| 垃圾回收器 | 作業區域 | 回收演算法 | 作業執行緒 | 用戶執行緒并行 | 描述 |
|---|---|---|---|---|---|
| Serial | 新生帶 | 復制演算法 | 單執行緒 | 否 | Client模式下默認新生代收集器, |
| Serial Old | 老年帶 | 標記-整理 | 單執行緒 | 否 | Serial老年代版本,給Client模式下的虛擬機使用 |
| ParNew | 新生帶 | 復制演算法 | 多執行緒 | 否 | Serial的多執行緒版本,Server模式下首選, 可搭配CMS的新生代收集器 |
| Parallel Scavenge | 新生帶 | 復制演算法 | 多執行緒 | 否 | 目標是達到可控制的吞吐量 |
| Parallel Old | 老年帶 | 標記-整理 | 多執行緒 | 否 | Parallel Scavenge老年代版本,吞吐量優先 |
| CMS | 老年代 | 標記-清除 | 多執行緒 | 是 | 以犧牲吞吐量為代價來獲得最短回收停頓時間的垃圾回收器 |
| G1 | 新生帶 +老年帶 | 標記-整理 + 復制演算法 | 多執行緒 | 是 | JDK1.9默認垃圾收集器 |
- 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://www.uj5u.com/qita/292457.html
標籤:其他
上一篇:輸入一個URL后發生了什么?









