1. 程式人生 > >小師妹學JVM之:JIT中的PrintCompilation

小師妹學JVM之:JIT中的PrintCompilation

[toc] # 簡介 上篇文章我們講到了JIT中的LogCompilation,將編譯的日誌都收集起來,存到日誌檔案裡面,並且詳細的解釋了LogCompilation日誌檔案中的內容定義。今天我們再和小師妹一起學習LogCompilation的姊妹篇PrintCompilation,看看都有什麼妙用吧。 # PrintCompilation 小師妹:F師兄,上次你給講的LogCompilation實在是太複雜了,生成的日誌檔案又多,完全看不了,我其實只是想知道有哪些方法被編譯成了機器碼,有沒有什麼更加簡單的辦法呢? > 真理的大海,讓未發現的一切事物躺臥在我的眼前,任我去探尋- 牛頓(英國) 當然有的,那就給你介紹一下LogCompilation的妹妹PrintCompilation,為什麼是妹妹呢?因為PrintCompilation輸出的日誌要比LogCompilation少太多了。 老規矩,上上我們的JMH執行程式碼,文章中使用的程式碼連結都會在文末註明,這裡使用圖片的原因只是為了方便讀者閱讀程式碼: ![](https://img-blog.csdnimg.cn/20200530152758239.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 這裡和上次的LogCompilation不同的是,我們使用:-XX:+PrintCompilation引數。 其實我們還可以新增更多的引數,例如: ~~~java -Xbatch -XX:-TieredCompilation -XX:+PrintCompilation ~~~ 先講一下-Xbatch。 一般來說JIT編譯器使用的是和主執行緒完全不同的新的執行緒。這樣做的好處就是JIT可以和主執行緒並行執行,編譯器的執行基本上不會影響到主執行緒的的執行。 但是有陰就有陽,有利就有弊。多執行緒在提高的處理速度的同時,帶給我們的就是輸出日誌的混亂。因為是並行執行的,我們主執行緒的日誌中,穿插了JIT編譯器的執行緒日誌。 如果使用-Xbatch就可以強迫JIT編譯器使用主執行緒。這樣我們的輸出日誌就是井然有序的。真棒。 再講一下TieredCompilation。 為了更好的提升編譯效率,JVM在JDK7中引入了分層編譯Tiered compilation的概念。 大概來說分層編譯可以分為三層: 第一層就是禁用C1和C2編譯器,這個時候沒有JIT進行。 第二層就是隻開啟C1編譯器,因為C1編譯器只會進行一些簡單的JIT優化,所以這個可以應對常規情況。 第三層就是同時開啟C1和C2編譯器。 在JDK8中,分層編譯是預設開啟的。因為不同的編譯級別處理編譯的時間是不一樣的,後面層級的編譯器啟動的要比前面層級的編譯器要慢,但是優化的程度更高。 這樣我們其實會產生很多中間的優化程式碼,這裡我們只是想分析最終的優化程式碼,所以我們需要停止分層編譯的功能。 最後是今天的主角:PrintCompilation。 PrintCompilation將會輸出被編譯方法的統計資訊,因此使用PrintCompilation可以很方便的看出哪些是熱點程式碼。熱點程式碼也就意味著存在著被優化的可能性。 # 分析PrintCompilation的結果 小師妹:F師兄,我照著你的例子運行了一下,結果果然清爽了很多。可是我還是看不懂。 沒有一個人能全面把握真理。小師妹,我們始終在未知的路上前行。不懂就問,不會就學。 我們再截個圖看一下生成的日誌吧。 ![](https://img-blog.csdnimg.cn/20200530155321272.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) ![](https://img-blog.csdnimg.cn/20200530155410945.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 因為日誌太長了,為了節約大家的流量,我只截取了開頭的部分和結尾的部分。 大家可以看到開頭部分基本上都是java自帶的類的優化。只有最後才是我們自己寫的類。 第一列是方法開始編譯的時間。 第二列是簡單的index。 第三列是一系列的flag的組合,有下面幾個flag: ~~~java b Blocking compiler (always set for client) * Generating a native wrapper % On stack replacement (where the compiled code is running) ! Method has exception handlers s Method declared as synchronized n Method declared as native made non entrant compilation was wrong/incomplete, no future callers will use this version made zombie code is not in use and ready for GC ~~~ 如果我們沒有關閉分層編譯的話,在方法名前面還會有數字,表示是使用的那個編譯器。 ![](https://img-blog.csdnimg.cn/20200530162454453.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 分層編譯詳細的來說可以分為5個級別。 0表示是使用直譯器,不使用JIT編譯。 1,2,3是使用C1編譯器(client)。 4是使用C2編譯器(server)。 現在讓我們來看一下最後一列。 最後一列包含了方法名和方法的長度。注意這裡的長度指的是位元組碼的長度。 如果位元組碼被編譯成為機器碼,長度會增加很多倍。 # 總結 本文介紹了JIT中PrintCompilation的使用,並再次複習了JIT中的分層編譯架構。希望大家能夠喜歡。 本文的例子[https://github.com/ddean2009/learn-java-base-9-to-20](https://github.com/ddean2009/learn-java-base-9-to-20) > 本文作者:flydean程式那些事 > > 本文連結:[http://www.flydean.com/jvm-jit-printcompilation/](http://www.flydean.com/jvm-jit-printcompilation/) > > 本文來源:flydean的部落格 > > 歡迎關注我的公眾號:程式那些事,更多精彩等著您!