🏆「作者推薦!」【Java 技術之旅】徹底你明白什麼是JIT編譯器(Just In Time編譯器)

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"前提概要","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"我們都知道開發語言整體分爲兩類,一類是編譯型語言,一類是解釋型語言。那麼你知道二者有何區別嗎?編譯器和解釋器又有什麼區別?","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這是爲了兼顧啓動效率和運行效率兩個方面。Java程序最初是通過解釋器進行解釋運行的,當虛擬機返現某個方法或代碼塊的運行特別頻繁時,就會把這段代碼標記爲熱點代碼,爲了提供熱點代碼的運行效率,在運行時,虛擬機就會把這些代碼編譯成與本地平臺相關的機器碼。並進行各種層次的優化。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"編譯器和解釋器","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Java編譯器(javac)的作用是將java源程序編譯成中間代碼字節碼文件,是最基本的開發工具。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Java解釋器(java)(英語:Interpreter),又譯爲直譯器,是一種電腦程序,能夠把高級編程語言一行一行直接轉譯運行。解釋器不會一次把整個程序轉譯出來,只像一位“中間人”,每次運行程序時都要先轉成另一種語言再作運行,因此解釋器的程序運行速度比較緩慢。","attrs":{}},{"type":"text","text":" 它每轉譯一行程序敘述就立刻運行,然後再轉譯下一行,再運行,如此不停地進行下去。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/98/98530c424d9f100f292a11afe4044e77.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"當程序需要首次啓動和執行的時候,解釋器可以首先發揮作用,一行一行直接轉譯運行,但效率低下。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"當多次調用方法或循環體時JIT編譯器可以發揮作用,把越來越多的代碼編譯成本地機器碼,之後可以獲得更高的效率(佔內存),此時就有了智能化的編譯器(JIT編譯器)","attrs":{}}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解釋器與編譯器的交互:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/66/66cfef1a3c31ffbdb1182064df9ee185.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HotSpot虛擬機中內置了兩個即時編譯器,分別稱爲Client Complier和Server Complier,它會根據自身版本與宿主機器的硬件性能自動選擇運行模式,用戶也可以使用\"-client\"或\"-server\"參數去強制指定虛擬機運行在Client模式或Server模式","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"什麼是JIT編譯器","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"即時(Just-In-Time)編譯器是Java運行時環境的一個組件,它可提高運行時Java應用程序的性能。JVM中沒有什麼比編譯器更能影響性能,而選擇編譯器是運行Java應用程序時做出的首要決定之一。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"當編譯器做的激進優化不成立,如載入了新類後類型繼承結構出現變化。出現了罕見陷阱時能夠進行逆優化退回到解釋狀態繼續運行。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/41/41e8efd3420a4bbffd0ed38e2f339bf8.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解釋器與編譯器搭配使用的方式:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HotSpot JVM內置了兩個編譯器,各自是Client Complier和Server Complier,虛擬機默認是Client模式。我們也能夠通過。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"-client:強制虛擬機運行Client模式","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"-server:強制虛擬機運行Server模式","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"默認(java -version混合模式)","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"而不管是Client模式還是Server模式,虛擬機都會運行在解釋器和編譯器配合使用的混合模式下。能夠通過。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"解釋模式(java -Xint -version)強制虛擬機運行於解釋模式,僅使用解釋器方式執行。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"編譯模式(java -Xcomp -version)優先採用編譯方式執行程序,但解釋器要在編譯無法進行的情況下介入執行過程。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"java -version\njava version \"1.8.0_121\"\nJava(TM) SE Runtime Environment (build 1.8.0_121-b13)\nJava HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"java -Xint -version\njava version \"1.8.0_121\"\nJava(TM) SE Runtime Environment (build 1.8.0_121-b13)\nJava HotSpot(TM) 64-Bit Server VM (build 25.121-b13, interpreted mode)\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"java -Xcomp -version\njava version \"1.8.0_121\"\nJava(TM) SE Runtime Environment (build 1.8.0_121-b13)\nJava HotSpot(TM) 64-Bit Server VM (build 25.121-b13, compiled mode)\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Java功能“一次編譯,到處運行”的關鍵是 bytecode。字節碼轉換爲應用程序的機器指令的方式對應用程序的速度有很大的影響","attrs":{}},{"type":"text","text":"。這些字節碼可以被解釋,編譯爲本地代碼,或者直接在指令集架構中符合字節碼規範的處理器上執行。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"解釋字節碼的是Java虛擬機(JVM)的標準實現,這會使程序的執行速度變慢。爲了提高性能,JIT編譯器在運行時與JVM交互,並將適當的字節碼序列編譯爲本地機器代碼。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"使用JIT編譯器時,硬件可以執行本機代碼,而不是讓JVM重複解釋相同的字節碼序列,並導致翻譯過程相對冗長。這樣可以提高執行速度,除非方法執行頻率較低。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"JIT編譯器編譯字節碼所花費的時間被添加到總體執行時間中,並且如果不頻繁調用JIT編譯的方法,則可能導致執行時間比用於執行字節碼的解釋器更長。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"當將字節碼編譯爲本地代碼時,JIT編譯器會執行某些優化。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"由於JIT編譯器將一系列字節碼轉換爲本機指令,因此它可以執行一些簡單的優化。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JIT編譯器執行的一些常見優化操作包括數據分析,從堆棧操作到寄存器操作的轉換,通過寄存器分配減少內存訪問,消除常見子表達式等。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JIT編譯器進行的優化程度越高,在執行階段花費的時間越多。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"因此,JIT編譯器無法承擔所有靜態編譯器所做的優化,這不僅是因爲增加了執行時間的開銷,而且還因爲它只對程序進行了限制。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/45/45ff1f6cb77e073962b74240e1bcd488.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"JIT編譯器默認情況下處於啓用狀態,並在調用Java方法時被激活。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"JIT編譯器將該方法的字節碼編譯爲本地機器代碼,“即時”編譯以運行。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"編譯方法後,JVM會直接調用該方法的已編譯代碼,而不是對其進行解釋。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從理論上講,如果編譯不需要處理器時間和內存使用量,則編譯每種方法都可以使Java程序的速度接近本機應用程序的速度。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JIT編譯確實需要處理器時間和內存使用率。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"JVM首次啓動時,將調用數千種方法。即使程序最終達到了非常好的峯值性能,編譯所有這些方法也會嚴重影響啓動時間。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"不同應用程序的不同編譯器","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"JIT編譯器有兩種形式,並且選擇使用哪個編譯器通常是運行應用程序時唯一需要進行的編譯器調整。實際上,即使在安裝Java之前,也要考慮知道要選擇哪個編譯器,因爲不同的Java二進制文件包含不同的編譯器。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"客戶端編譯器","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"著名的優化編譯器是C1,它是通過-clientJVM啓動選項啓用的編譯器。顧名思義,C1是客戶端編譯器。它是爲客戶端應用程序設計的,這些客戶端應用程序具有較少的可用資源,並且在許多情況下對應用程序啓動時間敏感。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"C1使用性能計數器進行代碼性能分析,以實現簡單,相對無干擾的優化","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"服務器端編譯器","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於長時間運行的應用程序(例如服務器端企業Java應用程序),客戶端編譯器可能不夠。可以使用類似C2的服務器端編譯器。通常通過將JVM啓動選項添加-server到啓動命令行來啓用C2 。由於大多數服務器端程序預計將運行很長時間,因此啓用C2意味着您將能夠比使用運行時間短的輕量級客戶端應用程序收集更多的性能分析數據。因此,您將能夠應用更高級的優化技術和算法。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"分層編譯","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"爲什麼要進行分層編譯","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這是由於編譯器編譯本機代碼須要佔用程序運行時間,要編譯出優化程度更高的代碼鎖花費的時間可能更長,並且想要編譯出優化程度更高的代碼,解釋器可能還要替編譯器收集性能監控信息。這對解釋運行的速度也有影響。爲了在程序啓動響應速度和運行效率之間尋找平衡點。因此採用分層編譯的策略。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"分層編譯結合了客戶端和服務器端編譯。分層編譯利用了JVM中客戶端和服務器編譯器的優勢","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客戶端編譯器在應用程序啓動期間最活躍,並處理由較低的性能計數器閾值觸發的優化","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客戶端編譯器還會插入性能計數器,併爲更高級的優化準備指令集,服務器端編譯器將在稍後階段解決這些問題。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"分層編譯是一種非常節省資源的性能分析方法,因爲編譯器能夠在影響較小的編譯器活動期間收集數據,以後可以將其用於更高級的優化。與僅使用解釋的代碼配置文件計數器所獲得的信息相比,這種方法還可以產生更多的信息。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"分層策略例如以下所看到的:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第0層:程序解釋運行。解釋器不開啓性能監控功能,可觸發第1層編譯。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第1層:即C1編譯。將字節碼編譯爲本地代碼。進行簡單和可靠的優化,如有必要將增加性能監控的邏輯。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第2層:即C2編譯,將字節碼編譯爲本地代碼,同一時候啓用一些編譯耗時較長的優化,甚至會依據性能監控信息進行一些不可靠的激進優化。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"代碼優化","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"當選擇一種方法進行編譯時,JVM會將其字節碼提供給即時編譯器(JIT)。JIT必須先了解字節碼的語義和語法,然後才能正確編譯該方法。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"爲了幫助JIT編譯器分析該方法,首先將其字節碼重新格式化爲稱爲trees,它比字節碼更類似於機器代碼。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"然後對方法的樹進行分析和優化","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"最後,將樹轉換爲本地代碼。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JIT編譯器可以使用多個編譯線程來執行JIT編譯任務,使用多個線程可以潛在地幫助Java應用程序更快地啓動。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"編譯線程的默認數量由JVM標識,並且取決於系統配置。如果生成的線程數不是最佳的,則可以使用該XcompilationThreads選項覆蓋JVM決策。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"編譯包括以下階段:","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"內聯","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內聯是將較小方法的樹合併或“內聯”到其調用者的樹中的過程。這樣可以加速頻繁執行的方法調用。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"局部優化","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"局部優化可以一次分析和改進一小部分代碼。許多本地優化實現了經典靜態編譯器中使用的久經考驗的技術。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"控制流優化","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"控制流優化分析方法(或方法的特定部分)內部的控制流,並重新排列代碼路徑以提高其效率。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"全局優化","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"全局優化可一次對整個方法起作用。它們更加“昂貴”,需要大量的編譯時間,但可以大大提高性能。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"本機代碼生成","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"本機代碼生成過程因平臺架構而異。通常,在編譯的此階段,將方法的樹轉換爲機器代碼指令;根據架構特徵執行一些小的優化。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"編譯對象","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"編譯對象即爲會被編譯優化的熱點代碼。有下面兩類","attrs":{}},{"type":"text","text":":","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"被多次調用的方法","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"被多次運行的循環體","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"觸發條件","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這就牽扯到觸發條件這個概念,推斷一段代碼是否是熱點代碼。是否須要觸發即時編譯,這樣的行爲成爲熱點探測(Spot Dectection)。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"熱點探測有兩種手段:","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"基於採樣的熱點探測(Sample Based Hot Spot Dectection)","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"虛擬機會週期性的檢查各個線程的棧頂,假設發現某些方法常常性的出如今棧頂,那麼這種方法就是熱點方法。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"基於計數器的熱點探測(Counter Based Hot Spot Dectection)","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"虛擬機會爲每一個方法或代碼塊建立計數器,統計方法的運行次數。假設運行次數超過一定的閾值就覺得他是熱點方法。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HotSpot JVM使用另外一種方法基於計數器的熱點探測方法。它爲每一個方法準備了兩類計數器:","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"方法調用計數器","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這個閾值在Client模式下是1500次。在Server模式下是10000此,這個閾值能夠通過參數","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"-XX:CompileThreshold","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"來人爲設定。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"方法調用次數統計的並非方法被調用的絕對次數,而是相對的運行頻率,即一段時間內方法被調用的次數,當超過一定時間限度,假設方法的調用次數仍然不足以讓它提交給即時編譯器編譯,那這種方法的調用計數器會被降低一半,這個過程被稱爲方法調用計數器的熱度衰減(Counter Decay)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"而這段時間就稱爲此方法統計的半衰週期(Counter Half Life Time)。相同也能夠使用參數","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"-XX:-UseCounterDecay","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"來關閉熱度衰減。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"方法調用計數器觸發即時編譯的整個流程例如以下圖所看到的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c5/c558344e2735c66fd8122ee76acd81bf.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"回邊計數器","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"什麼是回邊?","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在字節碼遇到控制流向後跳轉的指令稱爲回邊(Back Edge)。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回邊計數器是用來統計一個方法中循環體代碼運行的次數,回邊計數器的閾值能夠通過參數","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"-XX:OnStackReplacePercentage","attrs":{}}],"attrs":{}},{"type":"text","text":"來調整。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虛虛擬機運行在Client模式下,回邊計數器閡值計算公式爲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"方法調用計數器閉值(CompileThreshold) xOSR比率(OnStackReplacePercentage) / 100\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當中OnSlackReplacePercentage默認值爲933,假設都取默認值,那Client模式虛擬機的回邊計數器的閡值爲13995。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虛擬機運行在Server模式下,回邊計數器閡值的itm公式爲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"方法調用計數器閡值(CompileThreshold) x (OSR比率(OnStackReplacePercentage) - 解釋器監控比率(InterpreterProffePercentage) / 100\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當中OnSlackReplacePercentage默認值爲140。 InterpreterProffePercentage默認值爲33.","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設都取默認值。BF Server模式虛擬機回邊計數器的閾值爲10700。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回邊計數器觸發即時編譯的流程例如以下圖所看到的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/00c4be88a86e7053bc225f131de745b4.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回邊計數器與方法調用計數器不同的是,回邊計數器沒有熱度衰減,因此這個計數器統計的就是循環運行的絕對次數。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"編譯流程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在默認設置下,不管是方法調用產生的即時編譯請求,還是OSR編譯請求,虛擬機在代碼編譯器還未完畢之前,都仍然依照解釋方式繼續進行,而編譯動作則在後臺的編譯線程中繼續進行。也能夠使用-XX:-BackgroundCompilation來禁止後臺編譯,則此時一旦遇到JIT編譯,運行線程向虛擬機提交請求後會一直等待,直到編譯完畢後再開始運行編譯器輸出的本地代碼。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼在後臺編譯過程中,編譯器做了什麼事呢?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Client Compiler編譯流程","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一階段:一個平臺獨立的前端將字節碼構造成一種高級中間碼錶示(High Level Infermediate Representaion),HIR使用靜態單分配的形式來表示代碼值,這能夠使得一些的構造過程之中和之後進行的優化動作更easy實現,在此之前編譯器會在字節碼上完畢一部分基礎優化,如方法內聯、常量傳播等。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二階段:一個平臺相關的後端從HIR中產生低級中間代碼表示(Low Level Intermediate Representation),而在此之前會在HIR上完畢還有一些優化。如空值檢查消除、範圍檢查消除等。以便讓HIR達到更高效的代碼表示形式。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三階段:在平臺相關的後端使用線性掃描算法(Linear Scan Register Allocation)在LIR上分配寄存器,並在LIR上做窺孔優化(Peephole)優化,然後產生機器碼。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/77/77007c4a67dbc061150c407432457807.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章