咱們都知道,Java 建立的對象都是被分配到堆內存上,可是事實並非這麼絕對,經過對Java對象分配的過程分析,能夠知道有兩個地方會致使Java中建立出來的對象並必定分別在所認爲的堆上。這兩個點分別是Java中的逃逸分析和TLAB(Thread Local Allocation Buffer)線程私有的緩存區。html
逃逸分析,是一種能夠有效減小Java程序中同步負載和內存堆分配壓力的跨函數全局數據流分析算法。經過逃逸分析,Java Hotspot編譯器可以分析出一個新的對象的引用的使用範圍從而決定是否要將這個對象分配到堆上。java
在計算機語言編譯器優化原理中,逃逸分析是指分析指針動態範圍的方法,它同編譯器優化原理的指針分析和外形分析相關聯。當變量(或者對象)在方法中分配後,其指針有可能被返回或者被全局引用,這樣就會被其餘過程或者線程所引用,這種現象稱做指針(或者引用)的逃逸(Escape)。通俗點講,若是一個對象的指針被多個方法或者線程引用時,那麼咱們就稱這個對象的指針發生了逃逸。算法
Java在Java SE 6u23以及之後的版本中支持並默認開啓了逃逸分析的選項。Java的 HotSpot JIT編譯器,可以在方法重載或者動態加載代碼的時候對代碼進行逃逸分析,同時Java對象在堆上分配和內置線程的特色使得逃逸分析成Java的重要功能。緩存
代碼示例性能優化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package
me.stormma.gc;
/**
* <p>Created on 2017/4/21.</p>
*
* @author stormma
*
* @title <p>逃逸分析</p>
*/
public
class
EscapeAnalysis {
public
static
B b;
/**
* <p>全局變量賦值發生指針逃逸</p>
*/
public
void
globalVariablePointerEscape() {
b =
new
B();
}
/**
* <p>方法返回引用,發生指針逃逸</p>
* @return
*/
public
B methodPointerEscape() {
return
new
B();
}
/**
* <p>實例引用發生指針逃逸</p>
*/
public
void
instancePassPointerEscape() {
methodPointerEscape().printClassName(
this
);
}
class
B {
public
void
printClassName(EscapeAnalysis clazz) {
System.out.println(clazz.getClass().getName());
}
}
}
|
逃逸分析研究對於 java 編譯器有什麼好處呢?咱們知道 java 對象老是在堆中被分配的,所以 java 對象的建立和回收對系統的開銷是很大的。java 語言被批評的一個地方,也是認爲 java 性能慢的一個緣由就是 java不支持棧上分配對象。JDK6裏的 Swing內存和性能消耗的瓶頸就是因爲 GC 來遍歷引用樹並回收內存的,若是對象的數目比較多,將給 GC 帶來較大的壓力,也間接得影響了性能。減小臨時對象在堆內分配的數量,無疑是最有效的優化方法。java 中應用裏廣泛存在一種場景,通常是在方法體內,聲明瞭一個局部變量,而且該變量在方法執行生命週期內未發生逃逸,按照 JVM內存分配機制,首先會在堆內存上建立類的實例(對象),而後將此對象的引用壓入調用棧,繼續執行,這是 JVM優化前的方式。固然,咱們能夠採用逃逸分析對 JVM 進行優化。即針對棧的從新分配方式,首先咱們須要分析而且找到未逃逸的變量,將該變量類的實例化內存直接在棧裏分配,無需進入堆,分配完成以後,繼續調用棧內執行,最後線程執行結束,棧空間被回收,局部變量對象也被回收,經過這種方式的優化,與優化前的方案主要區別在於對象的存儲介質,優化前是在堆中,而優化後的是在棧中,從而減小了堆中臨時對象的分配(較耗時),從而優化性能。app
使用逃逸分析進行性能優化(-XX:+DoEscapeAnalysis開啓逃逸分析)函數
1
2
3
4
5
6
|
public
void
method() {
Test test =
new
Test();
//處理邏輯
......
test =
null
;
}
|
這段代碼,之因此能夠在棧上進行內存分配,是由於沒有發生指針逃逸,便是引用沒有暴露出這個方法體。性能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package
me.stormma.gc;
/**
* <p>Created on 2017/4/21.</p>
*
* @author stormma
* @description: <p>內存分配比較</p>
*/
public
class
EscapeAnalysisTest {
public
static
void
alloc() {
byte
[] b =
new
byte
[
2
];
b[
0
] =
1
;
}
public
static
void
main(String[] args) {
long
b = System.currentTimeMillis();
for
(
int
i =
0
; i <
100000000
; i++) {
alloc();
}
long
e = System.currentTimeMillis();
System.out.println(e - b);
}
}
|
JVM 參數爲-server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC, 運行結果測試
JVM 參數爲-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC, 運行結果優化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package
me.stormma.gc;
/**
* <p>Created on 2017/4/21.</p>
*
* @author stormma
*
* @description: <p>利用逃逸分析進行性能優化</p>
*/
public
class
EscapeAnalysisTest {
private
static
class
Foo {
private
int
x;
private
static
int
counter;
public
Foo() {
x = (++counter);
}
}
public
static
void
main(String[] args) {
long
start = System.nanoTime();
for
(
int
i =
0
; i <
1000
*
1000
*
10
; ++i) {
Foo foo =
new
Foo();
}
long
end = System.nanoTime();
System.out.println(
"Time cost is "
+ (end - start));
}
}
|
使用逃逸分析優化 JVM輸出結果( -server -XX:+DoEscapeAnalysis -XX:+PrintGC)
1
|
Time cost is
11012345
|
未使用逃逸分析優化 JVM 輸出結果( -server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC)
1
2
3
4
5
|
[GC (Allocation Failure) 33280K->408K(125952K),
0.0010344
secs]
[GC (Allocation Failure) 33688K->424K(125952K),
0.0009799
secs]
[GC (Allocation Failure) 33704K->376K(125952K),
0.0007297
secs]
[GC (Allocation Failure) 33656K->456K(159232K),
0.0014817
secs]
Time cost is
68562263
|
分析結果,性能優化1/6。