国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

Java虛擬機JVM性能優化(二):編譯器

2019-11-26 15:25:19
字體:
來源:轉載
供稿:網友

本文將是JVM 性能優化系列的第二篇文章(第一篇:傳送門),Java 編譯器將是本文討論的核心內容。

本文中,作者(Eva Andreasson)首先介紹了不同種類的編譯器,并對客戶端編譯,服務器端編譯器和多層編譯的運行性能進行了對比。然后,在文章的最后介紹了幾種常見的JVM優化方法,如死代碼消除,代碼嵌入以及循環體優化。

Java最引以為豪的特性“平臺獨立性”正是源于Java編譯器。軟件開發人員盡其所能寫出最好的java應用程序,緊接著后臺運行的編譯器產生高效的基于目標平臺的可執行代碼。不同的編譯器適用于不同的應用需求,因而也就產生不同的優化結果。因此,如果你能更好的理解編譯器的工作原理、了解更多種類的編譯器,那么你就能更好的優化你的Java程序。

本篇文章突出強調和解釋了各種Java虛擬機編譯器之間的不同。同時,我也會探討一些及時編譯器(JIT)常用的優化方案。

什么是編譯器?

簡單來說,編譯器就是以某種編程語言程序作為輸入,然后以另一種可執行語言程序作為輸出。Javac是最常見的一種編譯器。它存在于所有的JDK里面。Javac 以java代碼作為輸出,將其轉換成JVM可執行的代碼―字節碼。這些字節碼存儲在以.class結尾的文件中,并在java程序啟動時裝載到java運行時環境。

字節碼并不能直接被CPU讀取,它還需要被翻譯成當前平臺所能理解的機器指令語言。JVM中還有另一個編譯器負責將字節碼翻譯成目標平臺可執行的指令。一些JVM編譯器需要經過幾個等級的字節碼代碼階段。例如,一個編譯器在將字節碼翻譯成機器指令之前可能還需要經歷幾種不同形式的中間階段。

從平臺不可知論的角度出發,我們希望我們的代碼能夠盡可能的與平臺無關。

為了達到這個目的,我們在最后一個等級的翻譯―從最低的字節碼表示到真正的機器代碼―才真正將可執行代碼與一個特定平臺的體系結構綁定。從最高的等級來劃分,我們可以將編譯器分為靜態編譯器和動態編譯器。 我們可以根據我們的目標執行環境、我們渴望的優化結果、以及我們需要滿足的資源限制條件來選擇合適的編譯器。在上一篇文章中我們簡單的討論了一下靜態編譯器和動態編譯器,在接下來的部分我們將更加深入的解釋它們。

靜態編譯 VS 動態編譯

我們前面提到的javac就是一個靜態編譯的例子。對于靜態編譯器,輸入代碼被解釋一次,輸出即為程序將來被執行的形式。除非你更新源代碼并(通過編譯器)重新編譯,否則程序的執行結果將永遠不會改變:這是因為輸入是一個靜態的輸入并且編譯器是一個靜態的編譯器。

通過靜態編譯,下面的程序:

復制代碼 代碼如下:

staticint add7(int x ){      return x+7;}

將會轉換成類似下面的字節碼:

復制代碼 代碼如下:

iload0 bipush 7 iadd ireturn

動態編譯器動態的將一種語言編譯成另外一種語言,所謂動態的是指在程序運行的時候進行編譯―邊運行邊編譯!動態編譯和優化的好處就是可以處理應用程序加載時的一些變化。Java 運行時常常運行在不可預知甚至變化的環境上,因此動態編譯非常適用于Java 運行時。大部分的JVM 使用動態編譯器,如JIT編譯器。值得注意的是,動態編譯和代碼優化需要使用一些額外的數據結構、線程以及CPU資源。越高級的優化器或字節碼上下文分析器,消耗越多的資源。但是這些花銷相對于顯著的性能提升來說是微不足道的。

JVM種類以及Java的平臺獨立性

所有JVM的實現都有一個共同的特點就是將字節碼編譯成機器指令。一些JVM在加載應用程序時對代碼進行解釋,并通過性能計數器來找出“熱”代碼;另一些JVM則通過編譯來實現。編譯的主要問題是集中需要大量的資源,但是它也能帶來更好的性能優化。

如果你是一個java新手,JVM的錯綜復雜肯定會搞得你暈頭轉向。但好消息是你并不需要將它搞得特別清楚!JVM將管理代碼的編譯和優化,你并不需要為機器指令以及采取什么樣的方式寫代碼才能最佳的匹配程序運行平臺的體系結構而操心。

從java字節碼到可執行

 一旦將你的java代碼編譯成字節碼,接下來的一步就是將字節碼指令翻譯成機器代碼。這一步可以通過解釋器來實現,也可以通過編譯器來實現。

解釋

解釋是編譯字節碼最簡單的方式。解釋器以查表的形式找到每條字節碼指令對應的硬件指令,然后將它發送給CPU執行。

你可以將解釋器想象成查字典:每一個特定的單詞(字節碼指令),都有一個具體的翻譯(機器代碼指令)與之對應。因為解釋器每讀一條指令就會馬上執行該指令,所以該方式無法對一組指令集進行優化。同時每調用一個字節碼都要馬上對其進行解釋,因此解釋器運行速度是相當慢得。解釋器以一種非常準確的方式來執行代碼,但是由于沒有對輸出的指令集進行優化,因此它對目標平臺的處理器來說可能不是最優的結果。

編譯

編譯器則是將所有將要執行的代碼全部裝載到運行時。這樣當它翻譯字節碼時,就可以參考全部或部分的運行時上下文。它做出的決定都是基于對代碼圖分析的結果。如比較不同的執行分支以及參考運行時上下文數據。

在將字節碼序列被翻譯成機器代碼指令集后,就可以基于這個機器代碼指令集進行優化。優化過的指令集存儲在一個叫代碼緩沖區的結構中。當再次執行這些字節碼時,就可以直接從這個代碼緩沖區中取得優化過的代碼并執行。在有些情況下編譯器并不使用優化器來進行代碼優化,而是使用一種新的優化序列―“性能計數”。

使用代碼緩存器的優點是結果集指令可以被立即執行而不再需要重新解釋或編譯!

這可以大大的降低執行時間,尤其是對一個方法被多次調用的java應用程序。

優化

通過動態編譯的引入,我們就有機會來插入性能計數器。例如,編譯器插入性能計數器,每次字節碼塊(對應某個具體的方法)被調用時對應的計數器就加一。編譯器通過這些計數器找到“熱塊”,從而就能確定哪些代碼塊的優化能對應用程序帶來最大的性能提升。運行時性能分析數據能夠幫助編譯器在聯機狀態下得到更多的優化決策,從而更進一步提升代碼執行效率。因為得到越多越精確的代碼性能分析數據,我們就可以找到更多的可優化點從而做出更好的優化決定,例如:怎樣更好的序列話指令、是否用更有效率的指令集來替代原有指令集,以及是否消除冗余的操作等。

例如

考慮下面的java代碼

復制代碼 代碼如下:

staticint add7(int x ){      return x+7;}

Javac 將靜態的將它翻譯成如下字節碼:
復制代碼 代碼如下:

iload0

bipush 7

iadd

ireturn


當該方法被調用時,該字節碼將被動態的編譯成機器指令。當性能計數器(如果存在)達到指定的閥值時,該方法就可能被優化。優化后的結果可能類似下面的機器指令集:
復制代碼 代碼如下:

lea rax,[rdx+7]  ret

不同的編譯器適用于不同的應用

不同的應用程序擁有不同的需求。企業服務器端應用通常需要長時間運行,所以通常希望對其進行更多的性能優化;而客戶端小程序可能希望更快的響應時間和更少的資源消耗。下面讓我們一起討論三種不同的編譯器以及他們的優缺點。

客戶端編譯器(Client-side compilers)

C1是一種大家熟知的優化編譯器。當啟動JVM時,添加-client參數即可啟動該編譯器。通過它的名字我們即可發現C1是一種客戶端編譯器。它非常適用于那種系統可用資源很少或要求能快速啟動的客戶端應用程序。C1通過使用性能計數器來進行代碼優化。這是一種方式簡單,且對源代碼干預較少的優化方式。

服務器端編譯器(Server-side compilers)

對于那種長時間運行的應用程序(例如服務器端企業級應用程序),使用客戶端編譯器可能遠遠不能夠滿足需求。這時我們應該選擇類似C2這樣的服務器端編譯器。通過在JVM啟動行中加入

主站蜘蛛池模板: 元谋县| 四子王旗| 清水河县| 马龙县| 黔南| 洛阳市| 遂昌县| 福贡县| 海晏县| 沭阳县| 资溪县| 湘潭县| 登封市| 甘泉县| 淳化县| 新民市| 濉溪县| 武安市| 墨玉县| 云阳县| 宝应县| 红河县| 怀远县| 竹北市| 雅江县| 原阳县| 重庆市| 陈巴尔虎旗| 四子王旗| 阆中市| 旬邑县| 皮山县| 信宜市| 郑州市| 阿拉善盟| 龙里县| 泰顺县| 丹江口市| 贡嘎县| 太康县| 社旗县|