JVM原理

原文:https://baijiahao.baidu.com/s?id=1605937053950156833&wfr=spider&for=pc

1:什麼是JVM

JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用於計算設備的規範,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。Java虛擬機包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。 JVM屏蔽了與具體操作系統平臺相關的信息,使Java程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。JVM在執行字節碼時,實際上最終還是把字節碼解釋成具體平臺上的機器指令執行。

2:JRE/JDK/JVM是什麼關係

JRE(JavaRuntimeEnvironment,Java運行環境),也就是Java平臺。所有的Java 程序都要在JRE下才能運行。普通用戶只需要運行已開發好的java程序,安裝JRE即可。

JDK(Java Development Kit)是程序開發者用來來編譯、調試java程序用的開發工具包。JDK的工具也是Java程序,也需要JRE才能運行。爲了保持JDK的獨立性和完整性,在JDK的安裝過程中,JRE也是 安裝的一部分。所以,在JDK的安裝目錄下有一個名爲jre的目錄,用於存放JRE文件。

JVM(JavaVirtualMachine,Java虛擬機)是JRE的一部分。它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。Java語言最重要的特點就是跨平臺運行。使用JVM就是爲了支持與操作系統無關,實現跨平臺。

3:JVM原理

JVM是java的核心和基礎,在java編譯器和os平臺之間的虛擬處理器。它是一種利用軟件方法實現的抽象的計算機基於下層的操作系統和硬件平臺,可以在上面執行java的字節碼程序。

java編譯器只要面向JVM,生成JVM能理解的代碼或字節碼文件。Java源文件經編譯成字節碼程序,通過JVM將每一條指令翻譯成不同平臺機器碼,通過特定平臺運行。

4:JVM的體系結構

類裝載器(ClassLoader)(用來裝載.class文件)

執行引擎(執行字節碼,或者執行本地方法)

運行時數據區(方法區、堆、java棧、PC寄存器、本地方法棧)

5:JVM運行時數據區

第一塊:PC寄存器

PC寄存器是用於存儲每個線程下一步將執行的JVM指令,如該方法爲native的,則PC寄存器中不存儲任何信息。

第二塊:JVM棧

JVM棧是線程私有的,每個線程創建的同時都會創建JVM棧,JVM棧中存放的爲當前線程中局部基本類型的變量(java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結果以及Stack Frame,非基本類型的對象在JVM棧上僅存放一個指向堆上的地址。

第三塊:堆(Heap)

它是JVM用來存儲對象實例以及數組值的區域,可以認爲Java中所有通過new創建的對象的內存都在此分配,Heap中的對象的內存需要等待GC進行回收。

(1) 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,這也導致了new對象的開銷是比較大的

(2) Sun Hotspot JVM爲了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配

(3) TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

(4) 所有新創建的Object 都將會存儲在新生代Yong Generation中。如果Young Generation的數據在一次或多次GC後存活下來,那麼將被轉移到OldGeneration。新的Object總是創建在Eden Space。

第四塊:方法區域(Method Area)

(1)在Sun JDK中這塊區域對應的爲PermanetGeneration,又稱爲持久代。

(2)方法區域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態變量、類中定義爲final類型的常量、類中的Field信息、類中的方法信息,當開發人員在程序中通過Class對象中的getName、isInterface等方法來獲取信息時,這些數據都來源於方法區域,同時方法區域也是全局共享的,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出OutOfMemory的錯誤信息。

第五塊:運行時常量池(Runtime Constant Pool)

存放的爲類中的固定的常量信息、方法和Field的引用信息等,其空間從方法區域中分配。

第六塊:本地方法堆棧(Native Method Stacks)

JVM採用本地方法堆棧來支持native方法的執行,此區域用於存儲每個native方法調用的狀態。

6:對象“已死”的判定算法

由於程序計數器、Java虛擬機棧、本地方法棧都是線程獨享,其佔用的內存也是隨線程生而生、隨線程結束而回收。而Java堆和方法區則不同,線程共享,是GC的所關注的部分。

在堆中幾乎存在着所有對象,GC之前需要考慮哪些對象還活着不能回收,哪些對象已經死去可以回收。

有兩種算法可以判定對象是否存活:

1.)引用計數算法:給對象中添加一個引用計數器,每當一個地方應用了對象,計數器加1;當引用失效,計數器減1;當計數器爲0表示該對象已死、可回收。但是它很難解決兩個對象之間相互循環引用的情況。

2.)可達性分析算法:通過一系列稱爲“GC Roots”的對象作爲起點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(即對象到GC Roots不可達),則證明此對象已死、可回收。Java中可以作爲GC Roots的對象包括:虛擬機棧中引用的對象、本地方法棧中Native方法引用的對象、方法區靜態屬性引用的對象、方法區常量引用的對象。

在主流的商用程序語言(如我們的Java)的主流實現中,都是通過可達性分析算法來判定對象是否存活的。

7:JVM垃圾回收

GC (Garbage Collection)的基本原理:將內存中不再被使用的對象進行回收,GC中用於回收的方法稱爲收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停

(1)對新生代的對象的收集稱爲minor GC;

(2)對舊生代的對象的收集稱爲Full GC;

(3)程序中主動調用System.gc()強制執行的GC爲Full GC。

不同的對象引用類型, GC會採用不同的方法進行回收,JVM對象的引用分爲了四種類型:

(1)強引用:默認情況下,對象採用的均爲強引用(這個對象的實例沒有其他對象引用,GC時纔會被回收)

(2)軟引用:軟引用是Java中提供的一種比較適合於緩存場景的應用(只有在內存不夠用的情況下才會被GC)

(3)弱引用:在GC時一定會被GC回收

(4)虛引用:由於虛引用只是用來得知對象是否被GC

8:垃圾收集算法

1、標記-清除算法

最基礎的算法,分標記和清除兩個階段:首先標記處所需要回收的對象,在標記完成後統一回收所有被標記的對象。

它有兩點不足:一個效率問題,標記和清除過程都效率不高;一個是空間問題,標記清除之後會產生大量不連續的內存碎片(類似於我們電腦的磁盤碎片),空間碎片太多導致需要分配大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾回收動作。

2、複製算法

爲了解決效率問題,出現了“複製”算法,他將可用內存按容量劃分爲大小相等的兩塊,每次只需要使用其中一塊。當一塊內存用完了,將還存活的對象複製到另一塊上面,然後再把剛剛用完的內存空間一次清理掉。這樣就解決了內存碎片問題,但是代價就是可以用內容就縮小爲原來的一半。

3、標記-整理算法

複製算法在對象存活率較高時就會進行頻繁的複製操作,效率將降低。因此又有了標記-整理算法,標記過程同標記-清除算法,但是在後續步驟不是直接對對象進行清理,而是讓所有存活的對象都向一側移動,然後直接清理掉端邊界以外的內存。

 

4、分代收集算法

當前商業虛擬機的GC都是採用分代收集算法,這種算法並沒有什麼新的思想,而是根據對象存活週期的不同將堆分爲:新生代和老年代,方法區稱爲永久代(在新的版本中已經將永久代廢棄,引入了元空間的概念,永久代使用的是JVM內存而元空間直接使用物理內存)。

這樣就可以根據各個年代的特點採用不同的收集算法。

 

新生代中的對象“朝生夕死”,每次GC時都會有大量對象死去,少量存活,使用複製算法。新生代又分爲Eden區和Survivor區(Survivor from、Survivor to),大小比例默認爲8:1:1。

老年代中的對象因爲對象存活率高、沒有額外空間進行分配擔保,就使用標記-清除或標記-整理算法。

新產生的對象優先進去Eden區,當Eden區滿了之後再使用Survivor from,當Survivor from 也滿了之後就進行Minor GC(新生代GC),將Eden和Survivor from中存活的對象copy進入Survivor to,然後清空Eden和Survivor from,這個時候原來的Survivor from成了新的Survivor to,原來的Survivor to成了新的Survivor from。複製的時候,如果Survivor to 無法容納全部存活的對象,則根據老年代的分配擔保(類似於銀行的貸款擔保)將對象copy進去老年代,如果老年代也無法容納,則進行Full GC(老年代GC)。

大對象直接進入老年代:JVM中有個參數配置-XX:PretenureSizeThreshold,令大於這個設置值的對象直接進入老年代,目的是爲了避免在Eden和Survivor區之間發生大量的內存複製。

長期存活的對象進入老年代:JVM給每個對象定義一個對象年齡計數器,如果對象在Eden出生並經過第一次Minor GC後仍然存活,並且能被Survivor容納,將被移入Survivor並且年齡設定爲1。沒熬過一次Minor GC,年齡就加1,當他的年齡到一定程度(默認爲15歲,可以通過XX:MaxTenuringThreshold來設定),就會移入老年代。但是JVM並不是永遠要求年齡必須達到最大年齡纔會晉升老年代,如果Survivor 空間中相同年齡(如年齡爲x)所有對象大小的總和大於Survivor的一半,年齡大於等於x的所有對象直接進入老年代,無需等到最大年齡要求。

9:垃圾收集器

垃圾收集算法是方法論,垃圾收集器是具體實現。JVM規範對於垃圾收集器的應該如何實現沒有任何規定,因此不同的廠商、不同版本的虛擬機所提供的垃圾收集器差別較大,這裏只看HotSpot虛擬機。

JDK7/8後,HotSpot虛擬機所有收集器及組合(連線)如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章