您好,登錄后才能下訂單哦!
本篇文章為大家展示了怎么分析JVM原理,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
1:什么是JVM
JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設(shè)備的規(guī)范,它是一個虛構(gòu)出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。Java虛擬機包括一套字節(jié)碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。 JVM屏蔽了與具體操作系統(tǒng)平臺相關(guān)的信息,使Java程序只需生成在Java虛擬機上運行的目標代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行。JVM在執(zhí)行字節(jié)碼時,實際上最終還是把字節(jié)碼解釋成具體平臺上的機器指令執(zhí)行。
2:JRE/JDK/JVM是什么關(guān)系
JRE(JavaRuntimeEnvironment,Java運行環(huán)境),也就是Java平臺。所有的Java 程序都要在JRE下才能運行。普通用戶只需要運行已開發(fā)好的java程序,安裝JRE即可。
JDK(Java Development Kit)是程序開發(fā)者用來來編譯、調(diào)試java程序用的開發(fā)工具包。JDK的工具也是Java程序,也需要JRE才能運行。為了保持JDK的獨立性和完整性,在JDK的安裝過程中,JRE也是 安裝的一部分。所以,在JDK的安裝目錄下有一個名為jre的目錄,用于存放JRE文件。
JVM(JavaVirtualMachine,Java虛擬機)是JRE的一部分。它是一個虛構(gòu)出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。JVM有自己完善的硬件架構(gòu),如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。Java語言最重要的特點就是跨平臺運行。使用JVM就是為了支持與操作系統(tǒng)無關(guān),實現(xiàn)跨平臺。
3:JVM原理
JVM是java的核心和基礎(chǔ),在java編譯器和os平臺之間的虛擬處理器。它是一種利用軟件方法實現(xiàn)的抽象的計算機基于下層的操作系統(tǒng)和硬件平臺,可以在上面執(zhí)行java的字節(jié)碼程序。
java編譯器只要面向JVM,生成JVM能理解的代碼或字節(jié)碼文件。Java源文件經(jīng)編譯成字節(jié)碼程序,通過JVM將每一條指令翻譯成不同平臺機器碼,通過特定平臺運行。
4:JVM的體系結(jié)構(gòu)
類裝載器(ClassLoader)(用來裝載.class文件)
執(zhí)行引擎(執(zhí)行字節(jié)碼,或者執(zhí)行本地方法)
運行時數(shù)據(jù)區(qū)(方法區(qū)、堆、java棧、PC寄存器、本地方法棧)
5:JVM運行時數(shù)據(jù)區(qū)
第一塊:PC寄存器
PC寄存器是用于存儲每個線程下一步將執(zhí)行的JVM指令,如該方法為native的,則PC寄存器中不存儲任何信息。
第二塊:JVM棧
JVM棧是線程私有的,每個線程創(chuàng)建的同時都會創(chuàng)建JVM棧,JVM棧中存放的為當前線程中局部基本類型的變量(java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結(jié)果以及Stack Frame,非基本類型的對象在JVM棧上僅存放一個指向堆上的地址。
第三塊:堆(Heap)
它是JVM用來存儲對象實例以及數(shù)組值的區(qū)域,可以認為Java中所有通過new創(chuàng)建的對象的內(nèi)存都在此分配,Heap中的對象的內(nèi)存需要等待GC進行回收。
(1) 堆是JVM中所有線程共享的,因此在其上進行對象內(nèi)存的分配均需要進行加鎖,這也導(dǎo)致了new對象的開銷是比較大的
(2) Sun Hotspot JVM為了提升對象內(nèi)存分配的效率,對于所創(chuàng)建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據(jù)運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內(nèi)存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內(nèi)存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配
(3) TLAB僅作用于新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。
(4) 所有新創(chuàng)建的Object 都將會存儲在新生代Yong Generation中。如果Young Generation的數(shù)據(jù)在一次或多次GC后存活下來,那么將被轉(zhuǎn)移到OldGeneration。新的Object總是創(chuàng)建在Eden Space。
第四塊:方法區(qū)域(Method Area)
(1)在Sun JDK中這塊區(qū)域?qū)?yīng)的為PermanetGeneration,又稱為持久代。
(2)方法區(qū)域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態(tài)變量、類中定義為final類型的常量、類中的Field信息、類中的方法信息,當開發(fā)人員在程序中通過Class對象中的getName、isInterface等方法來獲取信息時,這些數(shù)據(jù)都來源于方法區(qū)域,同時方法區(qū)域也是全局共享的,在一定的條件下它也會被GC,當方法區(qū)域需要使用的內(nèi)存超過其允許的大小時,會拋出OutOfMemory的錯誤信息。
第五塊:運行時常量池(Runtime Constant Pool)
存放的為類中的固定的常量信息、方法和Field的引用信息等,其空間從方法區(qū)域中分配。
第六塊:本地方法堆棧(Native Method Stacks)
JVM采用本地方法堆棧來支持native方法的執(zhí)行,此區(qū)域用于存儲每個native方法調(diào)用的狀態(tài)。
6:對象“已死”的判定算法
由于程序計數(shù)器、Java虛擬機棧、本地方法棧都是線程獨享,其占用的內(nèi)存也是隨線程生而生、隨線程結(jié)束而回收。而Java堆和方法區(qū)則不同,線程共享,是GC的所關(guān)注的部分。
在堆中幾乎存在著所有對象,GC之前需要考慮哪些對象還活著不能回收,哪些對象已經(jīng)死去可以回收。
有兩種算法可以判定對象是否存活:
1.)引用計數(shù)算法:給對象中添加一個引用計數(shù)器,每當一個地方應(yīng)用了對象,計數(shù)器加1;當引用失效,計數(shù)器減1;當計數(shù)器為0表示該對象已死、可回收。但是它很難解決兩個對象之間相互循環(huán)引用的情況。
2.)可達性分析算法:通過一系列稱為“GC Roots”的對象作為起點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(即對象到GC Roots不可達),則證明此對象已死、可回收。Java中可以作為GC Roots的對象包括:虛擬機棧中引用的對象、本地方法棧中Native方法引用的對象、方法區(qū)靜態(tài)屬性引用的對象、方法區(qū)常量引用的對象。
在主流的商用程序語言(如我們的Java)的主流實現(xiàn)中,都是通過可達性分析算法來判定對象是否存活的。
7:JVM垃圾回收
GC (Garbage Collection)的基本原理:將內(nèi)存中不再被使用的對象進行回收,GC中用于回收的方法稱為收集器,由于GC需要消耗一些資源和時間,Java在對對象的生命周期特征進行分析后,按照新生代、舊生代的方式來對對象進行收集,以盡可能的縮短GC對應(yīng)用造成的暫停
(1)對新生代的對象的收集稱為minor GC;
(2)對舊生代的對象的收集稱為Full GC;
(3)程序中主動調(diào)用System.gc()強制執(zhí)行的GC為Full GC。
不同的對象引用類型, GC會采用不同的方法進行回收,JVM對象的引用分為了四種類型:
(1)強引用:默認情況下,對象采用的均為強引用(這個對象的實例沒有其他對象引用,GC時才會被回收)
(2)軟引用:軟引用是Java中提供的一種比較適合于緩存場景的應(yīng)用(只有在內(nèi)存不夠用的情況下才會被GC)
(3)弱引用:在GC時一定會被GC回收
(4)虛引用:由于虛引用只是用來得知對象是否被GC
8:垃圾收集算法
1、標記-清除算法
最基礎(chǔ)的算法,分標記和清除兩個階段:首先標記處所需要回收的對象,在標記完成后統(tǒng)一回收所有被標記的對象。
它有兩點不足:一個效率問題,標記和清除過程都效率不高;一個是空間問題,標記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片(類似于我們電腦的磁盤碎片),空間碎片太多導(dǎo)致需要分配大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾回收動作。
2、復(fù)制算法
為了解決效率問題,出現(xiàn)了“復(fù)制”算法,他將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只需要使用其中一塊。當一塊內(nèi)存用完了,將還存活的對象復(fù)制到另一塊上面,然后再把剛剛用完的內(nèi)存空間一次清理掉。這樣就解決了內(nèi)存碎片問題,但是代價就是可以用內(nèi)容就縮小為原來的一半。
3、標記-整理算法
復(fù)制算法在對象存活率較高時就會進行頻繁的復(fù)制操作,效率將降低。因此又有了標記-整理算法,標記過程同標記-清除算法,但是在后續(xù)步驟不是直接對對象進行清理,而是讓所有存活的對象都向一側(cè)移動,然后直接清理掉端邊界以外的內(nèi)存。
4、分代收集算法
當前商業(yè)虛擬機的GC都是采用分代收集算法,這種算法并沒有什么新的思想,而是根據(jù)對象存活周期的不同將堆分為:新生代和老年代,方法區(qū)稱為永久代(在新的版本中已經(jīng)將永久代廢棄,引入了元空間的概念,永久代使用的是JVM內(nèi)存而元空間直接使用物理內(nèi)存)。
這樣就可以根據(jù)各個年代的特點采用不同的收集算法。
新生代中的對象“朝生夕死”,每次GC時都會有大量對象死去,少量存活,使用復(fù)制算法。新生代又分為Eden區(qū)和Survivor區(qū)(Survivor from、Survivor to),大小比例默認為8:1:1。
老年代中的對象因為對象存活率高、沒有額外空間進行分配擔保,就使用標記-清除或標記-整理算法。
新產(chǎn)生的對象優(yōu)先進去Eden區(qū),當Eden區(qū)滿了之后再使用Survivor from,當Survivor from 也滿了之后就進行Minor GC(新生代GC),將Eden和Survivor from中存活的對象copy進入Survivor to,然后清空Eden和Survivor from,這個時候原來的Survivor from成了新的Survivor to,原來的Survivor to成了新的Survivor from。復(fù)制的時候,如果Survivor to 無法容納全部存活的對象,則根據(jù)老年代的分配擔保(類似于銀行的貸款擔保)將對象copy進去老年代,如果老年代也無法容納,則進行Full GC(老年代GC)。
大對象直接進入老年代:JVM中有個參數(shù)配置-XX:PretenureSizeThreshold,令大于這個設(shè)置值的對象直接進入老年代,目的是為了避免在Eden和Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。
長期存活的對象進入老年代:JVM給每個對象定義一個對象年齡計數(shù)器,如果對象在Eden出生并經(jīng)過第一次Minor GC后仍然存活,并且能被Survivor容納,將被移入Survivor并且年齡設(shè)定為1。沒熬過一次Minor GC,年齡就加1,當他的年齡到一定程度(默認為15歲,可以通過XX:MaxTenuringThreshold來設(shè)定),就會移入老年代。但是JVM并不是永遠要求年齡必須達到最大年齡才會晉升老年代,如果Survivor 空間中相同年齡(如年齡為x)所有對象大小的總和大于Survivor的一半,年齡大于等于x的所有對象直接進入老年代,無需等到最大年齡要求。
9:垃圾收集器
垃圾收集算法是方法論,垃圾收集器是具體實現(xiàn)。JVM規(guī)范對于垃圾收集器的應(yīng)該如何實現(xiàn)沒有任何規(guī)定,因此不同的廠商、不同版本的虛擬機所提供的垃圾收集器差別較大,這里只看HotSpot虛擬機。
JDK7/8后,HotSpot虛擬機所有收集器及組合(連線)如下:
1.Serial收集器
Serial收集器是最基本、歷史最久的收集器,曾是新生代手機的唯一選擇。他是單線程的,只會使用一個CPU或一條收集線程去完成垃圾收集工作,并且它在收集的時候,必須暫停其他所有的工作線程,直到它結(jié)束,即“Stop the World”。停掉所有的用戶線程,對很多應(yīng)用來說難以接受。比如你在做一件事情,被別人強制停掉,你心里奔騰而過的“羊駝”還數(shù)的過來嗎?
盡管如此,它仍然是虛擬機運行在client模式下的默認新生代收集器:簡單而高效(與其他收集器的單個線程相比,因為沒有線程切換的開銷等)。
工作示意圖:
2.ParNew收集器
ParNew收集器是Serial收集器的多線程版本,除了使用了多線程之外,其他的行為(收集算法、stop the world、對象分配規(guī)則、回收策略等)同Serial收集器一樣。
是許多運行在Server模式下的JVM中首選的新生代收集器,其中一個很重還要的原因就是除了Serial之外,只有他能和老年代的CMS收集器配合工作。
工作示意圖:
3.Parallel Scavenge收集器
新生代收集器,并行的多線程收集器。它的目標是達到一個可控的吞吐量(就是CPU運行用戶代碼的時間與CPU總消耗時間的比值,即 吞吐量=行用戶代碼的時間/[行用戶代碼的時間+垃圾收集時間]),這樣可以高效率的利用CPU時間,盡快完成程序的運算任務(wù),適合在后臺運算而不需要太多交互的任務(wù)。
4.Serial Old收集器
Serial 收集器的老年代版本,單線程,“標記整理”算法,主要是給Client模式下的虛擬機使用。
另外還可以在Server模式下:
JDK 1.5之前的版本中雨P(guān)arallel Scavenge 收集器搭配使用
可以作為CMS的后背方案,在CMS發(fā)生Concurrent Mode Failure是使用
工作示意圖:
5.Parallel Old收集器
Parallel Scavenge的老年代版本,多線程,“標記整理”算法,JDK 1.6才出現(xiàn)。在此之前Parallel Scavenge只能同Serial Old搭配使用,由于Serial Old的性能較差導(dǎo)致Parallel Scavenge的優(yōu)勢發(fā)揮不出來,尷了個尬~~
Parallel Old收集器的出現(xiàn),使“吞吐量優(yōu)先”收集器終于有了名副其實的組合。在吞吐量和CPU敏感的場合,都可以使用Parallel Scavenge/Parallel Old組合。組合的工作示意圖如下:
6.CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,停頓時間短,用戶體驗就好。
基于“標記清除”算法,并發(fā)收集、低停頓,運作過程復(fù)雜,分4步:
1)初始標記:僅僅標記GC Roots能直接關(guān)聯(lián)到的對象,速度快,但是需要“Stop The World”
2)并發(fā)標記:就是進行追蹤引用鏈的過程,可以和用戶線程并發(fā)執(zhí)行。
3)重新標記:修正并發(fā)標記階段因用戶線程繼續(xù)運行而導(dǎo)致標記發(fā)生變化的那部分對象的標記記錄,比初始標記時間長但遠比并發(fā)標記時間短,需要“Stop The World”
4)并發(fā)清除:清除標記為可以回收對象,可以和用戶線程并發(fā)執(zhí)行
由于整個過程耗時最長的并發(fā)標記和并發(fā)清除都可以和用戶線程一起工作,所以總體上來看,CMS收集器的內(nèi)存回收過程和用戶線程是并發(fā)執(zhí)行的。
工作示意圖:
CSM收集器有3個缺點:
1)對CPU資源非常敏感
并發(fā)收集雖然不會暫停用戶線程,但因為占用一部分CPU資源,還是會導(dǎo)致應(yīng)用程序變慢,總吞吐量降低。
CMS的默認收集線程數(shù)量是=(CPU數(shù)量+3)/4;當CPU數(shù)量多于4個,收集線程占用的CPU資源多于25%,對用戶程序影響可能較大;不足4個時,影響更大,可能無法接受。
2)無法處理浮動垃圾(在并發(fā)清除時,用戶線程新產(chǎn)生的垃圾叫浮動垃圾),可能出現(xiàn)"Concurrent Mode Failure"失敗。
并發(fā)清除時需要預(yù)留一定的內(nèi)存空間,不能像其他收集器在老年代幾乎填滿再進行收集;如果CMS預(yù)留內(nèi)存空間無法滿足程序需要,就會出現(xiàn)一次"Concurrent Mode Failure"失??;這時JVM啟用后備預(yù)案:臨時啟用Serail Old收集器,而導(dǎo)致另一次Full GC的產(chǎn)生;
3)產(chǎn)生大量內(nèi)存碎片:CMS基于"標記-清除"算法,清除后不進行壓縮操作產(chǎn)生大量不連續(xù)的內(nèi)存碎片,這樣會導(dǎo)致分配大內(nèi)存對象時,無法找到足夠的連續(xù)內(nèi)存,從而需要提前觸發(fā)另一次Full GC動作。
7.G1收集器
G1(Garbage-First)是JDK7-u4才正式推出商用的收集器。G1是面向服務(wù)端應(yīng)用的垃圾收集器。它的使命是未來可以替換掉CMS收集器。
G1收集器特性:
并行與并發(fā):能充分利用多CPU、多核環(huán)境的硬件優(yōu)勢,縮短停頓時間;能和用戶線程并發(fā)執(zhí)行。
分代收集:G1可以不需要其他GC收集器的配合就能獨立管理整個堆,采用不同的方式處理新生對象和已經(jīng)存活一段時間的對象。
空間整合:整體上看采用標記整理算法,局部看采用復(fù)制算法(兩個Region之間),不會有內(nèi)存碎片,不會因為大對象找不到足夠的連續(xù)空間而提前觸發(fā)GC,這點優(yōu)于CMS收集器。
可預(yù)測的停頓:除了追求低停頓還能建立可以預(yù)測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內(nèi),消耗在垃圾收集上的時間不超N毫秒,這點優(yōu)于CMS收集器。
為什么能做到可預(yù)測的停頓?
是因為可以有計劃的避免在整個Java堆中進行全區(qū)域的垃圾收集。
G1收集器將內(nèi)存分大小相等的獨立區(qū)域(Region),新生代和老年代概念保留,但是已經(jīng)不再物理隔離。
G1跟蹤各個Region獲得其收集價值大小,在后臺維護一個優(yōu)先列表;
每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的Region(名稱Garbage-First的由來);
這就保證了在有限的時間內(nèi)可以獲取盡可能高的收集效率。
對象被其他Region的對象引用了怎么辦?
判斷對象存活時,是否需要掃描整個Java堆才能保證準確?在其他的分代收集器,也存在這樣的問題(而G1更突出):新生代回收的時候不得不掃描老年代?
無論G1還是其他分代收集器,JVM都是使用Remembered Set來避免全局掃描:
每個Region都有一個對應(yīng)的Remembered Set;
每次Reference類型數(shù)據(jù)寫操作時,都會產(chǎn)生一個Write Barrier 暫時中斷操作;
然后檢查將要寫入的引用指向的對象是否和該Reference類型數(shù)據(jù)在不同的 Region(其他收集器:檢查老年代對象是否引用了新生代對象);
如果不同,通過CardTable把相關(guān)引用信息記錄到引用指向?qū)ο蟮乃赗egion對應(yīng)的Remembered Set中;
進行垃圾收集時,在GC根節(jié)點的枚舉范圍加入 Remembered Set ,就可以保證不進行全局掃描,也不會有遺漏。
不計算維護Remembered Set的操作,回收過程可以分為4個步驟(與CMS較為相似):
1)初始標記:僅僅標記GC Roots能直接關(guān)聯(lián)到的對象,并修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發(fā)運行時能在正確可用的Region中創(chuàng)建新對象,需要“Stop The World”
2)并發(fā)標記:從GC Roots開始進行可達性分析,找出存活對象,耗時長,可與用戶線程并發(fā)執(zhí)行
3)最終標記:修正并發(fā)標記階段因用戶線程繼續(xù)運行而導(dǎo)致標記發(fā)生變化的那部分對象的標記記錄。并發(fā)標記時虛擬機將對象變化記錄在線程Remember Set Logs里面,最終標記階段將Remember Set Logs整合到Remember Set中,比初始標記時間長但遠比并發(fā)標記時間短,需要“Stop The World”
4)篩選回收:首先對各個Region的回收價值和成本進行排序,然后根據(jù)用戶期望的GC停頓時間來定制回收計劃,最后按計劃回收一些價值高的Region中垃圾對象?;厥諘r采用復(fù)制算法,從一個或多個Region復(fù)制存活對象到堆上的另一個空的Region,并且在此過程中壓縮和釋放內(nèi)存;可以并發(fā)進行,降低停頓時間,并增加吞吐量。
上述內(nèi)容就是怎么分析JVM原理,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。