java对象创建的流程到底是什么样子的?new一个对象是真的直接放在堆里吗?其实大有学问!
目錄
對象創建流程圖
總結
逃逸分析
什么是逃逸分析?
對象逃逸狀態
逃逸分析優化
TLAB區
對象如何進入老年代
有問題可以直接留言討論~
對象創建流程圖
?? ?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區任然不足以存放當前對象,則直接分配到老年代。(或者超過from to50%大小??)
? ? 7.對象不在堆上分配主要的原因還是堆是共享的,在堆上分配有鎖的開銷。無論是TLAB還是棧都是線程私有的,私有即避免了競爭(當然也可能產生額外的問題例如可見性問題),這是典型的用空間換效率的做法。
總結
1.判斷是否在棧上分配。
2.判斷是否太小,如果太小在TLAB分配。
3.判斷對象是否過大,如果過大則在老年代分配。
4.否則就在eden區分配。
逃逸分析
什么是逃逸分析?
關于 Java 逃逸分析的定義:
逃逸分析(Escape Analysis)簡單來講就是,Java Hotspot 虛擬機可以分析新創建對象的使用范圍,并決定是否在 Java 堆上分配內存的一項技術。
逃逸分析的 JVM 參數如下:
-
開啟逃逸分析:-XX:+DoEscapeAnalysis
-
關閉逃逸分析:-XX:-DoEscapeAnalysis
-
顯示分析結果:-XX:+PrintEscapeAnalysis
逃逸分析技術在 Java SE 6u23+ 開始支持,并默認設置為啟用狀態,可以不用額外加這個參數。
對象逃逸狀態
我們了解了 Java 中的逃逸分析技術,再來了解下一個對象的逃逸狀態。
1、全局逃逸(GlobalEscape)
即一個對象的作用范圍逃出了當前方法或者當前線程,有以下幾種場景:
-
對象是一個靜態變量
-
對象是一個已經發生逃逸的對象
-
對象作為當前方法的返回值
2、參數逃逸(ArgEscape)
即一個對象被作為方法參數傳遞或者被參數引用,但在調用過程中不會發生全局逃逸,這個狀態是通過被調方法的字節碼確定的。
3、沒有逃逸
即方法中的對象沒有發生逃逸。
逃逸分析優化
針對上面第三點,當一個對象沒有逃逸時,可以得到以下幾個虛擬機的優化。
1) 鎖消除
我們知道線程同步鎖是非常犧牲性能的,當編譯器確定當前對象只有當前線程使用,那么就會移除該對象的同步鎖。
例如,StringBuffer 和 Vector 都是用 synchronized 修飾線程安全的,但大部分情況下,它們都只是在當前線程中用到,這樣編譯器就會優化移除掉這些鎖操作。
鎖消除的 JVM 參數如下:
-
開啟鎖消除:-XX:+EliminateLocks
-
關閉鎖消除:-XX:-EliminateLocks
鎖消除在 JDK8 中都是默認開啟的,并且鎖消除都要建立在逃逸分析的基礎上。
2) 標量替換
首先要明白標量和聚合量,基礎類型和對象的引用可以理解為標量,它們不能被進一步分解。而能被進一步分解的量就是聚合量,比如:對象。
對象是聚合量,它又可以被進一步分解成標量,將其成員變量分解為分散的變量,這就叫做標量替換。
這樣,如果一個對象沒有發生逃逸,那壓根就不用創建它,只會在棧或者寄存器上創建它用到的成員標量,節省了內存空間,也提升了應用程序性能。
標量替換的 JVM 參數如下:
-
開啟標量替換:-XX:+EliminateAllocations
-
關閉標量替換:-XX:-EliminateAllocations
-
顯示標量替換詳情:-XX:+PrintEliminateAllocations
標量替換同樣在 JDK8 中都是默認開啟的,并且都要建立在逃逸分析的基礎上。
3) 棧上分配
當對象沒有發生逃逸時,該對象就可以通過標量替換分解成成員標量分配在棧內存中,和方法的生命周期一致,隨著棧幀出棧時銷毀,減少了 GC 壓力,提高了應用程序性能。
在平時開發過程中就要可盡可能的控制變量的作用范圍了,變量范圍越小越好,讓虛擬機盡可能有優化的空間。
簡單舉一個例子吧,如:
return sb;
可以改為:
return sb.toString();
這是一種優化案例,把 StringBuilder 變量控制在了當前方法之內,沒有逃出當前方法作用域。
TLAB區
? ? TLAB全稱是Thread Local Allocation Buffer即線程本地分配緩存,從名字上看是一個線程專用的內存分配區域,是為了加速對象分配而生的。每一個線程都會產生一個TLAB,該線程獨享的工作區域,java虛擬機使用這種TLAB區來避免多線程沖突問題,提高了對象分配的效率。TLAB空間一般不會太大,當大對象無法在TLAB分配時,則會直接分配到堆上。
+XX:+UseTLAB 使用TLAB(默認為使用)
-XX:+TLABSize 設置TLAB大小(最好不要調整,了解即可)
-XX:TLABRefillWasteFraction 設置維護進入TLAB空間的單個對象大小,他是一個比例值,默認為64,即如果對象大于整個空間的1/64,則在堆創建對象。
-XX:+PrintTLAB 查看TLAB信息
-XX:ResizeTLAB 自調整TLABRefillWasteFraction閥值。
對象如何進入老年代
? ? 一般而言,對象首次創建會放置在新生代的eden區,如果沒有GC介入,則對象不會離開eden區,那么eden區的對象如何進入老年代呢?一般來講,只要對象的年齡達到一定的大小,就會自動離開年輕代進入老年代,對象年齡是由對象經歷的GC次數決定的,在新生代每次GC之后如果對象沒有被回收則年齡加1。虛擬機提供了一個參數來控制新生代對象的最大年齡,當超過這個年齡范圍就會晉升老年代。
-XX:MaxTenuringThreshold,默認情況下為15。
? ? 總結:根據設置MaxTenuringThreshold參數,可以指定新生代對象經過多少次回收后進入老年代。
? ? 另外,大對象(新生代eden區無法裝入時,也會直接進入老年代)。JVM里有個參數可以設置對象的大小超過指定的大小之后,直接晉升老年代。
-XX:PretenureSizeThreshold
?
?? ?虛擬機對于體積不大的對象 會優先把數據分配到TLAB區域中,因此就失去了在老年代分配的機會?-XX:-UseTLAB(禁用TLAB區域) java默認使用TLAB區域
總結
以上是生活随笔為你收集整理的java对象创建的流程到底是什么样子的?new一个对象是真的直接放在堆里吗?其实大有学问!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java反射,从0开始
- 下一篇: 今天刚学的idea的debug打断点,C