逃逸分析——編譯優化技術(最前沿的優化技術之一)

在Java中,典型的對象不再堆上分配的情況有兩種:TLAB和棧上分配。

  • 一、爲什麼不在堆上分配
    我們知道堆是由所有線程共享的,既然如此那它就是競爭資源,對於競爭資源,必須採取必要的同步,所以當使用new關鍵字在堆上分配對象時,是需要鎖的。既然有鎖,就必定存在鎖帶來的開銷,而且由於是對整個堆加鎖,相對而言鎖的粒度還是比較大的,當對象頻繁分配時,不免影響效率。

所以對於某些特殊情況,可以採取避免在堆上分配對象的辦法,以提高對象創建和銷燬的效率。

  • 二、TLAB分配
    JVM在內存新生代Eden Space中開闢了一小塊區域,由線程私有,稱作TLAB(Thread-local allocation buffer),默認設定爲佔用Eden Space的1%。在Java程序中很多對象都是小對象且用過即丟,它們不存在線程共享也適合被快速GC,所以對於小對象通常JVM會優先分配在TLAB上,並且TLAB上的分配由於是線程私有所以沒有鎖開銷。因此在實踐中分配多個小對象的效率通常比分配一個大對象的效率要高。

  • 也就是說,Java中每個線程都會有自己的緩衝區稱作TLAB(Thread-local allocation buffer),每個TLAB都只有一個線程可以操作,TLAB結合bump-the-pointer技術可以實現快速的對象分配,而不需要任何的鎖進行同步,也就是說,在對象分配的時候不用鎖住整個堆,而只需要在自己的緩衝區分配即可。

  • 三、棧上分配
    JVM在Server模式下的逃逸分析可以分析出某個對象是否永遠只在某個方法、線程的範圍內,並沒有“逃逸”出這個範圍,逃逸分析的一個結果就是對於某些未逃逸對象可以直接在棧上分配,由於該對象一定是局部的,所以棧上分配不會有問題。

  • 四、對象非堆上分配的思想和啓發
    對象不在堆上分配主要的原因還是堆是共享的,在堆上分配有鎖的開銷。無論是TLAB還是棧都是線程私有的,私有即避免了競爭(當然也可能產生額外的問題例如可見性問題),這是典型的用空間換效率的做法。
    在實踐中,類似的做法還有很多,例如Hadoop中對於Map過程在節點的本地內存中處理,直到最後Reduce過程再合併數據。對於任務之間可以分解到不同線程、進程的情況,就可以採用類似的做法用空間換效率,對吞吐率的提升有很大幫助。

JVM即時編譯器(晚期)的優化技術:公共子表達式消除,數組範圍檢查消除,方法內聯,逃逸分析參考運行期優化。這裏主講逃逸分析。

1.逃逸分析:

逃逸分析的基本行爲是:分析對象動態作用域:當一個對象在方法中被定義後,它可能被外部方法引用,作文參數傳遞到其他方法中,稱爲:方法逃逸。或者被線程訪問,稱爲線程逃逸。 而如果一個對象不會逃逸到方法或者線程之外。就是別的方法無法通過任何途徑訪問到這個對象,這個對象或變量就可以進行一些高效優化。

逃逸分析,是一種可以有效減少Java 程序中同步負載和內存堆分配壓力的跨函數全局數據流分析算法

優化方法:

編譯器可以使用逃逸分析的結果,對程序進行一下優化。

  1. 堆分配對象變成棧分配對象。一個方法當中的對象,對象的引用沒有發生逃逸,那麼這個方法可能會被分配在棧內存上而非常見的堆內存上。

  2. 消除同步。線程同步的代價是相當高的,同步的後果是降低併發性和性能。逃逸分析可以判斷出某個對象是否始終只被一個線程訪問,如果只被一個線程訪問,那麼對該對象的同步操作就可以轉化成沒有同步保護的操作,這樣就能大大提高併發程度和性能。

  3. 矢量替代。逃逸分析方法如果發現對象的內存存儲結構不需要連續進行的話,就可以將對象的部分甚至全部都保存在CPU寄存器內,這樣能大大提高訪問速度。

2.TLAB

JVM在內存新生代Eden Space中開闢了一小塊線程私有的區域,稱作TLAB(Thread-local allocation buffer)。默認設定爲佔用Eden Space的1%。在Java程序中很多對象都是小對象且用過即丟,它們不存在線程共享也適合被快速GC,所以對於小對象通常JVM會優先分配在TLAB上,並且TLAB上的分配由於是線程私有所以沒有鎖開銷。因此在實踐中分配多個小對象的效率通常比分配一個大對象的效率要高。
也就是說,Java中每個線程都會有自己的緩衝區稱作TLAB(Thread-local allocation buffer),每個TLAB都只有一個線程可以操作,TLAB結合bump-the-pointer技術可以實現快速的對象分配,而不需要任何的鎖進行同步,也就是說,在對象分配的時候不用鎖住整個堆,而只需要在自己的緩衝區分配即可。

3. Java對象分配的過程

  1. 編譯器通過逃逸分析,確定對象是在棧上分配還是在堆上分配。如果是在堆上分配,則進入選項2.

  2. 如果tlab_top + size <= tlab_end,則在在TLAB上直接分配對象並增加tlab_top 的值,如果現有的TLAB不足以存放當前對象則3.

  3. 重新申請一個TLAB,並再次嘗試存放當前對象。如果放不下,則4.

  4. 在Eden區加鎖(這個區是多線程共享的),如果eden_top + size <= eden_end則將對象存放在Eden區,增加eden_top 的值,如果Eden區不足以存放,則5.

  5. 執行一次Young GC(minor collection)。

  6. 經過Young GC之後,如果Eden區任然不足以存放當前對象,則直接分配到老年代。

原文鏈接

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