解釋型代碼:程序可移植,相同的代碼在任何有適當解釋器的機器上,都能運行,可是速度慢。
編譯型代碼:速度快,電視不一樣CPU平臺的代碼沒法兼容。
java則是使用java的編譯器先將其編譯爲class文件,也就是字節碼;而後將字節碼交由jvm(java虛擬機)解釋執行。因爲這個編譯是在程序執行時進行的,所以被稱爲「即便編譯」。java
對於程序來講,一般只有一部分代碼被常常執行,而應用的性能就取決於這些代碼執行得有多快。這些關鍵代碼段被稱爲應用的熱點,代碼執行得越多就被認爲是越熱。
所以JVM執行代碼時,並不會當即編譯代碼:緩存
Client編譯器和server編譯器主要的區別在於編譯代碼的時機不一樣。client編譯器開啓編譯比server編譯器要早。這意味着在代碼執行的開始階段,client編譯器比server編譯器要快,由於它的編譯代碼相比server編譯器而言要多。
分層編譯是綜合了client和server的優勢。在開啓分層編譯(-XX:+TieredCompilation)後代碼先由client編譯器編譯,隨着代碼變熱,由server編譯器從新編譯。jvm
JVM編譯代碼時,會在代碼緩存中保留編譯以後的彙編語言指令集。代碼緩存的大小固定,因此一旦填滿,JVM就不能編譯更多代碼了。
也就是說,若是代碼緩存太小,那麼就會有一些熱點代碼被編譯了,而其餘沒有,最終致使應用的大部分代碼都是解釋運行(很是慢)。這個問題在使用client編譯器或進行分層編譯時很常見。
當代碼緩存填滿時,JVM一般會發出如下警告:性能
Java HotSopt(TM) 64-Bit Server VM warning:CodeCache is full.Compiler has bean disabled. Java HotSopt(TM) 64-Bit Server VM warning:Try increasing the code cache size using -XX:ReservedCodeCacheSize=
各平臺代碼緩存的默認大小:優化
jvm | jdk版本 | 大小 |
---|---|---|
32位client | Java8 | 32MB |
32位client | 分層編譯,Java8 | 240MB |
64位client | 分層編譯,Java8 | 240MB |
32位client | Java7 | 32MB |
32位server | Java7 | 32MB |
64位server | Java7 | 48MB |
64位server | 分層編譯,Java7 | 48MB |
若是代碼緩存設爲1GB,JVM就會保留1GB的本地內存空間。若是是32位JVM,那麼進程佔用的總內存不能超過4GB(包括Java堆、JVM自身全部代碼佔用空間、分配給應用的本地內存、代碼緩存)。
經過jconsole Memory(內存)面板的Memory Pool Code Cache圖表,能夠監控代碼緩存。線程
一旦代碼執行到必定次數,且達到了編譯閾值,編譯器就能夠得到足夠的信息編譯代碼了。
編譯是基於兩種JVM計數器的:方法調用計數器和方法中的循環回邊計數器。回邊實際上能夠看做是循環完成執行的次數。
棧上替換:JVM能夠在方法循環運行時進行編譯,並在循環代碼編譯結束以後,JVM替換還在棧上的代碼,循環的下一次迭代就會執行快的多的代碼。
標準編譯由-XX:CompileThreshold=N標誌觸發。使用client編譯器時,N的默認值是1500,使用server編譯器時爲10000。
計數器會隨着時間而減小,因此計數器只是方法或循環最新熱度的度量。由此帶來一個反作用是,執行不太頻繁的代碼可能永遠不會編譯。日誌
-XX:+PrintCompilation
若是開啓PrintCompilation,每次編譯一個方法(或循環)時,JVM就會打印一行被編譯的內容信息。
絕大多數編譯日誌的行具備如下格式:
timestamp compilation_id attributes (tiered_level) method_name size deopt
timestamp表示編譯完成的時間
compilation_id內部的任務ID
attributes是一組5個字符長的串,表示代碼編譯的狀態。若是給定的編譯被賦予了特定屬性,就會打印下面列表中所顯示的字符,不然該屬性就打印一個空格。
* % :編譯爲OSR
* s :方法是同步的
* !:方法有異常處理器
* b :阻塞模式時發生的編譯
* n:爲封裝本地方法所發生的編譯
tiered_level 若是程序沒有使用分紅編譯的方式運行則爲空,不然爲數字,代表所完成編譯的級別
method_name格式爲:ClassName::method
而後是編譯後代碼大小(單位是字節)
最後,在某些狀況下,編譯日誌的結尾會有一條信息,代表發生了某種逆優化,一般是「made not entrant」或」made zombie」code
135 1 n 0 java.lang.Thread::currentThread (native) (static) 136 2 3 java.util.Arrays::copyOf (19 bytes) 136 7 3 sun.nio.cs.UTF_8$Encoder::encode (359 bytes) 137 8 2 java.lang.String::hashCode (55 bytes)
使用jstat -compiler 進程ID 也能夠看有多少方法被編譯
使用jstat -printcompilation 5003 1000 表示進程ID爲5003的程序每1秒輸出一次最近被編譯的方法server
當方法(或循環)適合編譯時,就會進入到編譯隊列。隊列則由一個或多個後臺線程處理。編譯隊列是一種優先隊列,即調用計數次數多的方法有更高的優先級。
當開啓分層編譯時,JVM默認開啓多個client和server線程。對象
cpu數量 | C1的線程數(client) | C2的線程數(server) |
---|---|---|
1 | 1 | 1 |
2 | 1 | 1 |
4 | 1 | 2 |
8 | 1 | 2 |
16 | 2 | 6 |
32 | 3 | 7 |
64 | 4 | 8 |
128 | 4 | 10 |
編譯器的線程數可經過-XX:CICompilerCount=N
標誌來設置。對於分層編譯來講,設置的值中三分之一將用來處理client編譯器隊列,其他的線程(至少一個)用來處理server編譯器隊列。
使用分層編譯時,線程數很容易超過系統限制,特別是有多個JVM同時運行的時候。在這種狀況下,減小線程數有助於提升總體的吞吐量(儘管代價多是熱身期會持續得更長)。
public class Point{ private int x,y; public int getX(){ return x; } public void setX(int i){ x = i;} }
若是你寫下面的代碼
Point p = getPoint(); p.setX(p.getX()*2);
編譯後的代碼本質上執行的是:
Point p = getPoint(); p.x = p.x *2;
方法是否內聯取決於它有多熱以及它的大小。
-XX:MaxInlineSize=N
默認是35字節,即只有方法小於35字節時第一次調用方法時就會被內聯。
-XX:MaxFreqInlineSize=N
默認是325字節,即只有當一個方法頻繁被調用而且小於325字節時會被內聯。
-XX:+DoEscapeAnalysis
默認爲true。逃逸分析可讓JVM對一個對象根據代碼來進行優化。