棧上分配

棧上分配(逃逸分析)

分析

​ 逃逸分析的基本行爲就是分析對象動態作用域:當一個對象在方法中被定義後,它可以能被外部方法所引用,例如作爲調用參數傳遞到其它地方種,稱爲方法逃逸。

​ 甚至還有可能被外部線程訪問到,譬如賦值給類變量或者可以在其它線程中訪問的實例變量,稱爲線程逃逸。

逃逸分析

​ 在計算機語言編譯器優化原理中,<u>逃逸分析是指分析指針動態範圍的方法</u>,它同編譯器優化原理的指針分析和外形分析相關聯。當變量/對象在方法中分配後,其指針有可能被返回或者被全局引用,這樣會被其它過程或者線程所引用,這種現象稱爲指針/引用的逃逸(Escape)。

​ 通俗的說,如果一個對象的指針被多個方法或者線程引用時,那麼我們成這個對象的指針發生了逃逸。

逃逸的三種狀態

  1. 全局逃逸:一個對象的引用掏出了方法或者線程。例如:一個對象的引用賦值給一個類變量,或者這個對象的用引用作爲方法的返回這返回給了調用方法。
  2. 參數級逃逸:方法調用過程中,傳遞對象給另一個方法
  3. 沒有逃逸:一個可以進行標量替換的對象,可以將這個對象不分配在傳統的堆上

逃逸分析作用

​ 通過逃逸分析,java Hotspot 編譯器能夠分析出一個新的對象的引用的使用範圍,從而覺得是否需要將這個對象分配到堆上。

逃逸分析後的優化

  1. 棧上分配:一個方法中的對象,若該對象沒有發生逃逸,則可以將這個對象分配在棧上
  2. 消除同步:線程同步的代價是相當高的,同步帶來的後果是降低了併發性和程序性能。逃逸分析以判斷某個對象是否始終只被一個線程訪問,如果只被一個線程訪問,那麼該對象的同步操作就可以轉化爲沒有同步的操作,這樣可以大大提高併發性能
  3. 標量替換:java虛擬機中的原始數據類型(int,long等)都不能在進一步分解,他們就可以成爲標量。相對的,如果一個數據可以繼續分解,那麼他成爲聚合量,java中最典型的聚合量就是對象。如果逃逸分析證明一個對象不會被外部訪問,並且這個這個對象是可以分解的,那麼程序真正執行的時候可能不創建這個對象,而改爲直接創建它的若干個被這個方法能夠使用到的成員變量來代替。拆散後的變量便可以被單獨的分析與優化,可以分別分配在棧幀或者寄存器上,原來的對象就不需要整體被分配在堆中。

 

 

一般在java程序中,new的對象是分配在堆空間中的,但是實際的情況是,大部分的new對象會進入堆空間中,而並非是全部的對象,還有另外兩個地方可以存儲new的對象,我們稱之爲棧上分配以及TLAB(其實也是在堆上)

棧上分配:針對那些作用域不會逃逸出方法的對象,在分配內存時不在將對象分配在堆內存中,而是將對象屬性打散後分配在棧(線程私有的,屬於棧內存)上,這樣,隨着方法的調用結束,棧空間的回收就會隨着將棧上分配的打散後的對象回收掉,不再給gc增加額外的無用負擔,從而提升應用程序整體的性能。

小對象(一般幾十個byte),在沒有逃逸的情況下,可以直接分配在棧上
直接分配在棧上,可以自動回收,減輕GC壓力
大對象或者逃逸對象無法在棧上分配
棧上分配需要有一定的前提

開啓逃逸分析 (-XX:+DoEscapeAnalysis)
逃逸分析的作用就是分析對象的作用域是否會逃逸出方法之外,在server虛擬機模式下才可以開啓(jdk1.6默認開啓)
開啓標量替換 (-XX:+EliminateAllocations)
標量替換的作用是允許將對象根據屬性打散後分配在棧上,比如若一個對象擁有兩個字段,會將這兩個字段視作局部變量進行分配。默認該配置爲開啓
對象分配在堆上,而堆是一個全局共享的區域,當多個線程同一時刻操作堆內存分配對象空間時,就需要進行同步,而同步帶來的效果就是對象分配效率變差(儘管JVM採用了CAS的形式處理分配失敗的情況),但是對於存在競爭激烈的分配場合仍然會導致效率變差。

TLAB:Thread Local Allocation Buffer 即線程本地分配緩存
JVM默認開啓了TLAB功能,也可以使用-XX: +UseTLAB 顯示開啓
JVM提供了-XX:+PrintTLAB 參數打開跟蹤TLAB的使用情況
-XX:TLABSize 通過該參數指定分配給每一個線程的TLAB空間的大小

需要TLAB的原因就是提高對象在堆上的分配效率而採用的一種手段,就是給每個線程分配一小塊私有的堆空間,即TLAB是一塊線程私有的堆空間(實際上是Eden區中劃出的)

如果開啓棧上分配,JVM會先進行棧上分配,如果沒有開啓棧上分配或則不符合條件的則會進行TLAB分配,如果TLAB分配不成功,再嘗試在eden區分配,如果對象滿足了直接進入老年代的條件,那就直接分配在老年代。如下圖

 

 

對象內存分配的兩種方法
爲對象分配空間的任務等同於把一塊確定大小的內存從Java堆中劃分出來。
指針碰撞(Serial、ParNew等帶Compact過程的收集器)
假設Java堆中內存是絕對規整的,所有用過的內存都放在一邊,空閒的內存放在另一邊,中間放着一個指針作爲分界點的指示器,那所分配內存就僅僅是把那個指針向空閒空間那邊挪動一段與對象大小相等的距離,這種分配方式稱爲“指針碰撞”(Bump the Pointer)。

空閒列表(CMS這種基於Mark-Sweep算法的收集器)
如果Java堆中的內存並不是規整的,已使用的內存和空閒的內存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄,這種分配方式稱爲“空閒列表”(Free List)。

對象在內存的引用方式

 

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