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