這篇文章主要介紹“JVM的主要組成部分及其作用有哪些”,在日常操作中,相信很多人在JVM的主要組成部分及其作用有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”JVM的主要組成部分及其作用有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
陽曲網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)公司!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站建設(shè)等網(wǎng)站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯(lián)公司自2013年起到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)公司。
JVM包含兩個子系統(tǒng)和兩個組件,兩個子系統(tǒng)為Class loader(類裝載)、Execution engine(執(zhí)行引擎);兩個組件為Runtime data area(運行時數(shù)據(jù)區(qū))、Native Interface(本地接口)。
Class loader(類裝載):根據(jù)給定的全限定名類名(如:java.lang.Object)來裝載class文件到Runtime data area中的method area。
Execution engine(執(zhí)行引擎):執(zhí)行classes中的指令。
Native Interface(本地接口):與native libraries交互,是其它編程語言交互的接口。
Runtime data area(運行時數(shù)據(jù)區(qū)域):這就是我們常說的JVM的內(nèi)存。
作用:首先通過編譯器把 Java 代碼轉(zhuǎn)換成字節(jié)碼,類加載器(ClassLoader)再把字節(jié)碼加載到內(nèi)存中,將其放在運行時數(shù)據(jù)區(qū)(Runtime data area)的方法區(qū)內(nèi),而字節(jié)碼文件只是 JVM 的一套指令集規(guī)范,并不能直接交給底層操作系統(tǒng)去執(zhí)行,因此需要特定的命令解析器執(zhí)行引擎(Execution Engine),將字節(jié)碼翻譯成底層系統(tǒng)指令,再交由 CPU 去執(zhí)行,而這個過程中需要調(diào)用其他語言的本地庫接口(Native Interface)來實現(xiàn)整個程序的功能。
下面是Java程序運行機制詳細(xì)說明
Java程序運行機制步驟
首先利用IDE集成開發(fā)工具編寫Java源代碼,源文件的后綴為.java;
再利用編譯器(javac命令)將源代碼編譯成字節(jié)碼文件,字節(jié)碼文件的后綴名為.class;
運行字節(jié)碼的工作是由解釋器(java命令)來完成的。
從上圖可以看,java文件通過編譯器變成了.class文件,接下來類加載器又將這些.class文件加載到JVM中。 其實可以一句話來解釋:類的加載指的是將類的.class文件中的二進制數(shù)據(jù)讀入到內(nèi)存中,將其放在運行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個 java.lang.Class對象,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。
Java 虛擬機在執(zhí)行 Java 程序的過程中會把它所管理的內(nèi)存區(qū)域劃分為若干個不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自的用途,以及創(chuàng)建和銷毀的時間,有些區(qū)域隨著虛擬機進程的啟動而存在,有些區(qū)域則是依賴線程的啟動和結(jié)束而建立和銷毀。Java 虛擬機所管理的內(nèi)存被劃分為如下幾個區(qū)域:
不同虛擬機的運行時數(shù)據(jù)區(qū)可能略微有所不同,但都會遵從 Java 虛擬機規(guī)范, Java 虛擬機規(guī)范規(guī)定的區(qū)域分為以下 5 個部分:
程序計數(shù)器(Program Counter Register):當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器,字節(jié)碼解析器的工作是通過改變這個計數(shù)器的值,來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能,都需要依賴這個計數(shù)器來完成;
Java 虛擬機棧(Java Virtual Machine Stacks):用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息;
本地方法棧(Native Method Stack):與虛擬機棧的作用是一樣的,只不過虛擬機棧是服務(wù) Java 方法的,而本地方法棧是為虛擬機調(diào)用 Native 方法服務(wù)的;
Java 堆(Java Heap):Java 虛擬機中內(nèi)存最大的一塊,是被所有線程共享的,幾乎所有的對象實例都在這里分配內(nèi)存;
方法區(qū)(Methed Area):用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯后的代碼等數(shù)據(jù)。
淺拷貝(shallowCopy)只是增加了一個指針指向已存在的內(nèi)存地址,
深拷貝(deepCopy)是增加了一個指針并且申請了一個新的內(nèi)存,使這個增加的指針指向這個新的內(nèi)存,
使用深拷貝的情況下,釋放內(nèi)存的時候不會因為出現(xiàn)淺拷貝時釋放同一個內(nèi)存的錯誤。
淺復(fù)制:僅僅是指向被復(fù)制的內(nèi)存地址,如果原地址發(fā)生改變,那么淺復(fù)制出來的對象也會相應(yīng)的改變。
深復(fù)制:在計算機中開辟一塊新的內(nèi)存地址用于存放復(fù)制的對象。
物理地址
堆的物理地址分配對對象是不連續(xù)的。因此性能慢些。在GC的時候也要考慮到不連續(xù)的分配,所以有各種算法。比如,標(biāo)記-消除,復(fù)制,標(biāo)記-壓縮,分代(即新生代使用復(fù)制算法,老年代使用標(biāo)記——壓縮)
棧使用的是數(shù)據(jù)結(jié)構(gòu)中的棧,先進后出的原則,物理地址分配是連續(xù)的。所以性能快。
內(nèi)存分別
堆因為是不連續(xù)的,所以分配的內(nèi)存是在運行期
確認(rèn)的,因此大小不固定。一般堆大小遠(yuǎn)遠(yuǎn)大于棧。
棧是連續(xù)的,所以分配的內(nèi)存大小要在編譯期
就確認(rèn),大小是固定的。
存放的內(nèi)容
堆存放的是對象的實例和數(shù)組。因此該區(qū)更關(guān)注的是數(shù)據(jù)的存儲
棧存放:局部變量,操作數(shù)棧,返回結(jié)果。該區(qū)更關(guān)注的是程序方法的執(zhí)行。
PS:
靜態(tài)變量放在方法區(qū)
靜態(tài)的對象還是放在堆。
程序的可見度
堆對于整個應(yīng)用程序都是共享、可見的。
棧只對于線程是可見的。所以也是線程私有。他的生命周期和線程相同。
隊列和棧都是被用來預(yù)存儲數(shù)據(jù)的。
操作的名稱不同。隊列的插入稱為入隊,隊列的刪除稱為出隊。棧的插入稱為進棧,棧的刪除稱為出棧。
可操作的方式不同。隊列是在隊尾入隊,隊頭出隊,即兩邊都可操作。而棧的進棧和出棧都是在棧頂進行的,無法對棧底直接進行操作。
操作的方法不同。隊列是先進先出(FIFO),即隊列的修改是依先進先出的原則進行的。新來的成員總是加入隊尾(不能從中間插入),每次離開的成員總是隊列頭上(不允許中途離隊)。而棧為后進先出(LIFO),即每次刪除(出棧)的總是當(dāng)前棧中最新的元素,即最后插入(進棧)的元素,而最先插入的被放在棧的底部,要到最后才能刪除。
說到對象的創(chuàng)建,首先讓我們看看 Java
中提供的幾種對象創(chuàng)建方式:
Header | 解釋 |
---|---|
使用new關(guān)鍵字 | 調(diào)用了構(gòu)造函數(shù) |
使用Class的newInstance方法 | 調(diào)用了構(gòu)造函數(shù) |
使用Constructor類的newInstance方法 | 調(diào)用了構(gòu)造函數(shù) |
使用clone方法 | 沒有調(diào)用構(gòu)造函數(shù) |
使用反序列化 | 沒有調(diào)用構(gòu)造函數(shù) |
下面是對象創(chuàng)建的主要流程:
虛擬機遇到一條new指令時,先檢查常量池是否已經(jīng)加載相應(yīng)的類,如果沒有,必須先執(zhí)行相應(yīng)的類加載。類加載通過后,接下來分配內(nèi)存。若Java堆中內(nèi)存是絕對規(guī)整的,使用“指針碰撞“方式分配內(nèi)存;如果不是規(guī)整的,就從空閑列表中分配,叫做”空閑列表“方式。劃分內(nèi)存時還需要考慮一個問題-并發(fā),也有兩種方式: CAS同步處理,或者本地線程分配緩沖(Thread Local Allocation Buffer, TLAB)。然后內(nèi)存空間初始化操作,接著是做一些必要的對象設(shè)置(元信息、哈希碼…),最后執(zhí)行<init>
方法。
類加載完成后,接著會在Java堆中劃分一塊內(nèi)存分配給對象。內(nèi)存分配根據(jù)Java堆是否規(guī)整,有兩種方式:
指針碰撞:如果Java堆的內(nèi)存是規(guī)整,即所有用過的內(nèi)存放在一邊,而空閑的的放在另一邊。分配內(nèi)存時將位于中間的指針指示器向空閑的內(nèi)存移動一段與對象大小相等的距離,這樣便完成分配內(nèi)存工作。
空閑列表:如果Java堆的內(nèi)存不是規(guī)整的,則需要由虛擬機維護一個列表來記錄那些內(nèi)存是可用的,這樣在分配的時候可以從列表中查詢到足夠大的內(nèi)存分配給對象,并在分配后更新列表記錄。
選擇哪種分配方式是由 Java 堆是否規(guī)整來決定的,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
對象的創(chuàng)建在虛擬機中是一個非常頻繁的行為,哪怕只是修改一個指針?biāo)赶虻奈恢?,在并發(fā)情況下也是不安全的,可能出現(xiàn)正在給對象 A 分配內(nèi)存,指針還沒來得及修改,對象 B 又同時使用了原來的指針來分配內(nèi)存的情況。解決這個問題有兩種方案:
對分配內(nèi)存空間的動作進行同步處理(采用 CAS + 失敗重試來保障更新操作的原子性);
把內(nèi)存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在 Java 堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer, TLAB)。哪個線程要分配內(nèi)存,就在哪個線程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 時,才需要同步鎖。通過-XX:+/-UserTLAB參數(shù)來設(shè)定虛擬機是否使用TLAB。
Java
程序需要通過 JVM
棧上的引用訪問堆中的具體對象。對象的訪問方式取決于 JVM
虛擬機的實現(xiàn)。目前主流的訪問方式有 句柄和 直接指針兩種方式。
指針:指向?qū)ο?,代表一個對象在內(nèi)存中的起始地址。
句柄:可以理解為指向指針的指針,維護著對象的指針。句柄不直接指向?qū)ο?,而是指向?qū)ο蟮闹羔槪ň浔话l(fā)生變化,指向固定內(nèi)存地址),再由對象的指針指向?qū)ο蟮恼鎸崈?nèi)存地址。
Java
堆中劃分出一塊內(nèi)存來作為句柄池,引用中存儲對象的句柄地址,而句柄中包含了對象實例數(shù)據(jù)與對象類型數(shù)據(jù)各自的具體地址信息,具體構(gòu)造如下圖所示:
優(yōu)勢:引用中存儲的是穩(wěn)定的句柄地址,在對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中的實例數(shù)據(jù)指針,而引用本身不需要修改。
如果使用直接指針訪問,引用中存儲的直接就是對象地址,那么Java
堆對象內(nèi)部的布局中就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息。
優(yōu)勢:速度更快,節(jié)省了一次指針定位的時間開銷。由于對象的訪問在Java
中非常頻繁,因此這類開銷積少成多后也是非常可觀的執(zhí)行成本。HotSpot 中采用的就是這種方式。
內(nèi)存泄漏是指不再被使用的對象或者變量一直被占據(jù)在內(nèi)存中。理論上來說,Java是有GC垃圾回收機制的,也就是說,不再被使用的對象,會被GC自動回收掉,自動從內(nèi)存中清除。
但是,即使這樣,Java也還是存在著內(nèi)存泄漏的情況,java導(dǎo)致內(nèi)存泄露的原因很明確:長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄露,盡管短生命周期對象已經(jīng)不再需要,但是因為長生命周期對象持有它的引用而導(dǎo)致不能被回收,這就是java中內(nèi)存泄露的發(fā)生場景。
在java中,程序員是不需要顯示的去釋放一個對象的內(nèi)存的,而是由虛擬機自行執(zhí)行。在JVM中,有一個垃圾回收線程,它是低優(yōu)先級的,在正常情況下是不會執(zhí)行的,只有在虛擬機空閑或者當(dāng)前堆內(nèi)存不足時,才會觸發(fā)執(zhí)行,掃面那些沒有被任何引用的對象,并將它們添加到要回收的集合中,進行回收。
GC 是垃圾收集的意思(Gabage Collection),內(nèi)存處理是編程人員容易出現(xiàn)問題的地方,忘記或者錯誤的內(nèi)存
回收會導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java 提供的 GC 功能可以自動監(jiān)測對象是否超過作用域從而達(dá)到自動
回收內(nèi)存的目的,Java 語言沒有提供釋放已分配內(nèi)存的顯示操作方法。
java語言最顯著的特點就是引入了垃圾回收機制,它使java程序員在編寫程序時不再考慮內(nèi)存管理的問題。
由于有這個垃圾回收機制,java中的對象不再有“作用域”的概念,只有引用的對象才有“作用域”。
垃圾回收機制有效的防止了內(nèi)存泄露,可以有效的使用可使用的內(nèi)存。
垃圾回收器通常作為一個單獨的低級別的線程運行,在不可預(yù)知的情況下對內(nèi)存堆中已經(jīng)死亡的或很長時間沒有用過的對象進行清除和回收。
程序員不能實時的對某個對象或所有對象調(diào)用垃圾回收器進行垃圾回收。
垃圾回收有分代復(fù)制垃圾回收、標(biāo)記垃圾回收、增量垃圾回收。
對于GC來說,當(dāng)程序員創(chuàng)建對象時,GC就開始監(jiān)控這個對象的地址、大小以及使用情況。
通常,GC采用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是"可達(dá)的",哪些對象是"不可達(dá)的"。當(dāng)GC確定一些對象為"不可達(dá)"時,GC就有責(zé)任回收這些內(nèi)存空間。
可以。程序員可以手動執(zhí)行System.gc(),通知GC運行,但是Java語言規(guī)范并不保證GC一定會執(zhí)行。
強引用:發(fā)生 gc 的時候不會被回收。
軟引用:有用但不是必須的對象,在發(fā)生內(nèi)存溢出之前會被回收。
弱引用:有用但不是必須的對象,在下一次GC時會被回收。
虛引用(幽靈引用/幻影引用):無法通過虛引用獲得對象,用 PhantomReference 實現(xiàn)虛引用,虛引用的用途是在 gc 時返回一個通知。
垃圾收集器在做垃圾回收的時候,首先需要判定的就是哪些內(nèi)存是需要被回收的,哪些對象是「存活」的,是不可以被回收的;哪些對象已經(jīng)「死掉」了,需要被回收。
一般有兩種方法來判斷:
引用計數(shù)器法:為每個對象創(chuàng)建一個引用計數(shù),有對象引用時計數(shù)器 +1,引用被釋放時計數(shù) -1,當(dāng)計數(shù)器為 0 時就可以被回收。它有一個缺點不能解決循環(huán)引用的問題;
可達(dá)性分析算法:從 GC Roots 開始向下搜索,搜索所走過的路徑稱為引用鏈。當(dāng)一個對象到 GC Roots 沒有任何引用鏈相連時,則證明此對象是可以被回收的。
當(dāng)對象對當(dāng)前使用這個對象的應(yīng)用程序變得不可觸及的時候,這個對象就可以被回收了。 垃圾回收不會發(fā)生在永久代,如果永久代滿了或者是超過了臨界值,會觸發(fā)完全垃圾回收(Full GC)。如果你仔細(xì)查看垃圾收集器的輸出信息,就會發(fā)現(xiàn)永久代也是被回收的。這就是為什么正確的永久代大小對避免Full GC是非常重要的原因。
垃圾回收不會發(fā)生在永久代,如果永久代滿了或者是超過了臨界值,會觸發(fā)完全垃圾回收(Full GC)。如果你仔細(xì)查看垃圾收集器的輸出信息,就會發(fā)現(xiàn)永久代也是被回收的。這就是為什么正確的永久代大小對避免Full GC是非常重要的原因。請參考下Java8:從永久代到元數(shù)據(jù)區(qū) (譯者注:Java8中已經(jīng)移除了永久代,新加了一個叫做元數(shù)據(jù)區(qū)的native內(nèi)存區(qū))
標(biāo)記-清除算法:標(biāo)記無用對象,然后進行清除回收。缺點:效率不高,無法清除垃圾碎片。
復(fù)制算法:按照容量劃分二個大小相等的內(nèi)存區(qū)域,當(dāng)一塊用完的時候?qū)⒒钪膶ο髲?fù)制到另一塊上,然后再把已使用的內(nèi)存空間一次清理掉。缺點:內(nèi)存使用率不高,只有原來的一半。
標(biāo)記-整理算法:標(biāo)記無用對象,讓所有存活的對象都向一端移動,然后直接清除掉端邊界以外的內(nèi)存。
分代算法:根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊,一般是新生代和老年代,新生代基本采用復(fù)制算法,老年代采用標(biāo)記整理算法。
標(biāo)記無用對象,然后進行清除回收。
標(biāo)記-清除算法(Mark-Sweep)是一種常見的基礎(chǔ)垃圾收集算法,它將垃圾收集分為兩個階段:
標(biāo)記階段:標(biāo)記出可以回收的對象。
清除階段:回收被標(biāo)記的對象所占用的空間。
標(biāo)記-清除算法之所以是基礎(chǔ)的,是因為后面講到的垃圾收集算法都是在此算法的基礎(chǔ)上進行改進的。
優(yōu)點:實現(xiàn)簡單,不需要對象進行移動。
缺點:標(biāo)記、清除過程效率低,產(chǎn)生大量不連續(xù)的內(nèi)存碎片,提高了垃圾回收的頻率。
標(biāo)記-清除算法的執(zhí)行的過程如下圖所示
為了解決標(biāo)記-清除算法的效率不高的問題,產(chǎn)生了復(fù)制算法。它把內(nèi)存空間劃為兩個相等的區(qū)域,每次只使用其中一個區(qū)域。垃圾收集時,遍歷當(dāng)前使用的區(qū)域,把存活對象復(fù)制到另外一個區(qū)域中,最后將當(dāng)前使用的區(qū)域的可回收的對象進行回收。
優(yōu)點:按順序分配內(nèi)存即可,實現(xiàn)簡單、運行高效,不用考慮內(nèi)存碎片。
缺點:可用的內(nèi)存大小縮小為原來的一半,對象存活率高時會頻繁進行復(fù)制。
復(fù)制算法的執(zhí)行過程如下圖所示
在新生代中可以使用復(fù)制算法,但是在老年代就不能選擇復(fù)制算法了,因為老年代的對象存活率會較高,這樣會有較多的復(fù)制操作,導(dǎo)致效率變低。標(biāo)記-清除算法可以應(yīng)用在老年代中,但是它效率不高,在內(nèi)存回收后容易產(chǎn)生大量內(nèi)存碎片。因此就出現(xiàn)了一種標(biāo)記-整理算法(Mark-Compact)算法,與標(biāo)記-整理算法不同的是,在標(biāo)記可回收的對象后將所有存活的對象壓縮到內(nèi)存的一端,使他們緊湊的排列在一起,然后對端邊界以外的內(nèi)存進行回收?;厥蘸?,已用和未用的內(nèi)存都各自一邊。
優(yōu)點:解決了標(biāo)記-清理算法存在的內(nèi)存碎片問題。
缺點:仍需要進行局部對象移動,一定程度上降低了效率。
標(biāo)記-整理算法的執(zhí)行過程如下圖所示
當(dāng)前商業(yè)虛擬機都采用分代收集的垃圾收集算法。分代收集算法,顧名思義是根據(jù)對象的存活周期將內(nèi)存劃分為幾塊。一般包括年輕代、老年代和 永久代,如圖所示:
如果說垃圾收集算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實現(xiàn)。下圖展示了7種作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,還有用于回收整個Java堆的G1收集器。不同收集器之間的連線表示它們可以搭配使用。
Serial收集器(復(fù)制算法): 新生代單線程收集器,標(biāo)記和清理都是單線程,優(yōu)點是簡單高效;
ParNew收集器 (復(fù)制算法): 新生代收并行集器,實際上是Serial收集器的多線程版本,在多核CPU環(huán)境下有著比Serial更好的表現(xiàn);
Parallel Scavenge收集器 (復(fù)制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用戶線程時間/(用戶線程時間+GC線程時間),高吞吐量可以高效率的利用CPU時間,盡快完成程序的運算任務(wù),適合后臺應(yīng)用等對交互相應(yīng)要求不高的場景;
Serial Old收集器 (標(biāo)記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;
Parallel Old收集器 (標(biāo)記-整理算法): 老年代并行收集器,吞吐量優(yōu)先,Parallel Scavenge收集器的老年代版本;
CMS(Concurrent Mark Sweep)收集器(標(biāo)記-清除算法): 老年代并行收集器,以獲取最短回收停頓時間為目標(biāo)的收集器,具有高并發(fā)、低停頓的特點,追求最短GC回收停頓時間。
G1(Garbage First)收集器 (標(biāo)記-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基于“標(biāo)記-整理”算法實現(xiàn),也就是說不會產(chǎn)生內(nèi)存碎片。此外,G1收集器不同于之前的收集器的一個重要特點是:G1回收的范圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的范圍僅限于新生代或老年代。
CMS 是英文 Concurrent Mark-Sweep 的簡稱,是以犧牲吞吐量為代價來獲得最短回收停頓時間的垃圾回收器。對于要求服務(wù)器響應(yīng)速度的應(yīng)用上,這種垃圾回收器非常適合。在啟動 JVM 的參數(shù)加上“-XX:+UseConcMarkSweepGC”來指定使用 CMS 垃圾回收器。
CMS 使用的是標(biāo)記-清除的算法實現(xiàn)的,所以在 gc 的時候回產(chǎn)生大量的內(nèi)存碎片,當(dāng)剩余內(nèi)存不能滿足程序運行要求時,系統(tǒng)將會出現(xiàn) Concurrent Mode Failure,臨時 CMS 會采用 Serial Old 回收器進行垃圾清除,此時的性能將會被降低。
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1
新生代垃圾回收器一般采用的是復(fù)制算法,復(fù)制算法的優(yōu)點是效率高,缺點是內(nèi)存利用率低;老年代回收器一般采用的是標(biāo)記-整理的算法進行垃圾回收。
分代回收器有兩個分區(qū):老生代和新生代,新生代默認(rèn)的空間占比總空間的 1/3,老生代的默認(rèn)占比是 2/3。
新生代使用的是復(fù)制算法,新生代里有 3 個分區(qū):Eden、To Survivor、From Survivor,它們的默認(rèn)占比是 8:1:1,它的執(zhí)行流程如下:
把 Eden + From Survivor 存活的對象放入 To Survivor 區(qū);
清空 Eden 和 From Survivor 分區(qū);
From Survivor 和 To Survivor 分區(qū)交換,F(xiàn)rom Survivor 變 To Survivor,To Survivor 變 From Survivor。
每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當(dāng)年齡到達(dá) 15(默認(rèn)配置是 15)時,升級為老生代。大對象也會直接進入老生代。
老生代當(dāng)空間占用到達(dá)某個值之后就會觸發(fā)全局垃圾收回,一般使用標(biāo)記整理的執(zhí)行算法。以上這些循環(huán)往復(fù)就構(gòu)成了整個分代垃圾回收的整體執(zhí)行流程。
所謂自動內(nèi)存管理,最終要解決的也就是內(nèi)存分配和內(nèi)存回收兩個問題。前面我們介紹了內(nèi)存回收,這里我們再來聊聊內(nèi)存分配。
對象的內(nèi)存分配通常是在 Java 堆上分配(隨著虛擬機優(yōu)化技術(shù)的誕生,某些場景下也會在棧上分配,后面會詳細(xì)介紹),對象主要分配在新生代的 Eden 區(qū),如果啟動了本地線程緩沖,將按照線程優(yōu)先在 TLAB 上分配。少數(shù)情況下也會直接在老年代上分配??偟膩碚f分配規(guī)則不是百分百固定的,其細(xì)節(jié)取決于哪一種垃圾收集器組合以及虛擬機相關(guān)參數(shù)有關(guān),但是虛擬機對于內(nèi)存的分配還是會遵循以下幾種「普世」規(guī)則:
多數(shù)情況,對象都在新生代 Eden 區(qū)分配。當(dāng) Eden 區(qū)分配沒有足夠的空間進行分配時,虛擬機將會發(fā)起一次 Minor GC。如果本次 GC 后還是沒有足夠的空間,則將啟用分配擔(dān)保機制在老年代中分配內(nèi)存。
這里我們提到 Minor GC,如果你仔細(xì)觀察過 GC 日常,通常我們還能從日志中發(fā)現(xiàn) Major GC/Full GC。
Minor GC是指發(fā)生在新生代的 GC,因為 Java 對象大多都是朝生夕死,所有 Minor GC 非常頻繁,一般回收速度也非???;
Major GC/Full GC是指發(fā)生在老年代的 GC,出現(xiàn)了 Major GC 通常會伴隨至少一次 Minor GC。Major GC 的速度通常會比 Minor GC 慢 10 倍以上。
所謂大對象是指需要大量連續(xù)內(nèi)存空間的對象,頻繁出現(xiàn)大對象是致命的,會導(dǎo)致在內(nèi)存還有不少空間的情況下提前觸發(fā) GC 以獲取足夠的連續(xù)空間來安置新對象。
前面我們介紹過新生代使用的是標(biāo)記-清除算法來處理垃圾回收的,如果大對象直接在新生代分配就會導(dǎo)致 Eden 區(qū)和兩個 Survivor 區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。因此對于大對象都會直接在老年代進行分配。
虛擬機采用分代收集的思想來管理內(nèi)存,那么內(nèi)存回收時就必須判斷哪些對象應(yīng)該放在新生代,哪些對象應(yīng)該放在老年代。因此虛擬機給每個對象定義了一個對象年齡的計數(shù)器,如果對象在 Eden 區(qū)出生,并且能夠被 Survivor 容納,將被移動到 Survivor 空間中,這時設(shè)置對象年齡為 1。對象在 Survivor 區(qū)中每「熬過」一次 Minor GC 年齡就加 1,當(dāng)年齡達(dá)到一定程度(默認(rèn) 15) 就會被晉升到老年代。
虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗,解析和初始化,最終形成可以被虛擬機直接使用的java類型。
Java中的所有類,都需要由類加載器裝載到JVM中才能運行。類加載器本身也是一個類,而它的工作就是把class文件從硬盤讀取到內(nèi)存中。在寫程序的時候,我們幾乎不需要關(guān)心類的加載,因為這些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。
類裝載方式,有兩種 :
1.隱式裝載, 程序在運行過程中當(dāng)碰到通過new 等方式生成對象時,隱式調(diào)用類裝載器加載對應(yīng)的類到j(luò)vm中,
2.顯式裝載, 通過class.forname()等方法,顯式加載需要的類
Java類的加載是動態(tài)的,它并不會一次性將所有類全部加載后再運行,而是保證程序運行的基礎(chǔ)類(像是基類)完全加載到j(luò)vm中,至于其他類,則在需要的時候才加載。這當(dāng)然就是為了節(jié)省內(nèi)存開銷。
實現(xiàn)通過類的權(quán)限定名獲取該類的二進制字節(jié)流的代碼塊叫做類加載器。
主要有一下四種類加載器:
啟動類加載器(Bootstrap ClassLoader)用來加載java核心類庫,無法被java程序直接引用。
擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現(xiàn)會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。
系統(tǒng)類加載器(system class loader):它根據(jù) Java 應(yīng)用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應(yīng)用的類都是由它來完成加載的??梢酝ㄟ^ ClassLoader.getSystemClassLoader()來獲取它。
用戶自定義類加載器,通過繼承 java.lang.ClassLoader類的方式實現(xiàn)。
類裝載分為以下 5 個步驟:
加載:根據(jù)查找路徑找到相應(yīng)的 class 文件然后導(dǎo)入;
驗證:檢查加載的 class 文件的正確性;
準(zhǔn)備:給類中的靜態(tài)變量分配內(nèi)存空間;
解析:虛擬機將常量池中的符號引用替換成直接引用的過程。符號引用就理解為一個標(biāo)示,而在直接引用直接指向內(nèi)存中的地址;
初始化:對靜態(tài)變量和靜態(tài)代碼塊執(zhí)行初始化工作。
在介紹雙親委派模型之前先說下類加載器。對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立在 JVM 中的唯一性,每一個類加載器,都有一個獨立的類名稱空間。類加載器就是根據(jù)指定全限定名稱將 class 文件加載到 JVM 內(nèi)存,然后再轉(zhuǎn)化為 class 對象。
類加載器分類:
啟動類加載器(Bootstrap ClassLoader),是虛擬機自身的一部分,用來加載Java_HOME/lib/目錄中的,或者被 -Xbootclasspath 參數(shù)所指定的路徑中并且被虛擬機識別的類庫;
其他類加載器:
擴展類加載器(Extension ClassLoader):負(fù)責(zé)加載\lib\ext目錄或Java. ext. dirs系統(tǒng)變量指定的路徑中的所有類庫;
應(yīng)用程序類加載器(Application ClassLoader)。負(fù)責(zé)加載用戶類路徑(classpath)上的指定類庫,我們可以直接使用這個類加載器。一般情況,如果我們沒有自定義類加載器默認(rèn)就是用這個加載器。
雙親委派模型:如果一個類加載器收到了類加載的請求,它首先不會自己去加載這個類,而是把這個請求委派給父類加載器去完成,每一層的類加載器都是如此,這樣所有的加載請求都會被傳送到頂層的啟動類加載器中,只有當(dāng)父加載無法完成加載請求(它的搜索范圍中沒找到所需的類)時,子加載器才會嘗試去加載類。
當(dāng)一個類收到了類加載請求時,不會自己先去加載這個類,而是將其委派給父類,由父類去加載,如果此時父類不能加載,反饋給子類,由子類去完成類的加載。
JDK 自帶了很多監(jiān)控工具,都位于 JDK 的 bin 目錄下,其中最常用的是 jconsole 和 jvisualvm 這兩款視圖監(jiān)控工具。
jconsole:用于對 JVM 中的內(nèi)存、線程和類等進行監(jiān)控;
jvisualvm:JDK 自帶的全能分析工具,可以分析:內(nèi)存快照、線程快照、程序死鎖、監(jiān)控內(nèi)存的變化、gc 變化等。
-Xms2g:初始化推大小為 2g;
-Xmx2g:堆最大內(nèi)存為 2g;
-XX:NewRatio=4:設(shè)置年輕的和老年代的內(nèi)存比例為 1:4;
-XX:SurvivorRatio=8:設(shè)置新生代 Eden 和 Survivor 比例為 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;
-XX:+PrintGC:開啟打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 詳細(xì)信息。
到此,關(guān)于“JVM的主要組成部分及其作用有哪些”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
分享名稱:JVM的主要組成部分及其作用有哪些
本文地址:http://www.rwnh.cn/article38/ipcgpp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營銷型網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計公司、微信公眾號、用戶體驗、網(wǎng)站維護、面包屑導(dǎo)航
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)