java性能权威指南中文_Java性能权威指南读书笔记--之一
JIT(即時編譯)
解釋型代碼:程序可移植,相同的代碼在任何有適當解釋器的機器上,都能運行,但是速度慢。
編譯型代碼:速度快,電視不同CPU平臺的代碼無法兼容。
java則是使用java的編譯器先將其編譯為class文件,也就是字節(jié)碼;然后將字節(jié)碼交由jvm(java虛擬機)解釋執(zhí)行。由于這個編譯是在程序執(zhí)行時進行的,因此被稱為“即使編譯”。
熱點編譯
對于程序來說,通常只有一部分代碼被經常執(zhí)行,而應用的性能就取決于這些代碼執(zhí)行得有多快。這些關鍵代碼段被稱為應用的熱點,代碼執(zhí)行得越多就被認為是越熱。
因此JVM執(zhí)行代碼時,并不會立即編譯代碼:
如果代碼只執(zhí)行一次,那編譯完全就是浪費精力。對于只執(zhí)行一次的代碼,解釋執(zhí)行Java字節(jié)碼比先編譯然后執(zhí)行的速度快。
JVM執(zhí)行特定方法或者循環(huán)的次數越多,它就會越了解這段代碼。這使得JVM可以在編譯代碼時進行大量優(yōu)化。
分層編譯
Client編譯器和server編譯器主要的區(qū)別在于編譯代碼的時機不同。client編譯器開啟編譯比server編譯器要早。這意味著在代碼執(zhí)行的開始階段,client編譯器比server編譯器要快,因為它的編譯代碼相比server編譯器而言要多。
分層編譯是綜合了client和server的優(yōu)點。在開啟分層編譯(-XX:+TieredCompilation)后代碼先由client編譯器編譯,隨著代碼變熱,由server編譯器重新編譯。
調優(yōu)代碼緩存
JVM編譯代碼時,會在代碼緩存中保留編譯之后的匯編語言指令集。代碼緩存的大小固定,所以一旦填滿,JVM就不能編譯更多代碼了。
也就是說,如果代碼緩存過小,那么就會有一些熱點代碼被編譯了,而其他沒有,最終導致應用的大部分代碼都是解釋運行(非常慢)。這個問題在使用client編譯器或進行分層編譯時很常見。
當代碼緩存填滿時,JVM通常會發(fā)出以下警告:
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圖表,可以監(jiān)控代碼緩存。
編譯閾值
一旦代碼執(zhí)行到一定次數,且達到了編譯閾值,編譯器就可以獲得足夠的信息編譯代碼了。
編譯是基于兩種JVM計數器的:方法調用計數器和方法中的循環(huán)回邊計數器?;剡厡嶋H上可以看作是循環(huán)完成執(zhí)行的次數。
棧上替換:JVM可以在方法循環(huán)運行時進行編譯,并在循環(huán)代碼編譯結束之后,JVM替換還在棧上的代碼,循環(huán)的下一次迭代就會執(zhí)行快的多的代碼。
標準編譯由-XX:CompileThreshold=N標志觸發(fā)。使用client編譯器時,N的默認值是1500,使用server編譯器時為10000。
計數器會隨著時間而減少,所以計數器只是方法或循環(huán)最新熱度的度量。由此帶來一個副作用是,執(zhí)行不太頻繁的代碼可能永遠不會編譯。
檢測編譯過程
-XX:+PrintCompilation
如果開啟PrintCompilation,每次編譯一個方法(或循環(huán))時,JVM就會打印一行被編譯的內容信息。
絕大多數編譯日志的行具有以下格式:
timestamp compilation_id attributes (tiered_level) method_name size deopt
timestamp表示編譯完成的時間
compilation_id內部的任務ID
attributes是一組5個字符長的串,表示代碼編譯的狀態(tài)。如果給定的編譯被賦予了特定屬性,就會打印下面列表中所顯示的字符,否則該屬性就打印一個空格。
* % :編譯為OSR
* s :方法是同步的
* !:方法有異常處理器
* b :阻塞模式時發(fā)生的編譯
* n:為封裝本地方法所發(fā)生的編譯
tiered_level 如果程序沒有使用分成編譯的方式運行則為空,否則為數字,表明所完成編譯的級別
method_name格式為:ClassName::method
然后是編譯后代碼大小(單位是字節(jié))
最后,在某些情況下,編譯日志的結尾會有一條信息,表明發(fā)生了某種逆優(yōu)化,通常是“made not entrant”或”made zombie”
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秒輸出一次最近被編譯的方法
編譯器線程
當方法(或循環(huán))適合編譯時,就會進入到編譯隊列。隊列則由一個或多個后臺線程處理。編譯隊列是一種優(yōu)先隊列,即調用計數次數多的方法有更高的優(yōu)先級。
當開啟分層編譯時,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編譯器隊列。
使用分層編譯時,線程數很容易超過系統(tǒng)限制,特別是有多個JVM同時運行的時候。在這種情況下,減少線程數有助于提高整體的吞吐量(盡管代價可能是熱身期會持續(xù)得更長)。
方法內聯(lián)
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);
編譯后的代碼本質上執(zhí)行的是:
Point p = getPoint();
p.x = p.x *2;
方法是否內聯(lián)取決于它有多熱以及它的大小。
-XX:MaxInlineSize=N默認是35字節(jié),即只有方法小于35字節(jié)時第一次調用方法時就會被內聯(lián)。
-XX:MaxFreqInlineSize=N默認是325字節(jié),即只有當一個方法頻繁被調用并且小于325字節(jié)時會被內聯(lián)。
逃逸分析
-XX:+DoEscapeAnalysis默認為true。逃逸分析可以讓JVM對一個對象根據代碼來進行優(yōu)化。
棧上分配
我們都知道Java中的對象都是在堆上分配的,而垃圾回收機制會回收堆中不再使用的對象,但是篩選可回收對象,回收對象還有整理內存都需要消耗時間。如果能夠通過逃逸分析確定某些對象不會逃出方法之外,那就可以讓這個對象在棧上分配內存,這樣該對象所占用的內存空間就可以隨棧幀出棧而銷毀,就減輕了垃圾回收的壓力。
同步消除
如果發(fā)現某個對象只能從一個線程可訪問,那么在這個對象上的操作可以不需要同步。
標量替換
Java虛擬機中的原始數據類型(int,long等數值類型以及reference類型等)都不能再進一步分解,它們就可以稱為標量。相對的,如果一個數據可以繼續(xù)分解,那它稱為聚合量,Java中最典型的聚合量是對象。如果逃逸分析證明一個對象不會被外部訪問,并且這個對象是可分解的,那程序真正執(zhí)行的時候將可能不創(chuàng)建這個對象,而改為直接創(chuàng)建它的若干個被這個方法使用到的成員變量來代替。拆散后的變量便可以被單獨分析與優(yōu)化,可以各自分別在棧幀或寄存器上分配空間,原本的對象就無需整體分配空間了。
小結
不用擔心小方法,特別是getter和setter,因為它們容易內聯(lián)。
需要編譯的代碼在編譯隊列中,隊列中代碼越多,程序打到最佳性能的時間越久。
雖然代碼緩存的大小可以調整,但它仍然是有限的資源
代碼越簡單,優(yōu)化越多。
總結
以上是生活随笔為你收集整理的java性能权威指南中文_Java性能权威指南读书笔记--之一的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java spring 过滤器_spri
- 下一篇: 浦发信用卡信用评分等级