資深架構(gòu)師一篇文章帶你深入淺出JVM腾誉!

大家都知道,Java中JVM的重要性组砚,學(xué)習(xí)了JVM你對Java的運行機(jī)制吻商、編譯過程和如何對Java程序進(jìn)行調(diào)優(yōu)相信都會有一個很好的認(rèn)知。

前言

JVM是Java Virtual Machine(Java 虛擬機(jī))的縮寫糟红,JVM是一種用于計算設(shè)備的規(guī)范艾帐,它是一個虛構(gòu)出來的計算機(jī),是通過在實際的計算機(jī)上仿真模擬各種計算機(jī)功能來實現(xiàn)的盆偿。

Java語言的一個非常重要的特點就是平臺無關(guān)性柒爸。而使用Java虛擬機(jī)是實現(xiàn)這一特點的關(guān)鍵。一般的高級語言如果要在不同的平臺上運行事扭,至少需要編譯成不同的目標(biāo)代碼捎稚。而引入Java語言虛擬機(jī)后,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用Java虛擬機(jī)屏蔽了與具體平臺相關(guān)的信息阳藻,使得Java語言編譯程序只需生成在Java虛擬機(jī)上運行的目標(biāo)代碼(字節(jié)碼)晰奖,就可以在多種平臺上不加修改地運行。Java虛擬機(jī)在執(zhí)行字節(jié)碼時腥泥,把字節(jié)碼解釋成具體平臺上的機(jī)器指令執(zhí)行匾南。這就是Java的能夠“一次編譯,到處運行”的原因蛔外。

JVM概述

JVM總體上是由類裝載子系統(tǒng)(ClassLoader)蛆楞、運行時數(shù)據(jù)區(qū)、執(zhí)行引擎夹厌、垃圾收集這四個部分組成豹爹。其中我們最為關(guān)注的運行時數(shù)據(jù)區(qū),也就是JVM的內(nèi)存部分則是由方法區(qū)(Method Area)矛纹、JAVA堆(Java Heap)臂聋、虛擬機(jī)棧(JVM Stack)、程序計數(shù)器或南、本地方法棧(Native Method Stack)這幾部分組成孩等。

JVM體系結(jié)構(gòu)


類裝載子系統(tǒng)

Class Loader類加載器負(fù)責(zé)加載.class文件,class文件在文件開頭有特定的文件標(biāo)示采够,并且ClassLoader負(fù)責(zé)class文件的加載等肄方,至于它是否可以運行,則由Execution Engine決定蹬癌。

?運行時數(shù)據(jù)區(qū)

棧管運行权她,堆管存儲。JVM調(diào)優(yōu)主要是優(yōu)化Java堆和方法區(qū)逝薪。

? ?方法區(qū)(Method Area)

方法區(qū)是各線程共享的內(nèi)存區(qū)域隅要,它用于存儲已被JVM加載的類信息、常量董济、靜態(tài)變量步清、運行時常量池等數(shù)據(jù)。

? ?Java堆(Java Heap)

Java堆是各線程共享的內(nèi)存區(qū)域感局,在JVM啟動時創(chuàng)建,這塊區(qū)域是JVM中最大的暂衡, 用于存儲應(yīng)用的對象和數(shù)組询微,也是GC主要的回收區(qū),一個 JVM 實例只存在一個堆內(nèi)存狂巢,堆內(nèi)存的大小是可以調(diào)節(jié)的撑毛。類加載器讀取了類文件后,需要把類、方法藻雌、常變量放到堆內(nèi)存中雌续,以方便執(zhí)行器執(zhí)行,堆內(nèi)存分為三部分:新生代胯杭、老年代驯杜、永久代。

說明:

Jdk1.6及之前:常量池分配在永久代 做个。

Jdk1.7:有鸽心,但已經(jīng)逐步“去永久代” 。

Jdk1.8及之后:無永久代居暖,改用元空間代替(java.lang.OutOfMemoryError: PermGen space,這種錯誤將不會出現(xiàn)在JDK1.8中)顽频。

? ? Java棧(JVM Stack)

?1) 棧是什么

Java棧是線程私有的,是在線程創(chuàng)建時創(chuàng)建太闺,它的生命期是跟隨線程的生命期糯景,線程結(jié)束棧內(nèi)存也就釋放,對于棧來說不存在垃圾回收問題省骂,只要線程一結(jié)束該棧就Over蟀淮,生命周期和線程一致〖窖纾基本類型的變量和對象的引用變量都是在函數(shù)的棧內(nèi)存中分配灭贷。

2) 棧存儲什么

每個方法執(zhí)行的時候都會創(chuàng)建一個棧幀,棧幀中主要存儲3類數(shù)據(jù):

局部變量表:輸入?yún)?shù)和輸出參數(shù)以及方法內(nèi)的變量略贮;

棧操作:記錄出棧和入棧的操作甚疟;

棧幀數(shù)據(jù):包括類文件、方法等等逃延。

3) 棧運行原理

棧中的數(shù)據(jù)都是以棧幀的格式存在览妖,棧幀是一個內(nèi)存區(qū)塊,是一個數(shù)據(jù)集揽祥,是一個有關(guān)方法和運行期數(shù)據(jù)的數(shù)據(jù)集讽膏。每一個方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在棧中從入棧到出棧的過程拄丰。


4) 本地方法棧(Native Method Stack)

本地方法棧和JVM棧發(fā)揮的作用非常相似府树,也是線程私有的,區(qū)別是JVM棧為JVM執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)料按,而本地方法棧為JVM使用到的Native方法服務(wù)奄侠。它的具體做法是在本地方法棧中登記native方法,在執(zhí)行引擎執(zhí)行時加載Native Liberies.有的虛擬機(jī)(比如Sun Hotpot)直接把兩者合二為一载矿。

5) 程序計數(shù)器(Program Counter Register)

程序計數(shù)器是一塊非常小的內(nèi)存空間垄潮,幾乎可以忽略不計,每個線程都有一個程序計算器,是線程私有的弯洗,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器旅急,指向方法區(qū)中的方法字節(jié)碼(下一個將要執(zhí)行的指令代碼),由執(zhí)行引擎讀取下一條指令牡整。

6) 運行時常量池

運行時常量池是方法區(qū)的一部分藐吮,用于存放編譯器生成的各種字面量和符號引用导匣,這部分內(nèi)容將在類加載后存放到方法區(qū)的運行時常量池中集侯。相較于Class文件常量池,運行時常量池更具動態(tài)性裆装,在運行期間也可以將新的變量放入常量池中秋泳,而不是一定要在編譯時確定的常量才能放入潦闲。最主要的運用便是String類的intern()方法。


執(zhí)行引擎(Execution Engine)

執(zhí)行引擎執(zhí)行包在裝載類的方法中的指令迫皱,也就是方法歉闰。執(zhí)行引擎以指令為單位讀取Java字節(jié)碼。它就像一個CPU一樣卓起,一條一條地執(zhí)行機(jī)器指令和敬。每個字節(jié)碼指令都由一個1字節(jié)的操作碼和附加的操作數(shù)組成。執(zhí)行引擎取得一個操作碼戏阅,然后根據(jù)操作數(shù)來執(zhí)行任務(wù)昼弟,完成后就繼續(xù)執(zhí)行下一條操作碼。

不過Java字節(jié)碼是用一種人類可以讀懂的語言編寫的奕筐,而不是用機(jī)器可以直接執(zhí)行的語言舱痘。因此,執(zhí)行引擎必須把字節(jié)碼轉(zhuǎn)換成可以直接被JVM執(zhí)行的語言离赫。字節(jié)碼可以通過以下兩種方式轉(zhuǎn)換成合適的語言:

解釋器: 一條一條地讀取芭逝,解釋并執(zhí)行字節(jié)碼執(zhí)行,所以它可以很快地解釋字節(jié)碼渊胸,但是執(zhí)行起來會比較慢旬盯。這是解釋執(zhí)行語言的一個缺點。

即時編譯器:用來彌補(bǔ)解釋器的缺點翎猛,執(zhí)行引擎首先按照解釋執(zhí)行的方式來執(zhí)行胖翰,然后在合適的時候,即時編譯器把整段字節(jié)碼編譯成本地代碼切厘。然后萨咳,執(zhí)行引擎就沒有必要再去解釋執(zhí)行方法了,它可以直接通過本地代碼去執(zhí)行迂卢。執(zhí)行本地代碼比一條一條進(jìn)行解釋執(zhí)行的速度快很多某弦,編譯后的代碼可以執(zhí)行的很快,因為本地代碼是保存在緩存里的而克。


?垃圾收集(Garbage Collection, GC)

? ? 什么是垃圾收集

垃圾收集即垃圾回收靶壮,簡單的說垃圾回收就是回收內(nèi)存中不再使用的對象。所謂使用中的對象(已引用對象)员萍,指的是程序中有指針指向的對象腾降;而未使用中的對象(未引用對象),則沒有被任何指針給指向碎绎,因此占用的內(nèi)存也可以被回收掉螃壤。

垃圾回收的基本步驟分兩步:

查找內(nèi)存中不再使用的對象(GC判斷策略)

釋放這些對象占用的內(nèi)存(GC收集算法)

? ?GC判斷策略

1) 引用計數(shù)算法

引用計數(shù)算法是給對象添加一個引用計數(shù)器,每當(dāng)有一個引用它時筋帖,計數(shù)器值就加1奸晴;當(dāng)引用失效時,計數(shù)器值就減1日麸;任何時刻計數(shù)器都為0的對象就是不可能再被使用的對象寄啼。缺點:很難解決對象之間相互循環(huán)引用的問題。

2) 根搜索算法

根搜索算法的基本思路就是通過一系列名為“GC Roots”的對象作為起始點代箭,從這些節(jié)點開始向下搜索墩划,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個對象到GC Roots沒有任何引用鏈相連(也就是說從GC Roots到這個對象不可達(dá))時嗡综,則證明此對象是不可用的乙帮。

在Java語言里,可作為GC Roots的對象包括以下幾種:

虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象极景;

方法區(qū)中類靜態(tài)屬性引用的對象察净;

方法區(qū)中常量應(yīng)用的對象;

本地方法棧中JNI(Native方法)引用的對象戴陡。


注:在根搜索算法中不可達(dá)的對象塞绿,也并非是“非死不可”的,因為要真正宣告一個對象死亡恤批,至少要經(jīng)歷兩次標(biāo)記過程:第一次是標(biāo)記沒有與GC Roots相連接的引用鏈异吻;第二次是GC對在F-Queue執(zhí)行隊列中的對象進(jìn)行的小規(guī)模標(biāo)記(對象需要覆蓋finalize()方法且沒被調(diào)用過)。

? ?GC收集算法

1) 標(biāo)記-清除算法(Mark-Sweep)

標(biāo)記-清楚算法采用從根集合(GC Roots)進(jìn)行掃描喜庞,首先標(biāo)記出所有需要回收的對象(根搜索算法)诀浪,標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對象。


該算法有兩個問題:

效率問題:標(biāo)記和清除過程的效率都不高延都;

空間問題:標(biāo)記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片, 空間碎片太多可能會導(dǎo)致在運行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集雷猪。

2) 復(fù)制算法(Copying)

復(fù)制算法是將可用內(nèi)存按容量劃分為大小相等的兩塊, 每次只用其中一塊, 當(dāng)這一塊的內(nèi)存用完, 就將還存活的對象復(fù)制到另外一塊上面, 然后把已使用過的內(nèi)存空間一次清理掉。


3) 標(biāo)記-整理算法(Mark-Compact)

標(biāo)記整理算法的標(biāo)記過程與標(biāo)記清除算法相同, 但后續(xù)步驟不再對可回收對象直接清理, 而是讓所有存活的對象都向一端移動,然后清理掉端邊界以外的內(nèi)存晰房。


4) 分代收集算法(Generational Collection)

分代收集算法是目前大部分JVM的垃圾收集器采用的算法求摇。它的核心思想是根據(jù)對象存活的生命周期將內(nèi)存劃分為若干個不同的區(qū)域射沟。一般情況下將堆區(qū)劃分為老年代(Tenured Generation)和新生代(Young Generation),在堆區(qū)之外還有一個代就是永久代(Permanet Generation)与境。老年代的特點是每次垃圾收集時只有少量對象需要被回收验夯,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那么就可以根據(jù)不同代的特點采取最適合的收集算法摔刁。


新生代(Young Generation)的回收算法(以復(fù)制算法為主)

所有新生成的對象首先都是放在年輕代的挥转。年輕代的目標(biāo)就是盡可能快速的收集掉那些生命周期短的對象。

新生代內(nèi)存按照8:1:1的比例分為一個eden區(qū)和兩個survivor(survivor0,survivor1)區(qū)共屈。一個Eden區(qū)绑谣,兩個 Survivor區(qū)(一般而言)。大部分對象在Eden區(qū)中生成拗引〗柘回收時先將eden區(qū)存活對象復(fù)制到一個survivor0區(qū),然后清空eden區(qū)矾削,當(dāng)這個survivor0區(qū)也存放滿了時暇务,則將eden區(qū)和survivor0區(qū)存活對象復(fù)制到另一個survivor1區(qū),然后清空eden和這個survivor0區(qū)怔软,此時survivor0區(qū)是空的垦细,然后將survivor0區(qū)和survivor1區(qū)交換,即保持survivor1區(qū)為空挡逼, 如此往復(fù)括改。

當(dāng)survivor1區(qū)不足以存放 eden和survivor0的存活對象時,就將存活對象直接存放到老年代家坎。若是老年代也滿了就會觸發(fā)一次Full GC(Major GC)嘱能,也就是新生代、老年代都進(jìn)行回收虱疏。

新生代發(fā)生的GC也叫做Minor GC惹骂,MinorGC發(fā)生頻率比較高(不一定等Eden區(qū)滿了才觸發(fā))。

老年代(Tenured Generation)的回收算法(以標(biāo)記-清除做瞪、標(biāo)記-整理為主)

在年輕代中經(jīng)歷了N次垃圾回收后仍然存活的對象对粪,就會被放到老年代中。因此装蓬,可以認(rèn)為老年代中存放的都是一些生命周期較長的對象著拭。

內(nèi)存比新生代也大很多(大概比例是1:2),當(dāng)老年代內(nèi)存滿時觸發(fā)Major GC即Full GC牍帚,F(xiàn)ull GC發(fā)生頻率比較低儡遮,老年代對象存活時間比較長,存活率標(biāo)記高暗赶。

永久代(Permanet Generation)的回收算法

用于存放靜態(tài)文件鄙币,如Java類肃叶、方法等。永久代對垃圾回收沒有顯著影響十嘿,但是有些應(yīng)用可能動態(tài)生成或者調(diào)用一些class被环,例如Hibernate 等,在這種時候需要設(shè)置一個比較大的永久代空間來存放這些運行過程中新增的類详幽。永久代也稱方法區(qū)。方法區(qū)主要回收的內(nèi)容有:廢棄常量和無用的類浸锨。對于廢棄常量也可通過根搜索算法來判斷唇聘,但是對于無用的類則需要同時滿足下面3個條件:

該類所有的實例都已經(jīng)被回收,也就是Java堆中不存在該類的任何實例柱搜;

加載該類的ClassLoader已經(jīng)被回收迟郎;

該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法聪蘸。

3.4.4 垃圾收集器

1) Serial收集器(復(fù)制算法)

新生代單線程收集器宪肖,標(biāo)記和清理都是單線程,優(yōu)點是簡單高效健爬。是client級別默認(rèn)的GC方式控乾,可以通過-XX:+UseSerialGC來強(qiáng)制指定。

2) Serial Old收集器(標(biāo)記-整理算法)

老年代單線程收集器娜遵,Serial收集器的老年代版本蜕衡。

3) ParNew收集器(停止-復(fù)制算法)

新生代多線程收集器,其實就是Serial收集器的多線程版本,在多核CPU環(huán)境下有著比Serial更好的表現(xiàn)设拟。

4) Parallel Scavenge收集器(停止-復(fù)制算法)

新生代并行的多線程收集器慨仿,追求高吞吐量,高效利用CPU纳胧。吞吐量一般為99%镰吆, 吞吐量= 用戶線程時間/(用戶線程時間+GC線程時間)。適合后臺應(yīng)用等對交互相應(yīng)要求不高的場景跑慕。是server級別默認(rèn)采用的GC方式万皿,可用-XX:+UseParallelGC來強(qiáng)制指定,用-XX:ParallelGCThreads=4來指定線程數(shù)核行。

5) Parallel Old收集器(停止-復(fù)制算法)

老年代并行的多線程收集器相寇,Parallel Scavenge收集器的老年代版本,并行收集器钮科,吞吐量優(yōu)先唤衫。

6) CMS(Concurrent Mark Sweep)收集器(標(biāo)記-清除算法)

CMS收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器,CMS收集器是基于“標(biāo)記--清除”(Mark-Sweep)算法實現(xiàn)的绵脯,整個過程分為四個步驟:

初始標(biāo)記: 標(biāo)記GC Roots能直接關(guān)聯(lián)到的對象佳励,速度很快休里;

并發(fā)標(biāo)記: 進(jìn)行GC Roots Tracing的過程;

重新標(biāo)記: 修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄赃承,這個階段的停頓時間一般會比初始標(biāo)記階段稍長一些妙黍,但比并發(fā)標(biāo)記時間短;

并發(fā)清除: 整個過程中耗時最長的并發(fā)標(biāo)記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作瞧剖,所以拭嫁,從總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的抓于。

優(yōu)點:并發(fā)收集做粤、低停頓

缺點:對CPU資源非常敏感、無法處理浮動垃圾捉撮、產(chǎn)生大量空間碎片怕品。

7) G1(Garbage First)收集器(標(biāo)記-整理算法)

G1是一款面向服務(wù)端應(yīng)用的垃圾收集器,是基于“標(biāo)記-整理”算法實現(xiàn)的巾遭,與其他GC收集器相比肉康,G1具備如下特點:

并行與并發(fā)

分代收集

空間整合

可預(yù)測性的停頓

G1運作步驟:

初始標(biāo)記(stop the world事件,CPU停頓只處理垃圾)

并發(fā)標(biāo)記(與用戶線程并發(fā)執(zhí)行)

最終標(biāo)記(stop the world事件灼舍,CPU停頓處理垃圾)

篩選回收(stop the world事件吼和,根據(jù)用戶期望的GC停頓時間回收)

? ?垃圾收集結(jié)構(gòu)圖

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市骑素,隨后出現(xiàn)的幾起案子纹安,更是在濱河造成了極大的恐慌,老刑警劉巖砂豌,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厢岂,死亡現(xiàn)場離奇詭異,居然都是意外死亡阳距,警方通過查閱死者的電腦和手機(jī)塔粒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筐摘,“玉大人卒茬,你說我怎么就攤上這事】欤” “怎么了圃酵?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長馍管。 經(jīng)常有香客問我郭赐,道長,這世上最難降的妖魔是什么确沸? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任捌锭,我火速辦了婚禮俘陷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘观谦。我一直安慰自己拉盾,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布豁状。 她就那樣靜靜地躺著捉偏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泻红。 梳的紋絲不亂的頭發(fā)上夭禽,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機(jī)與錄音承桥,去河邊找鬼。 笑死根悼,一個胖子當(dāng)著我的面吹牛凶异,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挤巡,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼剩彬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了矿卑?” 一聲冷哼從身側(cè)響起喉恋,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎母廷,沒想到半個月后轻黑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡琴昆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年氓鄙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片业舍。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡抖拦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舷暮,到底是詐尸還是另有隱情态罪,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布下面,位于F島的核電站复颈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沥割。R本人自食惡果不足惜券膀,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一君纫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芹彬,春花似錦蓄髓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至玩郊,卻和暖如春肢执,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背译红。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工预茄, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侦厚。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓耻陕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刨沦。 傳聞我的和親對象是個殘疾皇子诗宣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容