摘要:字節碼生成把語法樹定義的抽象的語法結構按照二進制字節碼的規則排布成字節碼,最終我們可以看到滿足虛擬機運行要求的二進制字節碼被轉換出來。上面的過程完成后,命令扮演的編譯器就將源代碼轉成了結構化的二進制字節碼。
這篇文章的素材來自周志明的《深入理解Java虛擬機》。
作為Java開發人員,一定程度了解JVM虛擬機的的運作方式非常重要,本文就一些簡單的虛擬機的相關概念和運作機制展開我自己的學習過程,是這個系列的第四篇。
Java字節碼的編譯生成我們討論完了字節碼的結構和活化字節碼在執行引擎下的執行之后要回到字節碼的原點:java的字節碼是怎么形成的呢?
我們這里討論的僅僅是從程序員編寫的java源代碼的編譯得到的字節碼,但是要知道的事,字節碼不僅僅可以從源文件編譯生成,字節碼可以通過直接用二進制的字節拼接產生,這個拼接的起點除了間接通過編譯期生成,也可以通過直接寫進內存,比如通過動態代理構造的臨時代理類就是通過直接寫入內存的二進制字節碼形成的,再比如jsp通過jsp轉換器可以轉變為一個對應的請求處理類,等等。總之,我們在這里討論的僅僅是通過編譯期將靜態的java源代碼文件編譯成二進制字節碼。
使用javac編寫的java命令是編譯過程的執行者,這個命令的使命就是把java源文件轉換成為java二進制字節碼,javac完成這一使命的步驟主要包括如下的子過程:
解析與填充符號表
插入式注解處理器的注解處理過程
分析與字節碼生成
這個過程的詳細數據流和控制流如下:
這些過程的目的和一般的傳統的編譯過程類似,因為和傳統編譯過程的源文件到機器代碼的目的相比,java源代碼到虛擬機二進制字節碼的編譯過程只是最終運行的平臺是虛擬機,除此之外大致是一樣的處理辦法。
解析這個過程是以源代碼為輸入流,詞法分析器和語法分析器為控制器,抽象語法樹為輸出流,最終生成的語法樹是一個以各種語法節點(接口、包等)為頂層節點的樹結構,詞法分析器對輸入流轉換成詞法元單位Token的序列,語法分析器對Token序列進行分析得到最終的語法樹,順著這個語法樹的各個頂層節點,可以找到程序中所有的變量、方法甚至是注釋的各種信息。語法樹是后期語義分析的基礎。
一個語法樹的實例:
在解析過后會分析生成的語法樹中的各類符號,包括程序中的各類符號的信息都將存儲在這個符號表里。在經歷完這一步之后符號表將成為一個包含了語法樹頂層節點的表,順著這個表可以分類地尋到每個符號的信息。
注解處理過程在引入注解之后加入了對插入式注解處理器的編譯過程,注解是形如”@XX”的語法結構,這個語法結構的目的當然不是簡單的標記,而是對應到了一個對應的注解處理器之上,注解完成的任務是注解處理器定義的,因此在這個過程里注解處理器定義的任務將會以修改語法樹的方式起作用。每次處理一個注解后都可能會改變語法樹的結構,然后再啟用符號表的填充,這個循環(round)將會是一次小規模的重建語法樹和符號表,當掃描完所有的注解后語法樹的結構在這個階段將會穩定下來,然后給出一個為下面過程提供信息的To do List。實際上注解處理過程是程序員在編譯過程中控制程序的很少的機會,因為其他過程大都是是編譯器以無人為控制(沒有程序員編寫程序的指導)的情況下的處理。
語義分析能通過詞法語法分析并不意味著語義上是成立的,因此這個過程是處理語義的過程,語義分析器通過對符號表索引的語法樹的分析,對程序表達的語義進行分析。它包括幾個字過程:
標注檢查:主要是類型對應變量聲明以及常量折疊的檢查;
數據控制流檢查:對程序上下文邏輯的檢驗,包括局部變量賦值、返回值和異常處理等;
解語法糖:語法糖是程序員友好的語法規則,這些友好的規則還是要在這個過程中解開成為真正需要表達的意思的(裝箱拆箱、泛型、遍歷循環等的語法都會在底層替換稱為“復雜”的實現)。
字節碼生成把語法樹定義的抽象的語法結構按照class二進制字節碼的規則排布成class字節碼,最終我們可以看到滿足虛擬機運行要求的二進制字節碼被轉換出來。在這個過程中還會有特定的代碼添加和初級的優化,比如默認的類構造器
注意不是構造函數,構造函數是在填充符號表的階段完成的,構造函數用于完成new操作,而構造器是在內存中構造出該類的基本結構,而構造函數是語法層級較高的操作,同時還會將靜態代碼塊static{}加入類構造器,將構造代碼塊{}加入到實例構造器中,包括實例變量和類變量的初始化、父類構造器的調用等過程都會加入到構造器中去。
上面的過程完成后,javac命令扮演的編譯器就將源代碼轉成了結構化的二進制字節碼。
Java字節碼的運行優化 解釋執行字節碼的運行過程我們在第三篇的時候已經解釋過了:
Java虛擬機 :Java字節碼指令的執行
當時我們看到的是逐一把二進制命令執行,也就是說執行引擎每取一條二進制指令就執行一次,這種執行方式稱為解釋執行(interpreted mode),我們其實可以看出解釋執行的優點在于每次執行的時候都會確知當前程序的狀態,但是每次執行都要從方法區里取命令,然后再能夠在堆棧中執行操作,每次都去取指令無疑是會減慢執行速度的,即便把馬上要執行的命令置于高速緩沖上。
即時編譯執行基于這個弱點就有了另一種執行模式,編譯模式(compiled mode),這個模式中非常重要的參與者就是JIT即時編譯器(Just Intime),編譯模式的原理其實就像是C一樣的編譯型語言一樣把源代碼直接編譯成機器語言然后一口氣運行完,省去了每次取指令的時間(只不過C是直接把源代碼編譯成機器碼,而java是把二進制字節碼通過虛擬機的JIT即時編譯器編成本地機器碼)。
JIT觸發的條件:
不是所有的代碼被以編譯模式執行都是好的,因為JIT編譯本身也是費時的,所以必須在非常有必要進行編譯的部分才應該去編譯,這些地方就是需要反復使用的部分,因為反復使用的部分是需要進行進行最大化優化的,而只用幾次的代碼可能使用的時間還不及JIT編譯的時間,這樣做就沒有“性價比”了。所以我們來看看被稱為hotspot的這些反復使用的代碼被編譯模式執行的特點:
如果是多次調用的方法或者是多次執行的循環體就是hotspot的。
一般虛擬機會為每個方法添加一個計數器,這個計數器用于計量方法執行的次數,當這個計數器計量這個方法調用超過某個閾值時就會觸發JIT編譯器對這個方法的編譯,即時編譯后的代碼會成為本地機器碼,執行速度會大大加快,同時由于這個方法使用次數非常多,所以將會大大加速程序的運行。當然這個過程不是僅僅這么簡單,因為如果這樣的話程序運行時間足夠長的話會有很多并不那么“熱”的代碼也會成為hotspot的,比如某段代碼運行了一段時間后陷入了“冷”狀態,那么這段代碼就算不上是hotspot的,因此默認情況下虛擬機查看的更是代碼在一個時間內的調用頻率,如果一段時間內的使用次數足夠多才會說明這段代碼是hotspot的。
同樣的,循環體會被虛擬機加入一個回邊計數器用以統計循環體的使用頻率。
下面展示的就是在JIT這套機制下的編譯模式的執行流程:
值得注意的是,JIT編譯的時候并不是說線程就停在這里一直等待編譯的本地機器碼的結果出現,而是繼續以解釋模式執行,這能充分利用執行時間,等到下次執行到這里的時候再看看是否JIT編譯已經有了結果,如果有了就去執行本地代碼,否則還得解釋執行以繼續等待。
JIT即時編譯器在后臺執行的編譯任務時也會首先對字節碼進行優化,包括方法內聯和常量傳播等策略,然后轉換成高級中間代碼表示,再進行一次優化,然后轉為平臺相關的低級中間代碼表示,再進行一次優化,最后變成平臺相關的機器代碼。這個底層的優化過程屬于相對機器層級的優化。
這里所提的還有幾個編譯過程中的比較典型的優化技術:
公共子表達式消除:用于消除重復計算帶來的性能損失;
數組邊界檢查消除:編譯期確定的數組范圍將不必要的邊界檢查條件去除;
方法內聯:避免方法調用的時候產生的棧切換和現場恢復等過程帶來的損耗,由于java的因為虛方法的重載重寫等問題帶來的方法分派問題,內聯的結果不能確定一定正確,所以才用的一般是激進優化失敗退回的策略;
逃逸分析:如果一個方法中的局部變量不會通過調用函數作為參數傳出被外部方法或線程使用的時候,可以采用更加高效的辦法優化:
棧上分配:在棧上直接為變量對象分配空間,因為知道了這個對象不會發生逃逸被外部訪問到,所以某種程度上來講這就是一個“臨時封閉在方法里”的對象,所以這種棧上分配的辦法不會造成問題。使用完畢后就將它直接釋放,也減小了gc的壓力。
同步消除:同理的,不會被外部線程訪問到的“臨時封閉在方法里”的對象是不會發生共享的,所以可以消除它的同步標記。
標量替換:如果一個局部變量對象是“臨時封閉在方法里”的對象,那么就完全沒有必要建立一個完整的對象,只需要在棧上創建它的相關字段就可以了,這樣做可以加速對真正被訪問的變量的速度。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/64702.html
摘要:近期在閱讀最新幾版的官方文檔過程中發現不少術語不清之處特發此文總結以下的術語大量在官方文檔中直接出現且直接如基本詞語一樣使用不理解它們會嚴重影響閱讀自適應自旋鎖自適應自旋鎖是一個允許線程在特定點自旋等待特定事件發生而不是直接進行并等待該事件 近期在閱讀JAVA最新幾版的官方文檔過程中發現不少術語不清之處,特發此文總結.以下的術語大量在官方文檔中直接出現,且直接如基本詞語一樣使用,不理解...
摘要:前言本文內容基本摘抄自深入理解虛擬機,以供復習之用,沒有多少參考價值。此區域是唯一一個在虛擬機規范中沒有規定任何情況的區域。堆是所有線程共享的內存區域,在虛擬機啟動時創建。虛擬機上把方法區稱為永久代。 前言 本文內容基本摘抄自《深入理解Java虛擬機》,以供復習之用,沒有多少參考價值。想要更詳細了解請參考原書。 第二章 1.運行時數據區域 showImg(https://segment...
摘要:語言通過字節碼的方式,在一定程度上解決了傳統解釋型語言執行效率低的問題,同時又保留了解釋型語言可移植的特點。有針對不同系統的特定實現,,,目的是使用相同的字節碼,它們都會給出相同的結果。 showImg(https://segmentfault.com/img/bVbsjCK?w=800&h=450); 一、面向對象和面向過程的區別 面向過程優點: 性能比面向對象高,因為類調用時需要實...
摘要:這個龐大的結構主要包含以下幾個部分魔數和版本號基本的信息用于確定二進制字節碼的特征和加載可行特征。 這篇文章的素材來自周志明的《深入理解Java虛擬機》。 作為Java開發人員,一定程度了解JVM虛擬機的的運作方式非常重要,本文就一些簡單的虛擬機的相關概念和運作機制展開我自己的學習過程,是這個系列的第二篇。 我們在文件里寫入了java的源代碼,源代碼寫就后存入磁盤,磁盤上的源代碼經過j...
閱讀 2025·2023-04-25 14:50
閱讀 2907·2021-11-17 09:33
閱讀 2611·2019-08-30 13:07
閱讀 2838·2019-08-29 16:57
閱讀 907·2019-08-29 15:26
閱讀 3540·2019-08-29 13:08
閱讀 1990·2019-08-29 12:32
閱讀 3382·2019-08-26 13:57