0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Java程序是如何運(yùn)行的

張康康 ? 2019-12-27 09:31 ? 次閱讀

JVM是Java的運(yùn)行時(shí)虛擬機(jī),所有的Java程序都是在JVM沙箱中運(yùn)行,每個(gè)Java程序就是一個(gè)獨(dú)立的JVM進(jìn)程。

談到Java程序是如何運(yùn)行的,首先需要理解的肯定是JVM是如何運(yùn)行的,什么是JVM;要理解我們編寫的Java程序,運(yùn)行起來以后到底是什么樣子,本質(zhì)上就是弄清楚JVM是什么樣子。

Java程序的代碼是什么樣的

Java誕生之初最大的賣點(diǎn)就是編寫的代碼跨平臺可移植性,實(shí)現(xiàn)這種可移植性,是因?yàn)镴ava通過平臺特定的虛擬機(jī),運(yùn)行中間的字節(jié)碼,而不是直接編譯成本地二進(jìn)制代碼實(shí)現(xiàn),中間字節(jié)碼也就是java文件編譯后生成的.class文件,Jar包的話,實(shí)際上只是一系列.class文件的集合。

編寫Java程序,首先需要一個(gè)入口點(diǎn),在運(yùn)行的時(shí)候通過指定MainClass來指定入口點(diǎn),代碼層面主類必須實(shí)現(xiàn)一個(gè)靜態(tài)的main函數(shù),運(yùn)行時(shí)虛擬機(jī)會從MainClass.main開始執(zhí)行指令,其他的邏輯只是import和函數(shù)調(diào)用了。

SDK自帶的javac命令,負(fù)責(zé)將我們編程的Java代碼,也就是.java文件,編譯成平臺無關(guān)的字節(jié)碼;字節(jié)碼可以在任何操作系統(tǒng)平臺上,通過平臺對應(yīng)的JVM執(zhí)行;JVM執(zhí)行的時(shí)候,運(yùn)行字節(jié)碼,根據(jù)自己的平臺特性,將字節(jié)碼轉(zhuǎn)換成平臺相關(guān)的二進(jìn)制碼運(yùn)行。

javac編譯器運(yùn)行的過程大致分為:詞法分析(Token流)、語法分析(語法樹)、語義分析(注解語法樹),還有代碼生成器,根據(jù)注解語法樹,生成字節(jié)碼,

語義分析階段,編譯器會做一些操作,將人類友好的代碼,做一些處理,轉(zhuǎn)換成更符合機(jī)器執(zhí)行機(jī)制的代碼,例如全局變量,魔法變量,依賴注入,注解這些魔法機(jī)制。大致分為以下步驟:

  1. 給類添加默認(rèn)構(gòu)造函數(shù)

  2. 處理注解

  3. 檢查語義的合法性并進(jìn)行邏輯判斷

  4. 數(shù)據(jù)流分析

  5. 對語法樹進(jìn)行語義分析(變量自動轉(zhuǎn)換并去掉語法糖)

JVM是什么

JVM = 類加載器 classloader + 執(zhí)行引擎 execution engine + 運(yùn)行時(shí)數(shù)據(jù)區(qū)域 runtime data area

JVM就是運(yùn)行編譯好字節(jié)碼的虛擬機(jī),不同的操作系統(tǒng)和平臺上,虛擬機(jī)將平臺無關(guān)的字節(jié)碼,編譯成特定平臺的指令去執(zhí)行。我覺得,JVM首先是一個(gè)獨(dú)立運(yùn)行在操作系統(tǒng)上的進(jìn)程。執(zhí)行java命令運(yùn)行程序的時(shí)候,會啟動一個(gè)進(jìn)程,每個(gè)獨(dú)立的程序就運(yùn)行在一個(gè)獨(dú)立的JVM進(jìn)程里。JVM負(fù)責(zé)執(zhí)行字節(jié)碼,從而實(shí)現(xiàn)程序要完成的所有功能。

JVM主要由三部分組成:類加載器、運(yùn)行時(shí)數(shù)據(jù)區(qū)和執(zhí)行引擎。類加載器加載編譯好的.class文件,將所有類結(jié)構(gòu)和方法變量放入運(yùn)行時(shí)數(shù)據(jù)區(qū),初始化之后,將程序的執(zhí)行交給執(zhí)行引擎;JIT編譯器,負(fù)責(zé)將字節(jié)碼編譯成平臺特定的二進(jìn)制碼,調(diào)用本地接口庫。垃圾回收器作為執(zhí)行引擎的一部分,負(fù)責(zé)維護(hù)運(yùn)行時(shí)數(shù)據(jù)區(qū)中可變的應(yīng)用程序內(nèi)存空間。

類加載器(ClassLoader

類加載器將類加載到內(nèi)存,并管理類的生命周期,知道將類從內(nèi)存中卸載結(jié)束生命周期。

系統(tǒng)提供了三種類加載器,分別用于不同類的加載:

  1. 啟動類加載器(Bootstrap ClassLoader),該加載器會將lib目錄下能被虛擬機(jī)識別的類加載到內(nèi)存中,也就是系統(tǒng)類

  2. 擴(kuò)展類加載器(Extension ClassLoader),該加載器會將libext目錄下的類庫加載到內(nèi)存

  3. 應(yīng)用程序類加載器(Application ClassLoader),該加載器負(fù)責(zé)加載用戶路徑上所指定的類庫。

運(yùn)行時(shí)數(shù)據(jù)區(qū)(Runtime Data Area

運(yùn)行時(shí)數(shù)據(jù)區(qū),是JVM運(yùn)行時(shí),在內(nèi)存中分配的空間。

運(yùn)行時(shí)數(shù)據(jù)區(qū),被分為五個(gè)不同的結(jié)構(gòu):

  1. Java虛擬機(jī)棧(Java Stacks): 也叫棧內(nèi)存,主管Java程序的運(yùn)行,是在線程創(chuàng)建時(shí)創(chuàng)建,它的生命期是跟隨線程的生命期,線程結(jié)束棧內(nèi)存也就釋放,對于棧來說不存在垃圾回收問題,只要線程一結(jié)束該棧就Over,生命周期和線程一致,是線程私有的。

  2. 本地方法棧(Native Method Memory): 登記的native方法,執(zhí)行引擎執(zhí)行時(shí)加載

  3. 程序寄存器(PC Registers): 當(dāng)前線程所執(zhí)行字節(jié)碼的指針,存儲每個(gè)線程下一步要執(zhí)行的字節(jié)碼JVM指令。

  4. Java堆(Heap Memory): 應(yīng)用的對象和數(shù)據(jù)都是存在這個(gè)區(qū)域,這塊區(qū)域也是線程共享的,也是gc 主要的回收區(qū),一個(gè) JVM 實(shí)例只存在一個(gè)堆類存,堆內(nèi)存的大小是可以調(diào)節(jié)的。類加載器讀取了類文件后,需要把類、方法、常變量放到堆內(nèi)存中,以方便執(zhí)行器執(zhí)行。

  5. 方法區(qū)(Method Area): 所有定義的方法的信息都保存在該區(qū)域,此區(qū)域?qū)儆诠蚕韰^(qū)間。靜態(tài)變量+常量+類信息+運(yùn)行時(shí)常量池存在方法區(qū)中,實(shí)例變量存在堆內(nèi)存中。

其中的程序寄存器、Java虛擬機(jī)棧是按照線程分配的,每個(gè)線程都有自己私有的獨(dú)立空間。

運(yùn)行的方法和運(yùn)行期數(shù)據(jù),以棧幀的形式存儲在運(yùn)行時(shí)JVM虛擬機(jī)棧中,棧幀中保存了本地變量,包括輸入輸出參數(shù)和本地變量;保存類文件和方法等幀數(shù)據(jù),還記錄了出棧入棧操作。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過程就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程。

堆在JVM是所有線程共享的,因此在其上進(jìn)行對象內(nèi)存的分配均需要進(jìn)行加鎖。

執(zhí)行引擎(Execution Engine

執(zhí)行引擎由三個(gè)模塊組成,分別是執(zhí)行引擎,JIT CompilerGarbage Collector,執(zhí)行引擎的核心是Jit Compiler,執(zhí)行字節(jié)碼或者本地方法;垃圾回收器,則是一系列線程,負(fù)責(zé)管理分代堆內(nèi)存。

三個(gè)模塊分別是運(yùn)行時(shí)計(jì)算和運(yùn)行時(shí)內(nèi)存的管理,負(fù)責(zé)執(zhí)行運(yùn)行時(shí)指令的是執(zhí)行引擎,通過程序寄存器和虛擬機(jī)棧中的棧幀出入棧實(shí)現(xiàn)方法和指令的執(zhí)行。GC則負(fù)責(zé)堆內(nèi)存的管理,因?yàn)镚C的時(shí)候需要停止指令的執(zhí)行,消耗資源,所以采用分代方式管理對象收集。JIT則是把字節(jié)碼編譯成本地二進(jìn)制代碼,并調(diào)用本地庫執(zhí)行。

GC垃圾回收機(jī)制

Java的內(nèi)存管理,主要是針對的堆內(nèi)存,因?yàn)槎褍?nèi)存是運(yùn)行時(shí)程序和數(shù)據(jù)分配的空間;不同于內(nèi)存的其他區(qū)域,加載完程序之后,基本上可以確定需要占用的空間大?。籬eap memory 空間會在運(yùn)行時(shí)動態(tài)的分配,無法預(yù)測,可大可小,而且快速變化,管理不慎就容易產(chǎn)生內(nèi)存溢出,所以由JVM提供了強(qiáng)大的分代內(nèi)存管理機(jī)制。

JVM 使用分代內(nèi)存管理,來分配運(yùn)行時(shí)的堆內(nèi)存結(jié)構(gòu),針對不同的世代,采用不同的垃圾回收算法

常用垃圾回收算法

  • 引用計(jì)數(shù)器法(Reference Counting)

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

  • 復(fù)制算法(Coping)

  • 標(biāo)記壓縮法(Mark-Compact)

  • 分代算法(Generational Collecting)

  • 分區(qū)算法(Region)

堆內(nèi)存的組成

heap 的組成有三區(qū)域/世代:分別是新生代(Young Generation)、老生代(Old Generation/tenured)和永久區(qū)(Perm)。

新生代堆內(nèi)存又分成Eden區(qū)和兩個(gè)生存區(qū),其中Eden區(qū)和生存區(qū)的占比為8:1:1,在清理新生代內(nèi)存的時(shí)候,使用的是復(fù)制清除算法,優(yōu)點(diǎn)是清除以后不會產(chǎn)生碎片;簡單的復(fù)制算法,將內(nèi)存分成大小相同的兩個(gè)區(qū)域,每次周期只分配其中的一半,這樣空間利用率比較低,只使用了一半的內(nèi)存。

考慮到新生代內(nèi)存區(qū)的對象都是周期很短的,所以JVM實(shí)現(xiàn)了一種優(yōu)化的復(fù)制算法,設(shè)置一個(gè)較大的Eden區(qū)來分配對象內(nèi)存,Eden區(qū)空間不夠了觸發(fā)垃圾回收,將上一個(gè)生存區(qū)和Eden區(qū)中還存活的對象,復(fù)制到空閑的生存區(qū)。然后清空上述兩個(gè)區(qū)域,這樣就不會產(chǎn)生內(nèi)存碎片。

將清理一定次數(shù)(15次)還生存的對象,定期晉升到老生代內(nèi)存區(qū),如果生存區(qū)空間不夠了,則馬上就會觸發(fā)晉升機(jī)制。將部分對象直接晉升到老生代。

如果晉升之后,發(fā)現(xiàn)老生代內(nèi)存不夠,就會觸發(fā)完整的全局GC,清理老生代和新生代內(nèi)存,老生代內(nèi)存清理需要使用標(biāo)記清除和標(biāo)記整理兩種算法。

GC工作原理

分配內(nèi)存的時(shí)候,首先分配到新生代的Eden區(qū),如果Eden區(qū)滿了,就會發(fā)起一次Minor GC,將Eden和From Survivor生存的對象,拷貝到To Survivor Space,如果清理過程中,to Space的空間占用達(dá)到一定閾值,或者有對象經(jīng)歷Minor GC的次數(shù)達(dá)標(biāo),就會將對象移動到老生代內(nèi)存。如果移動過程中發(fā)現(xiàn),老生代內(nèi)存的空間已經(jīng)不夠了。這時(shí)就需要發(fā)起Full GC,先進(jìn)行一次Minor GC,然后通過CMS進(jìn)行標(biāo)記清除算法,清理老生代內(nèi)存,老生代內(nèi)存經(jīng)歷標(biāo)記清除之后,因?yàn)闀a(chǎn)生內(nèi)存碎片,還需要采用標(biāo)記整理算法,將所有內(nèi)存塊往前移動,形成連續(xù)的內(nèi)存空間。

老生代標(biāo)記清除的優(yōu)點(diǎn)是不需要額外空間。不同于老生代清除算法,會產(chǎn)生碎片,而且標(biāo)記算法的成本開銷也很大;在新生代清除中,因?yàn)榭紤]到大多數(shù)新生代對象生存期都是很短暫的,可以使用一種空間換時(shí)間的思路,拿出一部分內(nèi)存空間不分配,而是作為中轉(zhuǎn),將每次檢查時(shí)還生存的對象拷貝到Survivor Space,然后直接清除所有原區(qū)域的對象,因?yàn)榇罅繉ο蠖际巧嬷芷跇O短的,所以Survivor Space的空間可以遠(yuǎn)小于正常分配的空間。

不同于引用計(jì)數(shù)方法,Java使用一種 GC Roots 的對象作為起點(diǎn)開始檢查對象,當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈相連時(shí), 即該對象不可達(dá), 也就說明此對象是不可用的。就會在GC的時(shí)候收回。

GC清理類型的時(shí)候,為了防止程序地址出現(xiàn)異常,需要stop the world,清理線程會停止所有運(yùn)行線程,直到清理完,這個(gè)時(shí)候是影響性能的。

垃圾回收器的本質(zhì)

垃圾回收器在JVM層面,是由一系列不同的組件組成的,每種組件是一個(gè)獨(dú)立線程,分別執(zhí)行自己的邏輯。

新生代垃圾收集器:

  1. Serial(串行)收集器是最基本、發(fā)展歷史最悠久的收集器,它是采用復(fù)制算法的新生代收集器,。它是一個(gè)單線程收集器,只會使用一個(gè)CPU或一條收集線程去完成垃圾收集工作,更重要的是它在進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程,直至Serial收集器收集結(jié)束為止(“Stop The World”)。

  2. ParNew收集器就是Serial收集器的多線程版本,它也是一個(gè)新生代收集器。除了使用多線程進(jìn)行垃圾收集外,其余行為包括Serial收集器可用的所有控制參數(shù)、收集算法(復(fù)制算法)、Stop The World、對象分配規(guī)則、回收策略等與Serial收集器完全相同,兩者共用了相當(dāng)多的代碼。

  3. Parallel Scavenge收集器也是一個(gè)并行的多線程新生代收集器,它也使用復(fù)制算法。Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)是盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而Parallel Scavenge收集器的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量(Throughput)。

老生代垃圾收集器:

  1. Serial Old 是Serial收集器的老年代版本,它同樣是一個(gè)單線程收集器,使用“標(biāo)記-整理”(Mark-Compact)算法

  2. Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法

  3. CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,優(yōu)點(diǎn)是:并發(fā)收集、低停頓,因此CMS收集器也被稱為并發(fā)低停頓收集器(Concurrent Low Pause Collector)

面向服務(wù)端的G1收集器。

G1收集器是一款面向服務(wù)端應(yīng)用的垃圾收集器。

在使用G1收集器時(shí),Java堆的內(nèi)存布局和其他收集器有很大的差別,它將這個(gè)Java堆分為多個(gè)大小相等的獨(dú)立區(qū)域,雖然還保留新生代和老年代的概念,但是新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續(xù))的集合。

GC回收的觸發(fā)條件

Minor GC觸發(fā)條件:當(dāng)Eden區(qū)滿時(shí),觸發(fā)Minor GC

Full GC觸發(fā)條件:

  1. gc()方法的調(diào)用

  2. 老年代代空間不足

  3. 方法區(qū)空間不足

  4. CMS GC時(shí)出現(xiàn)promotion failed和concurrent mode failure

  5. 統(tǒng)計(jì)得到的Minor GC晉升到舊生代的平均大小大于老年代的剩余空間

  6. 堆中分配很大的對象

  7. 通過Minor GC后進(jìn)入老年代的平均大小大于老年代的可用內(nèi)存

  8. 由Eden區(qū)、From Space區(qū)向To Space區(qū)復(fù)制時(shí),對象大小大于To Space可用內(nèi)存,則把該對象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對象大小

GC Roots

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

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

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

  • 方法區(qū)中常量引用的對象;

  • 本地方法棧中JNI(即一般說的Native方法)引用的對象;

總結(jié)就是,方法運(yùn)行時(shí),方法中引用的對象;類的靜態(tài)變量引用的對象;類中常量引用的對象;Native方法中引用的對象。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
收藏 人收藏

    評論

    相關(guān)推薦

    華納云:java web和java有什么區(qū)別java web和java有什么區(qū)別

    的平臺,Java可以用于開發(fā)桌面應(yīng)用程序、移動應(yīng)用程序、企業(yè)級應(yīng)用程序等。 – Java Web是Jav
    的頭像 發(fā)表于 07-16 13:35 ?618次閱讀
    華納云:<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ū)別

    java環(huán)境配置成功后怎么運(yùn)行

    Java環(huán)境配置成功后,我們可以使用幾種方式來運(yùn)行Java程序。下面將詳細(xì)介紹這幾種方式以及其使用方法。 命令行運(yùn)行方式 在成功配置
    的頭像 發(fā)表于 12-06 15:57 ?1929次閱讀

    idea如何運(yùn)行main方法

    運(yùn)行main方法是指在Java程序中執(zhí)行main方法來啟動程序Java編程中,main方法是每個(gè)Ja
    的頭像 發(fā)表于 12-06 14:58 ?3472次閱讀

    idea的java運(yùn)行配置怎么弄

    Java是一種跨平臺的編程語言,可以通過Java虛擬機(jī)(JVM)在不同的操作系統(tǒng)和硬件上運(yùn)行。在運(yùn)行Java
    的頭像 發(fā)表于 12-06 14:04 ?2024次閱讀

    eclipse設(shè)置jvm內(nèi)存大小

    內(nèi)存大小,并對其背后的原理進(jìn)行解釋。 JVM(Java虛擬機(jī))是Java程序運(yùn)行環(huán)境,它負(fù)責(zé)將Java字節(jié)碼翻譯成機(jī)器碼,以便在不同的平臺
    的頭像 發(fā)表于 12-06 11:43 ?1788次閱讀

    eclipse設(shè)置java運(yùn)行環(huán)境

    在Eclipse中設(shè)置Java運(yùn)行環(huán)境是非常重要的,它能夠確保你的代碼能夠正確地編譯和運(yùn)行。下面介紹如何設(shè)置Java運(yùn)行環(huán)境。 下載和安裝J
    的頭像 發(fā)表于 12-06 11:29 ?1444次閱讀

    eclipse怎么運(yùn)行java項(xiàng)目

    在Eclipse中運(yùn)行Java項(xiàng)目是非常簡單的。下面了解一下如何在Eclipse中運(yùn)行Java項(xiàng)目。 首先,確保您已經(jīng)在Eclipse中創(chuàng)建了Jav
    的頭像 發(fā)表于 12-06 11:25 ?1913次閱讀

    jvm和jmm的區(qū)別

    JVM(Java Virtual Machine)和JMM(Java Memory Model)是 Java 開發(fā)者非常熟悉的概念。JVM 是 Java
    的頭像 發(fā)表于 12-05 14:27 ?1262次閱讀

    jvm配置metaspace最大值的參數(shù)

    JVM(Java虛擬機(jī))是Java程序運(yùn)行環(huán)境,而Metaspace是Java 8及其更高版本中引入的一種新的內(nèi)存區(qū)域,用于存儲類的元數(shù)據(jù)
    的頭像 發(fā)表于 12-05 14:21 ?1909次閱讀

    jvm內(nèi)存區(qū)域由哪幾部分組成

    JVM(Java Virtual Machine)是Java程序運(yùn)行的環(huán)境,在JVM中存在著多個(gè)不同功能的內(nèi)存區(qū)域。這些內(nèi)存區(qū)域可以被分為幾個(gè)部分,包括堆內(nèi)存、棧內(nèi)存、方法區(qū)、PC寄存
    的頭像 發(fā)表于 12-05 14:10 ?764次閱讀

    jvm管理的內(nèi)存包括哪幾個(gè)運(yùn)行時(shí)數(shù)據(jù)內(nèi)存

    JVM(Java虛擬機(jī))是Java程序運(yùn)行環(huán)境,它提供了內(nèi)存管理機(jī)制來管理Java程序所需的
    的頭像 發(fā)表于 12-05 14:09 ?511次閱讀

    jvm哪些區(qū)域會發(fā)生oom

    JVM 是 Java 虛擬機(jī)的縮寫,是Java程序運(yùn)行平臺。JVM 內(nèi)存被劃分為不同的區(qū)域,每個(gè)區(qū)域負(fù)責(zé)不同的任務(wù)和存儲不同類型的數(shù)據(jù)。其中,一些區(qū)域容易發(fā)生內(nèi)存溢出錯(cuò)誤(Out
    的頭像 發(fā)表于 12-05 11:51 ?1329次閱讀

    jvm調(diào)優(yōu)參數(shù)

    JVM(Java虛擬機(jī))是Java程序運(yùn)行環(huán)境,它負(fù)責(zé)解釋Java字節(jié)碼并執(zhí)行相應(yīng)的指令。為了提高應(yīng)用
    的頭像 發(fā)表于 12-05 11:29 ?585次閱讀

    jvm內(nèi)存模型和內(nèi)存結(jié)構(gòu)

    JVM(Java虛擬機(jī))是Java程序運(yùn)行平臺,它負(fù)責(zé)將Java程序轉(zhuǎn)換成機(jī)器碼并在計(jì)算機(jī)上執(zhí)
    的頭像 發(fā)表于 12-05 11:08 ?872次閱讀

    如何查看java程序的內(nèi)存分布

    要查看Java程序的內(nèi)存分布,首先需要了解Java程序運(yùn)行時(shí)的內(nèi)存模型。 Java
    的頭像 發(fā)表于 11-23 14:47 ?987次閱讀