JVM之堆

堆在JVM規範裏是一種通用性的內存池(也存在於RAM中),用於存放所有的Java對象。堆是一個運行時數據區,類的對象從中分配空間。這些對象通過New關鍵字被建立,它們不需要程序代碼來顯式地釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存大小,生存週期也不需要事先告訴編譯器。由於它是在運行時動態分配內存的,Java的垃圾收集器會自動收走那些不再使用的數據。但缺點是,由於要在運行時動態分配內存,所以數據存取速度較慢。大多數的虛擬機裏,Java中的對象和數組都存放在堆中。


堆不同於棧的好處是,編譯器不需要知道要從堆裏分配多少存儲區域,也不必知道存儲的數據在堆裏需要存活多長時間。因此,在堆裏分配存儲相較於棧來說,有很大的靈活性。當你需要創建一個對象的時候,只需要引用New關鍵字寫一行簡單的代碼,當執行這行代碼時,會自動在堆裏進行存儲分配。當然,爲這種靈活性必須要付出相應的代價,即用堆進行存儲分配比用堆棧進行存儲存儲需要更多的時間。


Java堆區在JVM啓動的時候即被創建,它只要求邏輯上是連續的,在物理空間上可以是不連續。所有的線程共享Java堆,在這裏可以劃分線程私有的緩衝區(Thread Local Allocation Buffer,TLAB)。


如前所述,Java堆區是一塊用於存儲對象實例的內存區,同時也是GC(GarbageCollection,垃圾收集器)執行垃圾回收的重點區域。正是因爲Java堆區是GC的重點回收區域,那麼GC極有可能會在大內存的使用和頻繁進行垃圾回收過程上成爲系統性能瓶頸。爲了解決這個問題,JVM的設計者們開始考慮是否一定需要將對象實例存儲到Java堆區內。基於OpenJDK[1]深度定製的TaobaoJVM[2],其中創新的GCIH(GC invisible heap)技術實現了off-heap,即將生命週期較長的Java對象從heap中移到heap之外,並且GC不能管理GCIH內部的Java對象,以此達到降低GC的回收頻率和提升GC的回收效率的目的。除此之外,逃逸分析與棧上分配這樣的優化技術同樣也是降低GC回收頻率和提升GC回收效率的有效方式。這樣一來,Java堆區就不再是Java對象內存分配的唯一選擇了。目前主流的垃圾收集算法是按代收集,即按照對象的生存時間分爲新生代和老年代。新生代又進一步被劃分爲Eden區、FromSurvivor區和To Survivor區,主要是爲了垃圾回收用途。這裏淺顯地提一些垃圾回收知識,詳細內容請參考第7章節。


逃逸分析英文全名是Escape Analysis。在計算機語言編譯器優化原理中,逃逸分析是指分析指針動態範圍的方法,它同編譯器優化原理的指針分析和外形分析相關聯。計算機軟件方面,逃逸分析指的是計算機語言編譯器語言優化管理中,分析指針動態範圍的方法。通俗點講,如果一個對象的指針被多個方法或線程引用時,那我們可以稱這個指針發生了逃逸。Java語言也有逃逸情況存在,示例代碼如清單2-6所示。


代碼清單2-6 逃逸分析示例代碼 escapeAnalysisClass

public classescapeAnalysisClass{ 

    public static B b; 

 

    public voidglobalVariablePointerEscape(){//給全局變量賦值,發生逃逸

        b=new B(); 

    } 

 

    public B methodPointerEscape(){//方法返回值,發生逃逸

        return new B(); 

    } 

 

    public voidinstancePassPointerEscape(){ 

       methodPointerEscape().printClassName(this);//實例引用發生逃逸

    } 

 

 

 

class B{ 

    public void printClassName(G g){ 

       System.out.println(g.getClass().getName()); 

    } 

public class G {

     public static B b;

     public voidglobalVariablePointerEscape(){//給全局變量賦值,發生逃逸

         b=new B();

     }

     public B methodPointerEscape(){//方法返回值,發生逃逸

         return new B();

     }

     public void instancePassPointerEscape(){

        methodPointerEscape().printClassName(this);//實例引用發生逃逸

     }

 }

 class B{

     public void printClassName(G g){

        System.out.println(g.getClass().getName());

     }

 }

代碼清單2-6所示的這個例子中,一共舉了3種常見的指針逃逸場景,分別是全局變量賦值、方法返回值、實例引用傳遞。



[1]    OpenJDK做爲GPL許可(GPL-licensed)的Java平臺的開源化實現,Sun正式發佈它已經六年有餘。從發佈那一時刻起,Java社區的大衆們就又開始努力學習,以適應這個新的開源代碼基礎(code-base)。

[2]    由AliJVM團隊發佈,是AliJVM團隊基於OpenJDK HotSpot VM發佈的國內第一個優化、定製且開源的服務器版Java虛擬機。目前已經在淘寶、天貓上線,全部替換了Oracle官方JVM版本。


歡迎關注麥克叔叔每晚10點說,讓我們一起交流與學習。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章