投注網(wǎng)站建設(shè)需要優(yōu)書網(wǎng)
1 鎖優(yōu)化
JVM 在加鎖的過程中,會采用自旋、自適應(yīng)、鎖消除、鎖粗化等優(yōu)化手段來提升代碼執(zhí)行效率。
1.1 自旋鎖和自適應(yīng)自旋
現(xiàn)在大多的處理器都是多核處理器 ,如果在多核心處理器,有讓兩個或者以上的線程并行執(zhí)行,我們可以讓一個等待線程不放棄處理器的執(zhí)行時間。設(shè)置一個等待超時時間,看線程是否能夠很快的釋放鎖,在等等待的這段時間可以執(zhí)行一個空循環(huán),讓當(dāng)前線程繼續(xù)占用 CPU 的時間片。這就是所謂的「自旋鎖」。
JVM 中可以通過 +XX:UseSpinning來開啟自旋鎖,在 JDK1.6 過后默認(rèn)為我們開啟。由于自旋鎖的使用會讓鎖的競爭者占用更多的處理器時間, JVM 規(guī)定了一個自旋次數(shù)的一個參數(shù)。我們可以通過 -XX:PreBlockSping來進(jìn)行更改(默認(rèn)10次)。
偏向鎖、輕量級鎖的狀態(tài)轉(zhuǎn)化及對象 Mark Word 的關(guān)系轉(zhuǎn)換入下圖所示:

偏向鎖、輕量級鎖的狀態(tài)轉(zhuǎn)化及對象 Mark Word 的關(guān)系
1.2 鎖消除
鎖消除是指虛擬機即時編譯器在運行時檢測到某段需要同步的代碼不可能存在共享數(shù)據(jù)競爭而實施的一種對鎖進(jìn)行消除的優(yōu)化策略。鎖消除的主要判斷依據(jù)于逃逸分析。如果判斷一段代碼,在堆上所有的數(shù)據(jù)都不會逃逸出去被別的線程訪問到,那就把它當(dāng)作棧上的數(shù)據(jù)對待,認(rèn)為它們是私有的,同步加鎖就無需進(jìn)行。
下面是三個字符串 x, y, z 相加的例子,無論是從源代碼上還是邏輯上都沒有進(jìn)行同步:
public String concatStr(String x, String y, String z) {return x + y + z;
}
String 是一個不可變的類,對字符的鏈接總是生成新的 String 對象來進(jìn)行的,因此 Javac 編譯器會對 String 鏈接進(jìn)行自動優(yōu)化,在 JDK5 之前字符串鏈接會轉(zhuǎn)換為 StringBuffer;在 JDK5 之后會轉(zhuǎn)換為 StringBuilder 對象連續(xù)的 append()操作,我們看看 javac 過后,反編譯的結(jié)果:
public String concatStr(String x, String y, String z) {StringBuilder sb = new StringBuilder();sb.append(x);sb.append(y);sb.append(z);return sb.toString();
}
我們再來看看 javap 反編譯的結(jié)果:

javap 反編譯的結(jié)果
這里大家可能會擔(dān)心 StringBuilder 不是線程安全的的操作會存在線程安全的問題嗎?這里的答案是不會,x + y + z 操作的優(yōu)化「經(jīng)過逃逸分析」過后,他的動態(tài)作用域被限制在了 concatStr方法內(nèi),就是說當(dāng)前實際執(zhí)行的 StringBuilder 的操作在 concatStr 方法內(nèi)部,「其他的外部線程無法訪問」到,所以這里「雖然有鎖,但是可以被安全的消除掉。所以當(dāng)我們進(jìn)行編譯過后,這段代碼就會忽略掉所有的同步措施直接執(zhí)行?!?/p>
1.3 鎖粗化
原則上,我們在寫代碼的時候,總是推薦將同步塊的作用范圍限制得盡可能的小--只在共享數(shù)據(jù)的實際操作作用域中才進(jìn)行同步,這樣也是為了使得需要同步的操作盡可能的變少,即使存在鎖的競爭,等待的鎖的線程也能很快的獲取到鎖。大多數(shù)情況下,上面的原則都是正確的,但是如果「一系列的連續(xù)操作都是對同一個對象反復(fù)加鎖和解鎖,甚至加鎖操作時出現(xiàn)在循環(huán)體之中」的,那即使沒有線程的競爭,頻繁的進(jìn)行相互操作也會導(dǎo)致不必需要的性能損耗。
StringBuffer buffer = new StringBuffer();
/** 鎖粗化 */
public void append(){buffer.append("aaa").append(" bbb").append(" ccc");
}
上面的代碼每次調(diào)用 buffer.append 方法都需要加鎖和解鎖,如果 JVM 家冊到有一串連續(xù)的對同一個對象加鎖和解鎖的操作,就會將其合并成一次范圍更大的加鎖解鎖操作,即在第一個 append 方法執(zhí)行的時候進(jìn)行加鎖,最后一個 append 方法結(jié)束后進(jìn)行解鎖。
2 逃逸分析
逃逸分析(Escape Analysis),是一種可能減少有效 Java 程序中同步負(fù)載和內(nèi)存堆分配壓力的跨全局函數(shù)數(shù)據(jù)流分析算法。通過逃逸分析, Java Hotspot 編譯器能夠分析出一個新的對象引用范圍從而決定是否要將這個對象分配到堆上,「逃逸分析的基本行為就是分析對象的動態(tài)作用域?!?/p>
2.1 方法逃逸
當(dāng)一個對象在方法里面被定義后,它可能被外部方法所引用,例如調(diào)用參數(shù)傳遞到其他方法中,這種稱為方法逃逸。
2.2 線程逃逸
當(dāng)一個對象可能被外部線程訪問到,比如:賦值給其他線程中訪問的實例變量,這種稱為線程逃逸。
2.3 通過逃逸分析,編譯器對代碼的優(yōu)化
如果能夠證明一個對象不會逃逸到到方法外或線程外(其他線程方法或者線程無法通過任何方法訪問該變量),或者逃逸程度比較低(只逃逸出方法而不逃逸出線程)則可以對這個對象采用不同程度的優(yōu)化:
棧上分配(Stack Allocations)完全不會逃逸的局部變量和不會逃逸出線程的對象,采用棧上分配,對象就會跟隨方法的結(jié)束自動銷毀。以減少垃圾回收器的壓力。
標(biāo)量替換(Scalar Replacement)有個對象可能不需要作為一個連續(xù)的存儲結(jié)果存儲也能被訪問到,那么對象的部分(或者全部)可以不存儲在內(nèi)存,而是存儲在 CPU 寄存器中。
同步消除(Synchronization Elimination)如果一個對象發(fā)現(xiàn)只能在一個線程訪問到,那么這個對象的操作可以考慮不同步。
整理好的Java面試資料,推薦(下載):
最全的java面試題庫
Java核心知識點整理