Java編譯和解釋執(zhí)行對(duì)比及原理解析
編程語(yǔ)言分為低級(jí)語(yǔ)言和高級(jí)語(yǔ)言,機(jī)器語(yǔ)言、匯編語(yǔ)言是低級(jí)語(yǔ)言,C、C++、java、python等是高級(jí)語(yǔ)言。
機(jī)器語(yǔ)言是最底層的語(yǔ)言,能夠直接執(zhí)行。而我們編寫(xiě)的源代碼是人類(lèi)語(yǔ)言, 計(jì)算機(jī)只能識(shí)別某些特定的二進(jìn)制指令,在程序真正運(yùn)行之前必須將源代碼轉(zhuǎn)換成二進(jìn)制指令。
匯編語(yǔ)言通過(guò)匯編器翻譯成機(jī)器指令后執(zhí)行,一條匯編指令,對(duì)應(yīng)著一條機(jī)器指令。
高級(jí)語(yǔ)言編程的程序有三種執(zhí)行方式:
1.一種是編譯執(zhí)行,源程序先通過(guò)編譯器(負(fù)責(zé)將源程序翻譯成目標(biāo)機(jī)器指令)翻譯成機(jī)器指令,通過(guò)編譯-->鏈接-->目標(biāo)可執(zhí)行文件,然后執(zhí)行;即提前將所有源代碼一次性轉(zhuǎn)換成二進(jìn)制指令,也就是生成一個(gè)可執(zhí)行程序。比如C,C++等語(yǔ)言都是編譯執(zhí)行的。
2.一種是解釋執(zhí)行,是使用解釋器會(huì)將我們的一句句代碼解釋成機(jī)器可以識(shí)別的二進(jìn)制代碼來(lái)執(zhí)行,可以認(rèn)為是,解釋一句,執(zhí)行一句。在這個(gè)過(guò)程中,不會(huì)生成中間文件。如:腳本方式是一條條命令,在執(zhí)行時(shí),是由系統(tǒng)的解釋器,將其一條條翻譯成機(jī)器可識(shí)別的指令,例如shell腳本是由shell程序執(zhí)行的,js是由瀏覽器解釋執(zhí)行的。
3.最后一種是編譯和解釋相結(jié)合的執(zhí)行方式,下面我們來(lái)說(shuō)Java。
理解Java的幾個(gè)編譯器
前端編譯器:把.java文件轉(zhuǎn)變成.class文件。包括Sun的Javac、Eclipse JDT中的增量式編輯器(ECJ)
后端運(yùn)行期即時(shí)編譯器(JIT編譯器,Just In Time Compiler):把字節(jié)碼轉(zhuǎn)成機(jī)器碼。包括HotSpot VM的C1、C2編譯器
靜態(tài)提前編譯器(AOT編譯器,Ahead Of Time Compiler):把*.java編譯成本地機(jī)器碼。包括GNU Compiler for the Java(GCJ)、Excelsior JET
Java采用的是解釋和編譯混合的模式
在編譯時(shí)期,我們通過(guò)將源代碼編譯成.class ,配合JVM這種跨平臺(tái)的抽象,屏蔽了底層計(jì)算機(jī)操作系統(tǒng)和硬件的區(qū)別,實(shí)現(xiàn)了“一次編譯,到處運(yùn)行” 。 而在運(yùn)行時(shí)期,目前主流的JVM 都是混合模式(-Xmixed),即解釋運(yùn)行 和編譯運(yùn)行配合使用。
Java一開(kāi)始被定位為“解釋執(zhí)行”的語(yǔ)言,但是現(xiàn)在主流的虛擬機(jī)中都包含了即時(shí)編譯器JIT。
程序從源代碼到運(yùn)行經(jīng)歷階段:java程序--(編譯javac)-->字節(jié)碼文件.class-->類(lèi)裝載子系統(tǒng)化身為反射類(lèi)Class--->運(yùn)行時(shí)數(shù)據(jù)區(qū)--->(解釋執(zhí)行+JIT編譯器編譯)-->操作系統(tǒng)(Win,Linux,Mac JVM)。
.class文件就是可以到處運(yùn)行的文件。然后Java字節(jié)碼會(huì)被轉(zhuǎn)化為目標(biāo)機(jī)器代碼,這是是由JVM來(lái)執(zhí)行的,即Java的第二次編譯。
Java采用的是解釋和編譯混合的模式。它首先通過(guò)javac將源碼編譯成字節(jié)碼文件class.然后在運(yùn)行的時(shí)候通過(guò)解釋器或者JIT將字節(jié)碼轉(zhuǎn)換成最終的機(jī)器碼。
JIT將字節(jié)碼轉(zhuǎn)換成最終的機(jī)器碼:
以 Oracle JDK提供的HotSpot虛擬機(jī)為例,在HotSpot虛擬機(jī)中,提供了兩種編譯模式:解釋執(zhí)行 和 即時(shí)編譯(JIT,Just-In-Time)。解釋執(zhí)行即逐條翻譯字節(jié)碼為可運(yùn)行的機(jī)器碼,而即時(shí)編譯則以方法為單位將字節(jié)碼翻譯成機(jī)器碼(上述提到的“編譯執(zhí)行”)。前者的優(yōu)勢(shì)在于不用等待,后者則在實(shí)際運(yùn)行當(dāng)中效率更高。
即時(shí)編譯存在的意義在于它是提高程序性能的重要手段之一。根據(jù)“二八定律”(即:百分之二十的代碼占據(jù)百分之八十的系統(tǒng)資源),對(duì)于大部分不常用的代碼,我們無(wú)需耗時(shí)間將之編譯為機(jī)器碼,而是采用解釋執(zhí)行的方式,用到就去逐條解釋運(yùn)行;對(duì)于一些僅占據(jù)小部分的熱點(diǎn)代碼(可認(rèn)為是反復(fù)執(zhí)行的重要代碼),則可將之翻譯為符合機(jī)器的機(jī)器碼高效執(zhí)行,提高程序的效率,此為運(yùn)行時(shí)的即時(shí)編譯。
為了滿(mǎn)足不同的場(chǎng)景,HotSpot虛擬機(jī)內(nèi)置了多個(gè)即時(shí)編譯器:C1,C2與Graal。Graal 是Java10正式引入的實(shí)驗(yàn)性即時(shí)編譯器,在此暫不敘述(其實(shí)我不是很了解,尷尬···)。先看一下C1、C2 ,相信大家或多或少接觸過(guò)。
- C1:即Client編譯器,面向?qū)?dòng)性能有要求的客戶(hù)端GUI程序,采用的優(yōu)化手段比較簡(jiǎn)單,因此編譯的時(shí)間較短。
- C2:即Server編譯器,面向?qū)π阅芊逯涤幸蟮姆?wù)端程序,采用的優(yōu)化手段復(fù)雜,因此編譯時(shí)間長(zhǎng),但是在運(yùn)行過(guò)程中性能更好。
從Java7開(kāi)始,HotSpot虛擬機(jī)默認(rèn)采用分層編譯的方式:熱點(diǎn)方法首先被C1編譯器編譯,而后 熱點(diǎn)方法中的熱點(diǎn)再進(jìn)一步被C2編譯,根據(jù)前面的運(yùn)行計(jì)算出更優(yōu)的編譯優(yōu)化。為了不干擾程序的正常運(yùn)行,JIT編譯時(shí)放在額外的線(xiàn)程中執(zhí)行的,HotSpot根據(jù)實(shí)際CPU的資源,以 1:2的比例分配給C1和C2線(xiàn)程數(shù)。在計(jì)算機(jī)資源充足的情況,字節(jié)碼的解釋運(yùn)行和編譯運(yùn)行時(shí)可以同時(shí)進(jìn)行,JIT編譯執(zhí)行完后的機(jī)器碼會(huì)在下次調(diào)用該方法時(shí)啟動(dòng),已替換原本的解釋執(zhí)行(意思就是已經(jīng)翻譯出效率更高的機(jī)器碼,自然替換原來(lái)的相對(duì)低效率執(zhí)行的方法)。
以上,可以看出在Java中不單單是解釋執(zhí)行,即時(shí)編譯(編譯執(zhí)行)在Java性能優(yōu)化中彰顯重要的作用,所以現(xiàn)在應(yīng)該說(shuō):Java是解釋執(zhí)行和編譯執(zhí)行共同存在的,至少大部分是這樣。
編譯與解釋比較?
1.一段程序編譯會(huì)浪費(fèi)時(shí)間,并且移植到其他平臺(tái)上時(shí)還要進(jìn)行重新編譯,但是其編譯后生成的可執(zhí)行文件運(yùn)行速度快。
2.解釋型程序可跨平臺(tái)執(zhí)行,無(wú)需將全部代碼編譯之后再運(yùn)行,能夠及時(shí)運(yùn)行,但因?yàn)槭侵饤l解釋執(zhí)行所以最終的運(yùn)行速度不如編譯型程序。
3.內(nèi)存使用:編譯執(zhí)行需要生成編譯后的機(jī)器碼文件,而解釋執(zhí)行時(shí)逐句解釋執(zhí)行,所以解釋執(zhí)行對(duì)內(nèi)存占用更少。
單獨(dú)使用解釋器的缺點(diǎn):
拋棄了JIT可能帶來(lái)的性能優(yōu)勢(shì)。如果代碼沒(méi)有被JIT編譯的話(huà),再次運(yùn)行時(shí)需要重復(fù)解析。
單獨(dú)使用JIT編譯器的缺點(diǎn):
需要將全部的代碼編譯成本地機(jī)器碼。要花更多的時(shí)間,JVM啟動(dòng)會(huì)變慢非常多;
增加可執(zhí)行代碼的長(zhǎng)度(字節(jié)碼比JIT編譯后的機(jī)器碼小很多),這將導(dǎo)致頁(yè)面調(diào)度,從而降低程序的速度。
有些JIT編譯器的優(yōu)化方式,比如分支預(yù)測(cè),如果不進(jìn)行profiling,往往并不能進(jìn)行有效優(yōu)化。
因此,HotSpot采用了惰性評(píng)估(Lazy Evaluation)的做法,根據(jù)二八定律,消耗大部分系統(tǒng)資源的只有那一小部分的代碼(熱點(diǎn)代碼),而這也就是JIT所需要編譯的部分。JVM會(huì)根據(jù)代碼每次被執(zhí)行的情況收集信息并相應(yīng)地做出一些優(yōu)化,因此執(zhí)行的次數(shù)越多,它的速度就越快。
JDK 9引入了一種新的編譯模式AOT(Ahead of Time Compilation),它是直接將字節(jié)碼編譯成機(jī)器碼,這樣就避免了JIT預(yù)熱等各方面的開(kāi)銷(xiāo)。JDK支持分層編譯和AOT協(xié)作使用。
注:JIT為方法級(jí),它會(huì)緩存編譯過(guò)的字節(jié)碼在CodeCache中,而不需要被重復(fù)解釋。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
教你怎么用Java數(shù)組和鏈表實(shí)現(xiàn)棧
本篇文章為大家詳細(xì)介紹了怎么用Java數(shù)組和鏈表實(shí)現(xiàn)棧,文中有非常詳細(xì)的代碼示例及注釋,對(duì)正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05SpringBoot整合Thymeleaf小項(xiàng)目及詳細(xì)流程
這篇文章主要介紹了SpringBoot整合Thymeleaf小項(xiàng)目,本項(xiàng)目使用SpringBoot開(kāi)發(fā),jdbc5.1.48,主要涉及到Mybatis的使用,Thymeleaf的使用,用戶(hù)密碼加密,驗(yàn)證碼的設(shè)計(jì),圖片的文件上傳(本文件上傳到本地,沒(méi)有傳到數(shù)據(jù)庫(kù))登錄過(guò)濾,需要的朋友可以參考下2022-03-03SpringBoot整合Caffeine實(shí)現(xiàn)本地緩存的實(shí)踐分享
緩存是提升系統(tǒng)性能的一個(gè)不可或缺的工具,通過(guò)緩存可以避免大部分重復(fù)的請(qǐng)求到數(shù)據(jù)庫(kù)層,減少I(mǎi)O鏈接次數(shù),提升整體的響應(yīng)速率,本地緩存中比較常見(jiàn)的比如 Caffeine 緩存,這篇文章將結(jié)合具體的 Springboot 項(xiàng)目搭配 Caffeine 實(shí)現(xiàn)本地緩存的各種使用方式2024-07-07java實(shí)現(xiàn)人民幣大小寫(xiě)轉(zhuǎn)換方法分享
本文介紹java人民幣數(shù)字大小寫(xiě)轉(zhuǎn)換方法,代碼中有注釋?zhuān)蠹抑苯涌创a吧2014-01-01java并發(fā)無(wú)鎖多線(xiàn)程單線(xiàn)程示例詳解
這篇文章主要為大家介紹了java并發(fā)無(wú)鎖多線(xiàn)程單線(xiàn)程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Java注解實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的實(shí)例代碼
本篇文章主要介紹了Java注解實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06