史上最全java堆,将知识点掰碎了给你嚼,还不信学不会。
目錄
一、開局一張圖
二、堆的核心概述(注意“可能”、“幾乎”這幾個關鍵字,后面會談到)
舉例
三、堆的內存細分結構
1.現在垃圾收集器大部分都基于分代收集理論設計,堆空間細分為:
2.堆空間內部結構(-Xmx也只應用于新生代和老年代)
四、設置堆內存大小
五、OOM舉例
六、年輕代和老年代
配置新生代與老年代在堆結構的占比
七、對象分配過程
對象分配過程概述
總結
對象分配特殊情況
對象分配過程演示
八、Minor?GC、Major?GC與Full?GC
最簡單的分代式GC策略的觸發條件
實例
九、理解堆空間分代思想
為什么需要把java堆分代?不分代就不能正常工作了嗎?
十、內存分配策略(總結)
針對不同年齡段的對象分配原則如下所示
實例:大對象直接進入老年代
十一、為對象分配內存:TLAB
TLAB的說明:
對象分配過程(加上TLAB)
十二:堆空間的參數設置總結
十三、堆是分配對象存儲的唯一選擇嗎?
逃逸分析:
舉例
參數設置:
結論:
十四、逃逸分析-代碼優化
1.棧上分配。
2.同步省略。
3.分離對象或標量替換。
逃逸分析總結:
十五、jvm工具
一、Java\jdk1.8.0_131\bin\jvisualvm.exe的使用(分析內存)
二、jps->jstat查看堆內存
三、java使用-XX:+PrintGCDetails參數,可以查看堆信息打印
四、Jprofiler
一、開局一張圖
? ? ? ? 今天我們只說java的堆。java虛擬機其實由很多部分組成,而堆只是其中一部分,但是也是很重要的一部分。尤其是面試經常會問!
二、堆的核心概述(注意“可能”、“幾乎”這幾個關鍵字,后面會談到)
? ? 1.一個JVM實例只存在一個堆內存,堆也是java內存管理的核心區域。
? ? 2.java堆區在JVM啟動的時候即被創建,其空間大小也就確定了。是JVM管理的最大一塊內存空間。(堆內存的大小是可以調節的)
? ? 3.《Java虛擬機規范》規定,堆可以處于物理上不連續的內存空間中,但在邏輯上它應該被視為連續的。
? ? 4.所有的線程共享java堆,在這里還可以劃分線程私有的緩沖區(Thread Local Allocation Buffer, TLAB)。
? ? 5.《java虛擬機規范》中對java堆的描述是:所有的對象實例以及數組都應當在運行時分配在堆上。(The heap is the run-time data area from which memory for all class instances and arrays is allocated)
?? ?? ? 我要說的是:“幾乎”所有的對象實例都在這里分配內存。——從實際使用角度看的。
? ? 6.數組和對象可能永遠不會存儲在棧上,因為棧幀中保存引用,這個引用指向對象或者數組在堆中的位置。
? ? 7.在方法結束后,堆中的對象不會馬上被移除,僅僅在垃圾收集的時候才會被移除。
? ? 8.堆,是GC(Garbage Collection,垃圾收集器)執行垃圾回收的重點區域。
舉例
字節碼文件,new關鍵字就是在堆中開辟了一塊空間。
(堆創建對象,棧引用類型變量指向堆,方法區存放類及其方法實現)
三、堆的內存細分結構
1.現在垃圾收集器大部分都基于分代收集理論設計,堆空間細分為:
(1)java7及之前堆內存邏輯上分為三部分:新生區+養老區+永久區
? ? Young Generation Space,新生區,Young/New
?? ?? ? 又被劃分為Eden區和Survivor區
? ? Tenure generation space,養老區,Old/Tenure
? ? Permanent?Space,永久區,Perm
(2)java8及之后堆內存邏輯上分為三部分:新生區+養老區+元空間
? ? Young Generation Space,新生區,Young/New
?? ?? ? 又被劃分為Eden區和Survivor區
? ? Tenure generation space,養老區,Old/Tenure
? ? Meta Space,元空間,Meta
約定:新生區<=>新生代<=>年輕代;養老區<=>老年區<=>老年代;永久區<=>永久代
2.堆空間內部結構(-Xmx也只應用于新生代和老年代)
四、設置堆內存大小
1.java堆區用于存儲java對象實例,那么堆的大小在JVM啟動時就已經設定好了,大家可以通過選項“-XMx”和“-Xms”來進行設置(新生代+老年代)。
? ? “-Xms”用于表示堆區的起始內存,等價于-XX:InitialHeapSize
? ? “-Xmx”則用于表示堆區的最大內存,等價于-XX:MaxHeapSize
? ? -X?是jvm運行參數
? ? ms?是memory?start
2.一旦堆區中的內存大小超過“-Xmx”所指定的最大內存時,將會拋出OutOfMemoryError異常。
3.通常會將-Xms和-Xmx兩個參數配置相同的值,其目的是為了能夠在java垃圾回收機制清理完堆區后不需要重新分隔計算堆區的大小,從而提高性能。
4.默認情況下,初始內存大小:物理電腦內存大小 / 64;最大內存大小:物理電腦內存大小 / 4
5.查看設置的參數:方式一,jps ->jstat -gc?進程id;方式二,-XX:+PrintGCDetails
例如(默認是b,大小寫皆可):
-Xms1024
-Xms6166k
-Xms60m
-XX:InitialHeapSize=1024k
代碼獲取堆內存信息(假如說配置了jvm600M,用代碼只能得出575M,說明有25M是s1或s0區,因為這兩個區域只能同時使用一個):
使用-XX:+PrintGCDetails參數,可以查看堆信息打印。(可以對比jstat -gs,查看堆信息,這兩種方式是一樣的)(-XX:+PrintGCDetails打印的新生代total,from和to區,只算一個,因為同一時間只會用到一個)
(后面有使用jstat詳細方法)
五、OOM舉例
六、年輕代和老年代
? ? 1.存儲在JVM中的java對象可以被劃分為兩類:?? ??
?? ?? ? 一類是生命周期較短的瞬時對象,這類對象的創建和消亡都非常迅速。
?? ?? ? 另外一類對象的生命周期卻非常長,在某些極端的情況下還能夠與JVM的生命周期保持一致。
? ? 2.java堆區進一步細分的話,可以劃分為年輕代(YoungGen)和老年代(OldGen)。
? ? 3.其中年輕代又可以劃分為Eden空間、Survivor0空間和Survivor1空間(有時也叫作from區、to區)。
配置新生代與老年代在堆結構的占比
(這個參數開發中一般不會去調,如果明確知道很多對象生命周期比較長,可以將老年代調大)
? ? 1.默認-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整個堆的1/3。
? ? 2.可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整個堆的1/5。
? ? 3.在HotSpot中。Eden空間和另外兩個Survivor空間缺省所占的比例是8:1:1,當然開發人員可以通過選項“-XX:SurvivorRatio”調整這個空間比例。比如-XX:SurvivorRatio=8。
(jvm有個自適應的機制,可能配置了比例為8,但是實際上用工具看到的仍然是6:1:1,可以這樣關掉自適應內存分配策略:-XX:-UseAdaptiveSizePolicy)
? ? 4.幾乎所有的java對象都是在Eden區被new出來的。
? ? 5.絕大部分的java對象的銷毀都在新生代進行了。
?? ?? ? IBM公司的專門研究表明,新生代中80%的對象都是“朝生夕死”的。
? ? 6.可以使用選項“-Xmn”設置新生代最大內存大小。
?? ?? ? 這個參數一般使用默認值就可以了,直接設置比例即可,否則會跟“-XX:SurvivorRatio”沖突。
七、對象分配過程
對象分配過程概述
? ? 為新對象分配內存是一件非常嚴謹和復雜的任務,JVM的設計者們不僅需要考慮內存如何分配、在哪里分配等問題,并且由于內存分配算法與內存回收算法密切相關,所以還需要考慮GC執行完內存回收后是否會在內存空間中產生內存碎片。
? ? 1.new的對象先放伊甸園區。此區有大小限制。
? ? 2.當伊甸園的空間填滿時,程序又需要創建對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收(Minor?GC),將伊甸園區中的不再被其他對象所引用的對象進行銷毀。再加載新的對象放到伊甸園區。
? ? 3.然后將伊甸園區中的剩余對象移動到幸存者0區。
? ? 4.如果再次觸發垃圾回收,此時上次幸存下來的放到幸存者0區的對象,如果沒有回收,就會放到幸存者1區。
? ? 5.如果再次經歷垃圾回收,此時會重新放回幸存者0區,接著再去幸存者1區。
? ? 6.啥時候能去養老區呢?可以設置次數。默認是15次。
?? ?? ? ·?可以設置參數:-XX:MaxTenuringThreshold=<N>進行設置。
? ? 7.在養老區,相對悠閑。當養老區內存不足時,再次觸發GC:Major?GC,進行養老區的內存清理。
? ? 8.若養老區執行了Major?GC之后發現依然無法進行對象的保存,就會產生OOM異常。
?? ?注意:eden區滿了之后會觸發YGC/Minor GC,同時帶著Survivor區GC。而Survivor區滿了并不會觸發GC,這并不意味著Survivor區不會GC,而是它的GC是eden區滿了之后才會觸發GC。Survivor區滿了,對象會直接從eden區直接到老年代或者沒有滿15次直接到老年代(視情況而定,下面會提到)。
總結
? ? 1.針對幸存者s0,s1區的總結:復制之后有交換,誰空誰是to。
? ? 2.關于垃圾回收:頻繁在新生區收集,很少在養老區收集,幾乎不再永久區/元空間收集。
對象分配特殊情況
對象分配過程演示
(后面附有jvisualvm使用方法)
八、Minor?GC、Major?GC與Full?GC
? ? 1.JVM在進行GC時,并非每次都對上面三個內存(新生代、老年代;方法區)區域一起回收的,大部分時候回收的都是指新生代。
? ? 2.針對HotSpot?VM的實現,它里面的GC按照回收區域又分為兩大種類型:一種是部分收集(Partial?GC),一種是整堆收集(Full?GC)。
? ? 2.1部分收集:不是完整收集整個java堆的垃圾收集。其中又分為:
?? ?? ? 2.1.1新生代收集(Minor GC / Young GC):只是新生代(Eden、s0、s1)的垃圾收集。
?? ?? ? 2.1.2老年代收集(Major GC / Old GC):只是老年代的垃圾收集。
?? ??? ?? ? 目前,只有CMS?GC會有單獨收集老年代的行為。
?? ??? ?? ? 注意,很多時候Major?GC會和Full?GC混淆使用,需要具體分辨是老年代回收還是整堆回收。
?? ?? ? 2.1.3混合收集(Mixed?GC):收集整個新生代以及部分老年代的垃圾收集。
?? ??? ?? ? 目前,只有G1?GC會有這種行為。
? ? 2.2整堆收集(Full?GC):收集整個java堆和方法區的垃圾收集。
最簡單的分代式GC策略的觸發條件
1.年輕代GC(Minor?GC)觸發機制:
? ? 當年輕代空間不足時,就會觸發Minor?GC,這里的年輕代滿指的是Eden代滿,Survivor滿不會引發GC。(每次Minor?GC會清理年輕代的內存。)
? ? 因為java對象大多都具備朝生夕滅的特性,所以Minor?GC非常頻繁,一般回收速度也比較快,這一定義既清晰又易于理解。
? ? Minor?GC會引發STW(stop?the?word),暫停其他用戶的線程,等垃圾回收結束,用戶線程才恢復運行。
2.老年代GC(Major GC/Full GC)觸發機制:
? ? 指發生在老年代的GC,對象從老年代消失時,我們說“Major?GC”或“Full?GC”發生了。
? ? 出現Major?GC,經常會伴隨至少一次的Minor?GC(但非絕對的,在Parallel?Scavenge收集器的收集策略里就有直接進行Major?GC的策略選擇過程)。
?? ?? ? 也就是在老年代空間不足時,會嘗試觸發Minor?GC。如果之后空間還不足,則觸發Major?GC。
? ? Major GC的速度一般會比Minor?GC慢10倍以上。STW的時間更長。
? ? 如果Major?GC后,內存還不足,就報OOM了。
3.Full?GC觸發機制:(后面會細說)
? ? 觸發Full?GC執行的情況有以下五種:
? ? (1)調用System.gc()時,系統建議執行Full?GC,但是不必然執行。
? ? (2)老年代空間不足
? ? (3)方法區空間不足
? ? (4)通過Minor?GC后進入老年代的平均大小大于老年代的可用內存。
? ? (5)由Eden區、survivor?space0(From?Space)區向survivor?space1(To?Space)區復制時,對象大小大于To?Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小于該對象大小。
? ? 說明:full?GC是開發或調優中盡量要避免的。這樣暫時時間會短一些。
實例
(使用-XX:+PrintGCDetails,會打印GC情況)
九、理解堆空間分代思想
為什么需要把java堆分代?不分代就不能正常工作了嗎?
? ? 經研究,不同對象的生命周期不同。70%-99%的對象是臨時對象。
?? ?? ? ·?新生代:有Eden、兩塊大小相同的Survivor(又稱為from/to,s0/s1)構成,to總為空。
?? ?? ? ·?老年代:存放新生代中經歷多次GC仍然存活的對象。
? ? 其實不分代完全可以,分代的唯一理由就是優化GC性能。如果沒有分代,那所有的對象都在一塊,就如同把一個學校的人都關在一個教室。GC的時候要找到哪些對象沒用,這樣就會對堆的所有區域進行掃描。而很多對象都是朝生夕死的,如果分代的話,把新創建的對象放到某一地方,當GC的時候先把這塊存儲“朝生夕死”對象的區域進行回收,這樣就會騰出很大的空間出來。
十、內存分配策略(總結)
? ? 如果對象在Eden?出生并經過第一次MinorGC后仍然存活,并且能被Survivor容納的話,將被移動到Survivor空間中,并將對象年齡設置為1。對象在Survivor區每熬過一次MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲,其實每個JVM、每個GC都有所不同)時,就會被晉升到老年代中。
? ? 對象晉升老年代的年齡閾值,可通過選項 -XX:MaxTenuringThreshold來設置。
針對不同年齡段的對象分配原則如下所示
? ? 1.優先分配到Eden。
? ? 2.大對象直接分配到老年代。
?? ?? ? 盡量避免程序中出現過多的大對象。
? ? 3.長期存活的對象分配到老年代。
? ? 4.動態對象年齡判斷。
?? ?? ? 如果Survivor區中相同年齡的所有對象大小的綜合大于Survivor空間的一半,年齡大于或等于該年齡的對象可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。
? ? 5.空間分配擔保(大量對象在MinorGC之后仍然還有很多,那么這些對象可以直接放到老年代)。
?? ?? ? -XX:HandlePromotionFailure
實例:大對象直接進入老年代
十一、為對象分配內存:TLAB
為什么有TLAB(Thread Local Allocation Buffer)?
? ? 1.堆區是線程共享區域,任何線程都可以訪問到堆區中的共享數據。
? ? 2.由于對象實例的創建在JVM中非常頻繁,因此在并發環境下從堆區中劃分內存空間是線程不安全的。
? ? 3.為避免多個線程操作同一地址,需要使用加鎖等機制,進而影響分配速度。
什么是TLAB?
? ? 1.從內存模型而不是垃圾收集的角度,對Eden區域繼續進行劃分,JVM為每個線程分配了一個私有緩存區域,它包含在Eden空間內。
? ? 2.多線程同時分配內存時,使用TLAB可以避免一系列的非線程安全問題,同時還能夠提升內存分配的吞吐量,因此我們可以將這種內存分配方式稱之為快速分配策略。
? ? 3.據我所知所有OpenJDK衍生出來的JVM都提供了TLAB的設計。
TLAB的說明:
? ? 1.盡管不是所有的對象實例都能夠在TLAB中成功分配內存,但JVM確實是將TLAB作為內存分配的首選。
? ? 2.在程序中,開發人員可以通過選項“-XX:UseTLAB”設置是否開啟TLAB空間(默認是開啟TLAB)。
? ? 3.默認情況下,TLAB空間的內存非常小,僅占有整個Eden空間的1%,當然我們可以通過選項“-XX:TLABWasteTargetPercent”設置TLAB空間所占用Eden空間的百分比大小。
? ? 4.一旦對象在TLAB空間分配內存失敗時,JVM就會嘗試著通過使用加鎖機制確保數據操作的原子性,從而直接在Eden空間分配內存。
對象分配過程(加上TLAB)
十二:堆空間的參數設置總結
官網說明:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
查看jvm啟動參數:jinfo -flag?NewRatio pid
-XX:+PrintFlagsInitial:查看所有的參數的默認初始值
-XX:+PrintFlagsFinal:查看所有的參數的最終值(可能會存在修改,不再是初始值)
-Xms200m,(b、k、m、g,大小寫皆可)用于表示堆區的起始內存,等價于-XX:InitialHeapSize(默認為物理內存的1/64)
-Xmx200m,(b、k、m、g,大小寫皆可)則用于表示堆區的最大內存,等價于-XX:MaxHeapSize(默認為物理內存的1/4)
-XX:+PrintGCDetails,控制臺打印堆、GC信息
-XX:+PringGC 、-verbose:gc ,打印gc簡要信息
-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整個堆的1/3
-XX:SurvivorRatio=8,表示Eden空間和另外兩個Survivor空間缺省所占的比例是8:1:1
-XX:-UseAdaptiveSizePolicy,表示關掉自適應內存分配策略(否則會自動分配內存空間,使-XX:SurvivorRatio=8并不完全生效)
-Xmn,設置新生代最大內存大小,跟“-XX:SurvivorRatio”沖突
-XX:MaxTenuringThreshold=<N>,進行設置對象在Survivor經歷幾次GC進入老年代
-XX:HandlePromotionFailure=<boolean>,空間分配擔保(大量對象在MinorGC之后仍然還有很多,那么這些對象可以直接放到老年代)
HandlePromotionFailure注意:
1.在發生Minor?GC之前,虛擬機會檢查老年代最大可用的連續空間是否大于新生代所有對象的總空間。
? ? 如果大于,則此次Minor?GC是安全的。
? ? 如果小于,則虛擬機會查看-XX:HandlePromotionFailure設置值是否允許擔保失敗。
?? ?? ? 如果HandlePromotionFailure=true,那么會繼續檢查老年代最大可用連續空間是否大于歷次晉升到老年代的對象的平均大小。
?? ??? ?? ? 如果大于,則嘗試進行一次Minor?GC,但這次Minor?GC依然是由風險的;
?? ??? ?? ? 如果小于,則改為進行一次Full?GC。
?? ?? ? 如果HandlePromotionFailure=false,則改為進行一次Full?GC。
2.在JDK6?Update24之后,HandlePromotionFailure參數不會再影響到虛擬機的空間分配擔保策略,觀察OpenJDK中的源碼變化,雖然源碼中還定義了HandlePromotionFailure參數,但是在代碼中已經不會再使用它。JDK6 Update24之后的規則變為只要老年代的連續空間大于新生代對象總大小或者歷次晉升的平均大小就會進行Minor?GC,否則將進行Full?GC。(相當于HandlePromotionFailure就是true)
?? ??? ?
-XX:UseTLAB,設置是否開啟TLAB空間
-XX:TLABWasteTargetPercent,設置TLAB空間所占用Eden空間的百分比大小
-XX:+DoEscapeAnalysis,顯式開啟逃逸分析(JDK 6u23之后默認開啟)。
-XX:+PrintEscapeAnalysis,查看逃逸分析的篩選結果。
-XX:+EliminateAllocations,開啟了標量替換(默認打開),允許將對象打散分配在棧上。
十三、堆是分配對象存儲的唯一選擇嗎?
? ? 在《深入理解Java虛擬機》中關于Java堆內存有這樣一段描述:隨著JIT編譯器的發展與逃逸分析技術逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化,所有的對象都分配到堆上也漸漸變得不那么“絕對”了。
? ? 在Java虛擬機中,對象是在Java堆中分配內存的,這是一個普遍的常識。但是,有一種特殊情況,那就是如果經過逃逸分析(Escape?Analysis)后發現,一個對象并沒有逃逸出方法的話,那么就可能被優化成棧上分配。這樣就無需再堆上分配內存,也無須進行垃圾回收了。這也是最常見的堆外存儲技術。
? ? 此外,前面提到的基于OpenJDK深度定制的TaoBaoVM,其中創新的GCIH(GC?invisible?heap)技術實現off-heap,將生命周期較長的Java對象從heap中移至heap外,并且GC不能管理GCIH內部的java對象,以此達到降低GC的回收頻率和提升GC的回收效率的目的。
逃逸分析:
1.如何將堆上的對象分配到棧,需要使用逃逸分析手段。
2.這是一種可以有效減少Java程序中同步負載和內存堆分配壓力的跨函數全局數據流分析算法。
3.通過逃逸分析,Java?Hotspot編譯器能夠分析出一個新的對象的引用的使用范圍,從而決定是否要將這個對象分配到堆上。
4.逃逸分析的基本行為就是分析對象動態作用域:
? ? 當一個對象在方法中被定義后,對象實體只在方法內部使用,則認為沒有發生逃逸(就使用棧上分配)。
? ? 當一個對象在方法中被定義后,它被外部方法所引用,則認為發生逃逸。例如作為調用參數傳遞到其他地方中。
沒有發生逃逸的對象,則可以分配到棧上,隨著方法執行的結束,棧空間就被移除。
舉例
下面代碼v沒有發生逃逸:
下面代碼,sb發生了逃逸:
上述代碼如果想要StringBuffer?sb不逃逸,可以這樣寫:
逃逸分析情況舉例:
參數設置:
1.在JDK 6u23版本之后,HotSpot中默認就已經開啟了逃逸分析。
2.如果使用的是較早的版本,開發人員則可以通過:
? ? 選項“-XX:+DoEscapeAnalysis”顯式開啟逃逸分析。
? ? 通過選項“-XX:+PrintEscapeAnalysis”查看逃逸分析的篩選結果。
結論:
開發中能使用局部變量的,就不要使用在方法外定義。
十四、逃逸分析-代碼優化
使用逃逸分析,編譯器可以對代碼做如下優化:
1.棧上分配。
將堆分配轉化為棧分配。如果一個對象在子程序中被分配,要使指向該對象的指針永遠不會逃逸,對象可能是棧分配的候選,而不是堆分配。
? ? JIT編譯器在編譯期間根據逃逸分析的結果,發現如果一個對象并沒有逃逸出方法的話,就可能被優化成棧上分配。分配完成后,繼續在調用棧內執行,最后線程結束,棧空間被回收,局部變量對象也被回收。這樣無須進行垃圾回收了。
常見棧上分配的場景:
? ? 在逃逸分析中,已經說明了。分別是給成員變量賦值、方法返回值、實例引用傳遞。
2.同步省略。
如果一個對象被發現只能從一個線程被訪問到,那么對于這個對象的操作可以不考慮同步。
? ? 線程同步的代價是相當高的,同步的后果是降低并發性和性能。
? ? 在動態編譯同步塊的時候,JIT編譯器可以借助逃逸分析來判斷同步塊所使用的的鎖對象是否只能夠被一個線程訪問而沒有被發布到其他線程。如果沒有,那么JIT編譯器在編譯這個同步塊的時候就會取消對這部分代碼的同步。這樣就能大大提高并發性和性能。這個取消同步的過程就叫同步省略,也叫鎖消除。
3.分離對象或標量替換。
有的對象可能不需要作為一個連續的內存結構存在也可以被訪問到,那么對象的部分(或全部)可以不存儲在內存,而是存儲在CPU寄存器中。
? ? 標量(Scalar)是指一個無法再分解成更小的數據的數據。Java中的原始數據類型就是標量。
? ? 相對的,那些還可以分解的數據叫做聚合量(Aggregate),Java中的對象就是聚合量,因為他可以分解成其他聚合量和標量。
? ? 在JIT階段,如果經過逃逸分析,發現一個對象不會被外界訪問的話,那么經過JIT優化,就會把這個對象拆解成若干個其中包含的若干個成員變量來代替。這個過程就是標量替換。
以上代碼經過標量替換后,就會變成:
? ? 可以看到,Point這個聚合量經過逃逸分析之后,發現他并沒有逃逸,就被替換成兩個聚合量了。那么標量替換有什么好處呢?就是可以大大較少堆內存的占用。因為一旦不需要創建對象了,那么就不再需要分配堆內存了。
? ? 標量替換為棧上分配提供了很好地基礎。
?? ?標量替換參數設置:參數-XX:+EliminateAllocations,開啟了標量替換(默認打開),允許將對象打散分配在棧上。
? ? 未開啟標量替換的話,即使開啟了逃逸分析,對象還是分配在堆中的(比如Point)。其實對象是經過了標量替換(用get、set方法也會進行標量替換),才是在棧上分配的。總而言之,如果一個完整的對象還是分配在堆上的,如果說是因為逃逸分析而分配在棧上的話,那這個對象是經過了標量替換,這個對象被分解了,所以就不能再稱這個對象是一個完整的對象了。
逃逸分析總結:
? ? 1.開啟逃逸分析、標量替換,如果對象未發生逃逸,會進行標量替換,并在棧上分配該對象(其實是已經分解成標量了)。
? ? 2.未開啟逃逸分析,對象直接在堆上分配。
? ? 3.開啟逃逸分析,不開啟標量替換,對象仍然是在堆上分配。
//使用 -Xmx60m -Xms60m -XX:+PrintGC -XX:-EliminateAllocations ,取消掉標量替換,會看到很多GC。 //而加上逃逸分析、加上標量替換,就不會看到GC,因為User直接被分解,在棧上分配了。public class TestJVM {public static void main(String[] args) {for (int i = 0; i < 100000000; i++) {ts();}}static void ts(){User u = new User();u.setId("1");u.setName("zs");} } class User{private String id;private String name;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;} }? ? 關于逃逸分析的論文在1999年就已經發表了,但直到JDK1.6才有實現,而且這項技術到如今也并不是十分成熟的。
? ? 其根本原因就是無法保證逃逸分析的性能消耗一定高于他的消耗。雖然經過逃逸分析可以做標量替換、棧上分配、和鎖消除。但是逃逸分析自身也是需要進行一系列復雜的分析的,這其實也是一個相對耗時的過程。
? ? 一個極端的例子,就是經過逃逸分析之后,發現沒有一個對象是不逃逸的。那這個逃逸分析的過程就白白浪費掉了。
? ? 雖然這項技術并不十分成熟,但是它也是即時編譯器優化技術中一個十分重要的手段。
? ? 注意到有一些觀點,認為通過逃逸分析,JVM會在棧上分配那些不會逃逸的對象,這在理論上是可行的,但是取決于JVM設計者的選擇。據我所知,Oracle?Hotspot?JVM并未這么做,這一點在逃逸分析相關的文檔里已經說明,所以可以明確所有的對象實例都是創建在堆上。
? ? 目前很多書籍還是基于JDK 7以前的版本,JDK已經發生了很大變化,intern字符串的緩存和靜態變量曾經都被分配在永久代上,而永久代已經被元數據區替代。但是,intern字符串緩存和靜態變量并不是被轉移到元數據區,而是直接在堆上分配,所以這一點同樣符合前面一點的結論:對象實例都是分配在堆上。
十五、jvm工具
一、Java\jdk1.8.0_131\bin\jvisualvm.exe的使用(分析內存)
1.安裝插件:工具-插件,選擇安裝即可
二、jps->jstat查看堆內存
jstat命令命令格式: jstat [Options] vmid [interval] [count] 命令參數說明: Options,一般使用 -gcutil 或??-gc 查看gc 情況 pid,當前運行的 java進程號 interval,間隔時間,單位為秒或者毫秒 count,打印次數,如果缺省則打印無數次 Options 參數如下: -gc:統計 jdk gc時 heap信息,以使用空間字節數表示 -gcutil:統計 gc時, heap情況,以使用空間的百分比表示 -class:統計 class loader行為信息 -compile:統計編譯行為信息 -gccapacity:統計不同 generations(新生代,老年代,持久代)的 heap容量情況 -gccause:統計引起 gc的事件 -gcnew:統計 gc時,新生代的情況 -gcnewcapacity:統計 gc時,新生代 heap容量 -gcold:統計 gc時,老年代的情況 -gcoldcapacity:統計 gc時,老年代 heap容量 -gcpermcapacity:統計 gc時, permanent區 heap容量 顯示內容詳解: S0C:第一個幸存區的大小 S1C:第二個幸存區的大小 S0U:第一個幸存區的使用大小 S1U:第二個幸存區的使用大小 EC:伊甸園區的大小 EU:伊甸園區的使用大小 OC:老年代大小 OU:老年代使用大小 MC:方法區大小 MU:方法區使用大小 CCSC:壓縮類空間大小 CCSU:壓縮類空間使用大小 YGC:年輕代垃圾回收次數 YGCT:年輕代垃圾回收消耗時間 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間 單位:KB查看jvm啟動參數:
jinfo -flag?NewRatio pid
三、java使用-XX:+PrintGCDetails參數,可以查看堆信息打印
四、Jprofiler
1.建議裝Jprofiler 11
2.idea裝插件,直接搜索Jprofiler
3.啟動
總結
以上是生活随笔為你收集整理的史上最全java堆,将知识点掰碎了给你嚼,还不信学不会。的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis-数据类型与应用
- 下一篇: java方法区超详细汇总,方法区到底是干