java 执行机制_Java类的执行机制
在完成將class文件信息加載到JVM并產(chǎn)生Class對象后,就可執(zhí)行Class對象的靜態(tài)方法或?qū)嵗瘜ο筮M行調(diào)用了。在源碼編譯階段將源碼編譯為JVM字節(jié)碼,JVM字節(jié)碼是一種中間代碼的方式,要由JVM在運行期對其進行解釋并執(zhí)行,這種方式成為字節(jié)碼解釋執(zhí)行方式
字節(jié)碼解釋執(zhí)行
由于采用的為中間碼的方式,JVM有一套自己的指令,對于面向?qū)ο蟮恼Z言而言,最重要的是執(zhí)行方法的指令,JVM采用了invokestatic、invokevirtual、invokeinterface、invokespecial四個指令來執(zhí)行不同的方法調(diào)用。invokestatic對應(yīng)的是調(diào)用static方法,invokevirtual對應(yīng)的是調(diào)用對象實例的方法,invokeinterface對應(yīng)的是調(diào)用接口的方法,invokespecial對應(yīng)的是調(diào)用private方法和編譯源碼后生成的方法,此方法為對象實例化時的初始化方法(構(gòu)造方法)。
Sun
JDK基于棧的體系結(jié)構(gòu)來執(zhí)行字節(jié)碼,基于棧方式的好處是代碼緊湊,體積小。線程在創(chuàng)建后,都會產(chǎn)生程序計數(shù)器(PC)和棧(Stack):PC存放了下一條要執(zhí)行的指令在方法內(nèi)的偏移量,棧中存放了棧幀,每個方法每次調(diào)用都會產(chǎn)生棧幀。棧幀主要分為局部變量區(qū)和操作數(shù)棧兩部分,局部變量區(qū)用于存放方法中的局部變量和參數(shù),操作數(shù)棧中用于存放方法執(zhí)行過程中產(chǎn)生的中間結(jié)果,棧幀中還會有一些雜用空間,例如指向方法已解析的常量池的引用、其他一些VM內(nèi)部實現(xiàn)需要的數(shù)據(jù)等。
1、指令解釋執(zhí)行
對于方法的指令解釋執(zhí)行,執(zhí)行方式為經(jīng)典馮諾依曼體系中的FDX循環(huán)方法,即獲取下一條指令,解碼并分派,然后執(zhí)行。在實現(xiàn)FDX循環(huán)式有switch-threading、token-threading、direct-threading、subroutine-threading、inline-threading等多種方式。Sun
JDK的重點為編譯成機器碼,并沒有在解釋器上做太復(fù)雜的處理,因此采用了token-threading方式。為了讓解釋執(zhí)行能更加高效,Sun
JDK還做了一些其他的優(yōu)化,主要有:棧頂緩存和部分棧幀共享。
2、棧頂緩存
在方法的執(zhí)行過程中,可看到有很多操作要將值放入操作數(shù)棧,這導(dǎo)致了寄存器和內(nèi)存要不斷的交換數(shù)據(jù),Sun
JDK采用了一個棧頂緩存,即將本來位于操作數(shù)棧的值直接緩存在寄存器上,這對于大部分只需要一個值的操作而言,無需將數(shù)據(jù)放入操作數(shù)棧,可直接在寄存器計算,然后放回操作數(shù)棧。
3、部分棧幀共享
當(dāng)一個方法調(diào)用另一個方法時,通常傳入另一個方法的參數(shù)為已存放在操作數(shù)棧的數(shù)據(jù),Sun
JDK在此做了一個優(yōu)化,就是當(dāng)調(diào)用方法時,后一方法可將前一方法的操作數(shù)棧作為當(dāng)前方法的局部變量,從而節(jié)省數(shù)據(jù)copy帶來的消耗。
編譯執(zhí)行
編譯執(zhí)行的效率較低,為提升代碼的執(zhí)行性能,SunJDK提供將字節(jié)碼編譯為機器碼的支持,編譯在運行時進行,通常稱為JIT編譯器。SunJDK在執(zhí)行過程中對執(zhí)行頻率高的代碼進行編譯,對執(zhí)行不頻繁的代碼則繼續(xù)采用解釋執(zhí)行的方式,因此SunJDK又稱為Hotspot
VM,在編譯上SunJDK提供了兩種模式client compiler(-client)和server
compiler(-server)。
client
compliler又稱為C1,較為輕量級,只做少量性能開銷比高的優(yōu)化,它占用內(nèi)存較少,適合桌面交互式應(yīng)用,在寄存器分配策略上,JDK6以后采用的為線性掃描寄存器分配算法,在其他方面的優(yōu)化主要有:方法內(nèi)嵌、去虛擬化、冗余消除等。
1、方法內(nèi)聯(lián)
對于java面向?qū)ο蟮恼Z言,通常要調(diào)用多個方法來完成功能,執(zhí)行時,要經(jīng)歷多次參數(shù)傳遞,返回值傳遞及跳轉(zhuǎn)等,于是C1采用了方法內(nèi)聯(lián)的方式,即把調(diào)用到的方法的指令直接植入到當(dāng)前的方法中。(可在debug版本的JDK的啟動參數(shù)加速-XX:PrintInlining來查看方法內(nèi)聯(lián)的信息)
2、去虛擬化
去虛擬化是指在裝載class文件后,進行類層次的分析,如發(fā)現(xiàn)類中的方法只提供一個實現(xiàn)類,那么對于調(diào)用此方法的代碼,也可進行方法內(nèi)聯(lián),從而提升執(zhí)行的性能。
3、冗余消除
冗余消除是指在編譯時,根據(jù)運行時狀況進行代碼折疊或消除。如某個判斷條件為false則可將條件內(nèi)的代碼消除。
Server
Compiler又稱為C2,較為重量級,C2采用了大量傳統(tǒng)編譯優(yōu)化技巧,占用內(nèi)存會相對多,適用于服務(wù)器應(yīng)用。和C1不同的主要是寄存器分配策略和優(yōu)化的范圍,寄存器分配策略上C2采用的為傳統(tǒng)的圖著色寄存器分配算法:由于C2會收集程序的運行信息(收集的信息主要有,分支的跳轉(zhuǎn)/不跳轉(zhuǎn)的頻率,某條指令出新過的類型、是否出現(xiàn)過控制、是否出現(xiàn)過一場),因此其優(yōu)化的范圍更多在于全局的優(yōu)化,而不僅僅是一個方法塊的優(yōu)化。
逃逸分析是C2進行很多優(yōu)化的基礎(chǔ),逃逸分析是指根據(jù)運行狀況來判斷方法中的變量是否會被外部讀取,如不會則認(rèn)為此變量是逃逸的,基于逃逸分析C2在編譯時會做標(biāo)量替換、棧上分配和同步消除等優(yōu)化。
1、標(biāo)量替換
變量替換的意思簡單來說就是用標(biāo)量替換聚合量。這種方式能帶來的好處是,如果創(chuàng)建的對象并未用到其中的全部變量,則可以節(jié)省一定的內(nèi)存。
2、棧上分配
如果變量沒有逃逸,那么C2會選擇在棧上直接創(chuàng)建對象實例,而不是在JVM堆上,在棧上分配的好處一方面是更加快速,另一方面是回收時隨著方法的結(jié)束,對象也被回收了。
3、同步消除
同步消除是指如果發(fā)現(xiàn)同步的對象未逃逸,那也沒有同步的必要了,在C2編譯時會直接去掉同步。
除了C1\C2外,還有一種較為特殊的編譯:OSR(On
Stack
Replace)。OSR和C1、C2的最主要的不同點在于OSR編譯只替換循環(huán)代碼體的入口,而C1、C2替換的是方法調(diào)用的入口,因此在OSR編譯后會出現(xiàn)的現(xiàn)象是方法的整段代碼都被編譯了,但在只有循環(huán)代碼體部分才執(zhí)行編譯后的機器碼,其他部分仍然是是解釋執(zhí)行方法。
默認(rèn)情況下,SunJDK根據(jù)機器配置來選擇C1或C2模式,當(dāng)機器配置CPU超過2核且內(nèi)存超過2GB及默認(rèn)為C2模式,但在32位機器上時鐘選擇的都是C1模式,也可在啟動時通過增加-client或-server來強制指定。
SunJDK為提升程序執(zhí)行性能,在C1好C2上做了很多努力,其他各種實現(xiàn)的JVM也在編譯執(zhí)行上做了很多的優(yōu)化,SunJDK之所以未選擇在啟動時即編譯成機器碼,主要是因為:靜態(tài)編譯并不能根據(jù)程序的運行狀況來優(yōu)化執(zhí)行的代碼,C2這種方式是根據(jù)運行狀況來進行動態(tài)編譯的,如分支判斷、逃逸分析等,這些措施會提升程序執(zhí)行的性能,在靜態(tài)編譯的情況下是無法實現(xiàn)的,給C2收集運行數(shù)據(jù)越長時間,編譯出的代碼也會越優(yōu);解釋執(zhí)行比編譯執(zhí)行更節(jié)省內(nèi)存;啟動時解釋執(zhí)行的啟動速度比編譯在啟動更快。
當(dāng)程序在未編譯期間解釋執(zhí)行會比較慢,因此需要取一個權(quán)衡值,在SunJDK中主要依據(jù)方法上的兩個計數(shù)器是否超過閾值,其中一個計數(shù)器為調(diào)用計數(shù)器,即方法被調(diào)用的次數(shù);另一個為回邊計數(shù)器,即方法中循環(huán)執(zhí)行部分代碼的執(zhí)行次數(shù)。下面介紹兩個計數(shù)器對應(yīng)的閾值
(1)ComplieThresold
該值是指方法被調(diào)用多少次后,就編譯為機器碼,在client模式下默認(rèn)為1500次,在server模式下為10000次,可通過在啟動時天機-XX:CompilerThreshold=10000來設(shè)置該值
(2)OnStackReplacePercentage
該值用于計算是否觸發(fā)OSR編譯的閾值,默認(rèn)模式下client為933,server模式下為140,該值可通過在啟動時添加-XX:OnStackReplacePercetage=140來設(shè)置。
反射執(zhí)行
反射執(zhí)行是Java的亮點之一,基于反射可動態(tài)調(diào)用某對象實例中對應(yīng)的方法、訪問查看對象的屬性等,無需再編寫代碼時就確定要創(chuàng)建的對象。這使得Java可以很靈活的實現(xiàn)對象的調(diào)用,例如MVC框架中通常要調(diào)用實現(xiàn)類中的execute方法,但框架在編寫時是無法知道實現(xiàn)類的,在Java中則可以通過反射機制直接去調(diào)用應(yīng)用類的實現(xiàn)類中的execute方法。
這種方式對于框架之類的代碼而言非常重要,反射和直接創(chuàng)建對象實例調(diào)用方法的最大不同在于創(chuàng)建的過程、方法調(diào)用的過程是動態(tài)的。這也是的采用反射生成執(zhí)行方法調(diào)用的代碼并不像直接調(diào)用實例對象代碼,編譯后就可直接生成對象方法調(diào)用的字節(jié)碼,而是只能生成調(diào)用JVM反射實現(xiàn)的字節(jié)碼了。
要實現(xiàn)動態(tài)的調(diào)用,最直接的方法就是動態(tài)生成字節(jié)碼,并加載到JVM中執(zhí)行,SunJDK采用的即為這種方法。
總結(jié)
以上是生活随笔為你收集整理的java 执行机制_Java类的执行机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 惠斯通电桥信号调理芯片_elmos推出专
- 下一篇: 哈希表数据结构_Java数据结构哈希表如