JVM是Java的運行時虛擬機,所有的Java程序都是在JVM沙箱中運行,每個Java程序就是一個獨立的JVM進程。
談到Java程序是如何運行的,首先需要理解的肯定是JVM是如何運行的,什么是JVM;要理解我們編寫的Java程序,運行起來以后到底是什么樣子,本質(zhì)上就是弄清楚JVM是什么樣子。
Java程序的代碼是什么樣的
Java誕生之初最大的賣點就是編寫的代碼跨平臺可移植性,實現(xiàn)這種可移植性,是因為Java通過平臺特定的虛擬機,運行中間的字節(jié)碼,而不是直接編譯成本地二進制代碼實現(xiàn),中間字節(jié)碼也就是java文件編譯后生成的.class文件,Jar包的話,實際上只是一系列.class文件的集合。
編寫Java程序,首先需要一個入口點,在運行的時候通過指定MainClass來指定入口點,代碼層面主類必須實現(xiàn)一個靜態(tài)的main函數(shù),運行時虛擬機會從MainClass.main開始執(zhí)行指令,其他的邏輯只是import和函數(shù)調(diào)用了。
SDK自帶的javac命令,負責將我們編程的Java代碼,也就是.java文件,編譯成平臺無關的字節(jié)碼;字節(jié)碼可以在任何操作系統(tǒng)平臺上,通過平臺對應的JVM執(zhí)行;JVM執(zhí)行的時候,運行字節(jié)碼,根據(jù)自己的平臺特性,將字節(jié)碼轉(zhuǎn)換成平臺相關的二進制碼運行。
javac編譯器運行的過程大致分為:詞法分析(Token流)、語法分析(語法樹)、語義分析(注解語法樹),還有代碼生成器,根據(jù)注解語法樹,生成字節(jié)碼,
語義分析階段,編譯器會做一些操作,將人類友好的代碼,做一些處理,轉(zhuǎn)換成更符合機器執(zhí)行機制的代碼,例如全局變量,魔法變量,依賴注入,注解這些魔法機制。大致分為以下步驟:
-
給類添加默認構造函數(shù)
-
處理注解
-
檢查語義的合法性并進行邏輯判斷
-
數(shù)據(jù)流分析
-
對語法樹進行語義分析(變量自動轉(zhuǎn)換并去掉語法糖)
JVM是什么
JVM = 類加載器 classloader + 執(zhí)行引擎 execution engine + 運行時數(shù)據(jù)區(qū)域 runtime data area
JVM就是運行編譯好字節(jié)碼的虛擬機,不同的操作系統(tǒng)和平臺上,虛擬機將平臺無關的字節(jié)碼,編譯成特定平臺的指令去執(zhí)行。我覺得,JVM首先是一個獨立運行在操作系統(tǒng)上的進程。執(zhí)行java命令運行程序的時候,會啟動一個進程,每個獨立的程序就運行在一個獨立的JVM進程里。JVM負責執(zhí)行字節(jié)碼,從而實現(xiàn)程序要完成的所有功能。
JVM主要由三部分組成:類加載器、運行時數(shù)據(jù)區(qū)和執(zhí)行引擎。類加載器加載編譯好的.class文件,將所有類結構和方法變量放入運行時數(shù)據(jù)區(qū),初始化之后,將程序的執(zhí)行交給執(zhí)行引擎;JIT編譯器,負責將字節(jié)碼編譯成平臺特定的二進制碼,調(diào)用本地接口庫。垃圾回收器作為執(zhí)行引擎的一部分,負責維護運行時數(shù)據(jù)區(qū)中可變的應用程序內(nèi)存空間。
類加載器(ClassLoader)
類加載器將類加載到內(nèi)存,并管理類的生命周期,知道將類從內(nèi)存中卸載結束生命周期。
系統(tǒng)提供了三種類加載器,分別用于不同類的加載:
-
啟動類加載器(Bootstrap ClassLoader),該加載器會將
lib目錄下能被虛擬機識別的類加載到內(nèi)存中,也就是系統(tǒng)類 -
擴展類加載器(Extension ClassLoader),該加載器會將
libext目錄下的類庫加載到內(nèi)存 -
應用程序類加載器(Application ClassLoader),該加載器負責加載用戶路徑上所指定的類庫。
運行時數(shù)據(jù)區(qū)(Runtime Data Area)
運行時數(shù)據(jù)區(qū),是JVM運行時,在內(nèi)存中分配的空間。
運行時數(shù)據(jù)區(qū),被分為五個不同的結構:
-
Java虛擬機棧(Java Stacks): 也叫棧內(nèi)存,主管Java程序的運行,是在線程創(chuàng)建時創(chuàng)建,它的生命期是跟隨線程的生命期,線程結束棧內(nèi)存也就釋放,對于棧來說不存在垃圾回收問題,只要線程一結束該棧就Over,生命周期和線程一致,是線程私有的。
-
本地方法棧(Native Method Memory): 登記的native方法,執(zhí)行引擎執(zhí)行時加載
-
程序寄存器(PC Registers): 當前線程所執(zhí)行字節(jié)碼的指針,存儲每個線程下一步要執(zhí)行的字節(jié)碼JVM指令。
-
Java堆(Heap Memory): 應用的對象和數(shù)據(jù)都是存在這個區(qū)域,這塊區(qū)域也是線程共享的,也是gc 主要的回收區(qū),一個 JVM 實例只存在一個堆類存,堆內(nèi)存的大小是可以調(diào)節(jié)的。類加載器讀取了類文件后,需要把類、方法、常變量放到堆內(nèi)存中,以方便執(zhí)行器執(zhí)行。
-
方法區(qū)(Method Area): 所有定義的方法的信息都保存在該區(qū)域,此區(qū)域?qū)儆诠蚕韰^(qū)間。靜態(tài)變量+常量+類信息+運行時常量池存在方法區(qū)中,實例變量存在堆內(nèi)存中。
其中的程序寄存器、Java虛擬機棧是按照線程分配的,每個線程都有自己私有的獨立空間。
運行的方法和運行期數(shù)據(jù),以棧幀的形式存儲在運行時JVM虛擬機棧中,棧幀中保存了本地變量,包括輸入輸出參數(shù)和本地變量;保存類文件和方法等幀數(shù)據(jù),還記錄了出棧入棧操作。每一個方法被調(diào)用直至執(zhí)行完成的過程就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。
堆在JVM是所有線程共享的,因此在其上進行對象內(nèi)存的分配均需要進行加鎖。
執(zhí)行引擎(Execution Engine)
執(zhí)行引擎由三個模塊組成,分別是執(zhí)行引擎,JIT Compiler和Garbage Collector,執(zhí)行引擎的核心是Jit Compiler,執(zhí)行字節(jié)碼或者本地方法;垃圾回收器,則是一系列線程,負責管理分代堆內(nèi)存。
三個模塊分別是運行時計算和運行時內(nèi)存的管理,負責執(zhí)行運行時指令的是執(zhí)行引擎,通過程序寄存器和虛擬機棧中的棧幀出入棧實現(xiàn)方法和指令的執(zhí)行。GC則負責堆內(nèi)存的管理,因為GC的時候需要停止指令的執(zhí)行,消耗資源,所以采用分代方式管理對象收集。JIT則是把字節(jié)碼編譯成本地二進制代碼,并調(diào)用本地庫執(zhí)行。
GC垃圾回收機制
Java的內(nèi)存管理,主要是針對的堆內(nèi)存,因為堆內(nèi)存是運行時程序和數(shù)據(jù)分配的空間;不同于內(nèi)存的其他區(qū)域,加載完程序之后,基本上可以確定需要占用的空間大??;heap memory 空間會在運行時動態(tài)的分配,無法預測,可大可小,而且快速變化,管理不慎就容易產(chǎn)生內(nèi)存溢出,所以由JVM提供了強大的分代內(nèi)存管理機制。
JVM 使用分代內(nèi)存管理,來分配運行時的堆內(nèi)存結構,針對不同的世代,采用不同的垃圾回收算法。
常用垃圾回收算法
-
引用計數(shù)器法(Reference Counting)
-
標記清除法(Mark-Sweep)
-
復制算法(Coping)
-
標記壓縮法(Mark-Compact)
-
分代算法(Generational Collecting)
-
分區(qū)算法(Region)
堆內(nèi)存的組成
heap 的組成有三區(qū)域/世代:分別是新生代(Young Generation)、老生代(Old Generation/tenured)和永久區(qū)(Perm)。
新生代堆內(nèi)存又分成Eden區(qū)和兩個生存區(qū),其中Eden區(qū)和生存區(qū)的占比為8:1:1,在清理新生代內(nèi)存的時候,使用的是復制清除算法,優(yōu)點是清除以后不會產(chǎn)生碎片;簡單的復制算法,將內(nèi)存分成大小相同的兩個區(qū)域,每次周期只分配其中的一半,這樣空間利用率比較低,只使用了一半的內(nèi)存。
考慮到新生代內(nèi)存區(qū)的對象都是周期很短的,所以JVM實現(xiàn)了一種優(yōu)化的復制算法,設置一個較大的Eden區(qū)來分配對象內(nèi)存,Eden區(qū)空間不夠了觸發(fā)垃圾回收,將上一個生存區(qū)和Eden區(qū)中還存活的對象,復制到空閑的生存區(qū)。然后清空上述兩個區(qū)域,這樣就不會產(chǎn)生內(nèi)存碎片。
將清理一定次數(shù)(15次)還生存的對象,定期晉升到老生代內(nèi)存區(qū),如果生存區(qū)空間不夠了,則馬上就會觸發(fā)晉升機制。將部分對象直接晉升到老生代。
如果晉升之后,發(fā)現(xiàn)老生代內(nèi)存不夠,就會觸發(fā)完整的全局GC,清理老生代和新生代內(nèi)存,老生代內(nèi)存清理需要使用標記清除和標記整理兩種算法。
GC工作原理
分配內(nèi)存的時候,首先分配到新生代的Eden區(qū),如果Eden區(qū)滿了,就會發(fā)起一次Minor GC,將Eden和From Survivor生存的對象,拷貝到To Survivor Space,如果清理過程中,to Space的空間占用達到一定閾值,或者有對象經(jīng)歷Minor GC的次數(shù)達標,就會將對象移動到老生代內(nèi)存。如果移動過程中發(fā)現(xiàn),老生代內(nèi)存的空間已經(jīng)不夠了。這時就需要發(fā)起Full GC,先進行一次Minor GC,然后通過CMS進行標記清除算法,清理老生代內(nèi)存,老生代內(nèi)存經(jīng)歷標記清除之后,因為會產(chǎn)生內(nèi)存碎片,還需要采用標記整理算法,將所有內(nèi)存塊往前移動,形成連續(xù)的內(nèi)存空間。
老生代標記清除的優(yōu)點是不需要額外空間。不同于老生代清除算法,會產(chǎn)生碎片,而且標記算法的成本開銷也很大;在新生代清除中,因為考慮到大多數(shù)新生代對象生存期都是很短暫的,可以使用一種空間換時間的思路,拿出一部分內(nèi)存空間不分配,而是作為中轉(zhuǎn),將每次檢查時還生存的對象拷貝到Survivor Space,然后直接清除所有原區(qū)域的對象,因為大量對象都是生存周期極短的,所以Survivor Space的空間可以遠小于正常分配的空間。
不同于引用計數(shù)方法,Java使用一種 GC Roots 的對象作為起點開始檢查對象,當一個對象到 GC Roots 沒有任何引用鏈相連時, 即該對象不可達, 也就說明此對象是不可用的。就會在GC的時候收回。
GC清理類型的時候,為了防止程序地址出現(xiàn)異常,需要stop the world,清理線程會停止所有運行線程,直到清理完,這個時候是影響性能的。
垃圾回收器的本質(zhì)
垃圾回收器在JVM層面,是由一系列不同的組件組成的,每種組件是一個獨立線程,分別執(zhí)行自己的邏輯。
新生代垃圾收集器:
-
Serial(串行)收集器是最基本、發(fā)展歷史最悠久的收集器,它是采用復制算法的新生代收集器,。它是一個單線程收集器,只會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是它在進行垃圾收集時,必須暫停其他所有的工作線程,直至Serial收集器收集結束為止(“Stop The World”)。
-
ParNew收集器就是Serial收集器的多線程版本,它也是一個新生代收集器。除了使用多線程進行垃圾收集外,其余行為包括Serial收集器可用的所有控制參數(shù)、收集算法(復制算法)、Stop The World、對象分配規(guī)則、回收策略等與Serial收集器完全相同,兩者共用了相當多的代碼。
-
Parallel Scavenge收集器也是一個并行的多線程新生代收集器,它也使用復制算法。Parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是盡可能縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標是達到一個可控制的吞吐量(Throughput)。
老生代垃圾收集器:
-
Serial Old 是Serial收集器的老年代版本,它同樣是一個單線程收集器,使用“標記-整理”(Mark-Compact)算法
-
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法
-
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,優(yōu)點是:并發(fā)收集、低停頓,因此CMS收集器也被稱為并發(fā)低停頓收集器(Concurrent Low Pause Collector)
面向服務端的G1收集器。
G1收集器是一款面向服務端應用的垃圾收集器。
在使用G1收集器時,Java堆的內(nèi)存布局和其他收集器有很大的差別,它將這個Java堆分為多個大小相等的獨立區(qū)域,雖然還保留新生代和老年代的概念,但是新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續(xù))的集合。
GC回收的觸發(fā)條件
Minor GC觸發(fā)條件:當Eden區(qū)滿時,觸發(fā)Minor GC
Full GC觸發(fā)條件:
-
gc()方法的調(diào)用
-
老年代代空間不足
-
方法區(qū)空間不足
-
CMS GC時出現(xiàn)promotion failed和concurrent mode failure
-
統(tǒng)計得到的Minor GC晉升到舊生代的平均大小大于老年代的剩余空間
-
堆中分配很大的對象
-
通過Minor GC后進入老年代的平均大小大于老年代的可用內(nèi)存
-
由Eden區(qū)、From Space區(qū)向To Space區(qū)復制時,對象大小大于To Space可用內(nèi)存,則把該對象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對象大小
GC Roots
在Java語言中,可以作為GC Roots的對象包括下面幾種:
-
虛擬機棧(棧幀中的本地變量表)中引用的對象;
-
方法區(qū)中類靜態(tài)屬性引用的對象;
-
方法區(qū)中常量引用的對象;
-
本地方法棧中JNI(即一般說的Native方法)引用的對象;
總結就是,方法運行時,方法中引用的對象;類的靜態(tài)變量引用的對象;類中常量引用的對象;Native方法中引用的對象。
發(fā)布評論請先 登錄
相關推薦
Spire.XLS for Android via Java組件說明
![Spire.XLS for Android via <b class='flag-5'>Java</b>組件說明](https://file1.elecfans.com/web3/M00/07/1B/wKgZO2eTFFKACjKPAAAlbAyqU-k118.png)
華為云 Flexus X 實例下的場景體驗——小企業(yè)必備——JAVA 環(huán)境搭建——保姆級教學
![華為云 Flexus X 實例下的場景體驗——小企業(yè)必備——<b class='flag-5'>JAVA</b> 環(huán)境搭建——保姆級教學](https://file1.elecfans.com//web3/M00/05/20/wKgZPGd87kyAfdjBAAFJwXrGRlM239.png)
SSM框架在Java開發(fā)中的應用 如何使用SSM進行web開發(fā)
Java 23功能介紹
![<b class='flag-5'>Java</b> 23功能介紹](https://file1.elecfans.com/web3/M00/00/FA/wKgZO2dPuOmAC6YoAAAZQa1ialc743.png)
對比Python與Java編程語言
java反編譯能拿到源碼嗎
keil軟件怎么運行寫好的程序
linux驅(qū)動程序運行在什么空間
華納云:java web和java有什么區(qū)別java web和java有什么區(qū)別
![華納云:<b class='flag-5'>java</b> web和<b class='flag-5'>java</b>有什么區(qū)別<b class='flag-5'>java</b> web和<b class='flag-5'>java</b>有什么區(qū)別](https://file1.elecfans.com/web2/M00/FD/AC/wKgaomaWBouAMAnAAAAglmvluP4293.png)
評論