面試官:Java虛擬機的記憶體分為哪幾個區域?
我(微笑著):程式計數器、虛擬機堆疊、本地方法堆疊、堆、方法區
面試官:物件一般存放在哪個區域?
我:堆,
面試官:物件都存放在堆中嗎?
我:是的,
面試官:你了解過逃逸分析嗎?
我(皺了皺眉):是記憶體溢位嗎?
面試官:不是的,
我(撓了撓頭):不是很了解,
面試官:今天的面試先到這,回去等訊息吧!
然后就沒有然后了,不甘心的我開始了查找相關資料,
逃逸分析
逃逸分析(Escape Analysis)是一種確定物件的參考動態范圍的分析方法,說人話就是:分析在程式的哪些地方可以訪問到物件的參考,
當一個物件在方法中被分配時,該物件的參考可能逃逸到其它執行執行緒中,或是回傳到方法的呼叫者,
如果一個方法中分配一個物件并回傳一個該物件的參考針,那么該物件可能被訪問到的地方就無法確定,此時物件的參考就發生了“逃逸”,
如果物件的參考存盤在靜態變數或者其它資料結構中,因為靜態變數是可以在當前方法之外訪問到,此時物件的參考也發生了“逃逸”,
逃逸分析確定某個物件的參考可以被訪問的所有地方,以及確定能否保證物件的參考的生命周期只在當前行程或執行緒中,
逃逸狀態
物件的逃逸狀態一般分為三種:全域逃逸、引數逃逸、沒有逃逸,
全域逃逸(GlobalEscape)
物件的參考逃出了方法或者執行緒,比如:物件的參考賦值給了一個靜態變數,或者存盤在一個已經逃逸的物件中, 或者物件的參考作為方法的回傳值給了呼叫方法,
比如餓漢的單例模式:
package one.more;
public final class GlobalEscape {
// instance物件賦值給了一個靜態變數,發生了全域逃逸
private static GlobalEscape instance = new GlobalEscape();
private GlobalEscape() {
}
public static GlobalEscape getInstance() {
return instance;
}
}
引數逃逸(ArgEscape)
物件被作為方法引數傳遞或者被引數參考,但在呼叫程序中不會發生全域逃逸,這個狀態是通過分析被呼叫方法的位元組碼來確定的,
比如:
package one.more;
public class ArgEscape {
class Rectangle {
private int length;
private int width;
public Rectangle(int length, int width) {
this.length = length;
this.width = width;
}
public int getArea() {
return this.length * this.width;
}
}
public int getArea(int length, int width) {
Rectangle rectangle = buildRectangle(length, width);
return rectangle.getArea();
}
private Rectangle buildRectangle(int length, int width){
Rectangle rectangle = new Rectangle(length, width);
// rectangle物件發生了引數逃逸
return rectangle;
}
}
沒有逃逸(NoEscape)
方法中的物件沒有發生逃逸,這意味著可以不將該物件分配在堆上,
比如:
package one.more;
public class NoEscape {
class Rectangle {
private int length;
private int width;
public Rectangle(int length, int width) {
this.length = length;
this.width = width;
}
public int getArea() {
return this.length * this.width;
}
}
public int getArea(int length, int width) {
// rectangle物件沒有逃逸
Rectangle rectangle = new Rectangle(length, width);
return rectangle.getArea();
}
}
逃逸分析后的優化
如果一個物件沒有發生逃逸,或者只有引數逃逸,就可能為這個物件采取不同程度的優化,比如:堆疊上分配、標量替換、同步消除,
堆疊上分配(Stack Allocations)
如果一個物件不會逃逸出執行緒之外,那讓這個物件在堆疊上分配記憶體將會是一個很不錯的主意,物件所占用的記憶體空間就可以隨堆疊幀出堆疊而銷毀,
那么,物件就會隨著方法的結束而自動銷毀了,可以降低垃圾收集器運行的頻率,垃圾收集的壓力就會下降很多,
標量替換(Scalar Replacement)
標量(Scalar)是指一個無法再分解成更小的資料的資料,Java虛擬機中的基本資料型別(int、long等數值型別及reference型別等)都不能再進一步分解了,那么這些資料就可以被稱為標量,相對的,如果一個資料可以繼續分解,那它就被稱為聚合量(Aggregate),Java中的物件就是典型的聚合量,
如果把一個Java物件拆散,根據程式訪問的情況,將其用到的成員變數恢復為基本型別來訪問,這個程序就稱為標量替換,
如果一個物件沒有發生逃逸,可以進行標量替換,那么物件的成員變數就在堆疊上分配和讀寫,不需要分配到堆中,
標量替換可以視作堆疊上分配的一種特例,實作更簡單,但對逃逸程度的要求更高,它不允許物件沒有發生逃逸,
同步消除(Synchronization Elimination)
執行緒同步本身是一個相對耗時的程序,如果一個物件沒有逃逸出執行緒,無法被其他執行緒訪問,那么該物件的讀寫肯定就不會有競爭,對該物件實施的同步加鎖操作也就可以安全地消除掉,
總結
說了這么多,可以發現物件并不是都在堆上分配記憶體的,因為通過逃逸分析后,可以對沒有逃逸的物件進行標量替換,
另外,由于復雜度等原因,HotSpot中目前還不支持堆疊上分配的優化,
最后,謝謝你這么帥,還給我點贊和關注,
微信公眾號:萬貓學社
微信掃描二維碼
關注后回復「電子書」
獲取12本Java必讀技術書籍
作者:萬貓學社
出處:http://www.cnblogs.com/heihaozi/
著作權宣告:本文遵循 CC 4.0 BY-NC-SA 著作權協議,轉載請附上原文出處鏈接和本宣告,
微信掃描二維碼,關注萬貓學社,回復「電子書」,免費獲取12本Java必讀技術書籍,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/443442.html
標籤:Java
