java堆內存(Heap)

堆的核心概述

  • 一個JVM實例只存在一個堆內存,堆也是Java內存管理的核心區域。
  • Java堆區在JVM啓動的時候即被創建,其空間大小也就確定了。是JVM管理的最大1塊內存空間。堆內存的大小是可以調節的。-Xms、-Xmx表示初識堆空間大小和最大堆空間大小。
  • 《Java虛擬機規範》規定,堆可以處於物理上不連續的內存空間中,但在邏輯上它應該被視爲連續的。
  • 所有的線程共享Java堆,在這裏還可以劃分線程私有的緩衝區(Thread Local Allocation Buffer, TLAB) 。
  • 《Java虛擬機規範》中對Java堆的描述是:幾乎所有的對象實例以及數組都應當在運行時分配在堆上。“幾乎”所有的對象實例都在這裏分配內存,———從實際使用角度看的。
  • 數組和對象可能永遠不會存儲在棧上,因爲棧幀中保存引用,這個引用指向對象或者數組在堆中的位置。.
  • 方法結束後,堆中的對象不會馬上被移除,僅僅在垃圾收集的時候纔會被移除
  • 堆,是GC ( Garbage Collection,垃圾收集器)執行垃圾回收的重點區域。
    在這裏插入圖片描述

內部細分

現代垃圾收集器大部分都基於分代收集理論設計,堆空間細分爲:(約定:新生區=新生代=年輕代,養老區=老年區=老年代,永久區=永久代
,這幾個名字不做區分,就行一個類中屬性可以叫字段可以叫屬性一樣)
在這裏插入圖片描述

堆空間大小設置

Java堆區用於存儲Java對象實例,那麼堆的大小在JVM啓動時就已經設定好了,可以通過選項"-Xmx"和" -Xms"來進行設置。
➢“-Xms" 用於表示堆區的起始內存(年輕代和老年代),等價於-XX: InitialHeapSize
➢“-Xmx" 則用於表示堆區的最大內存(年輕代和老年代),等價於-XX :MaxHeapSize
一旦堆區中的內存大小超過“-Xmx"所指定的最大內存時,將會拋出OutOfMemoryError異常。
通常會將-Xms 和- -Xmx兩個參 數配置相同的值,其目的是爲了能夠在java垃圾回收機制清理完堆區後不需要重新分隔計算堆區的大小,從而提高性能。
默認情況下,初始內存大小:物理電腦內存大小/ 64,最大內存大小:物理申腦內存大小/4

年輕代和老年代

  • 存儲在JVM中的Java對象可以被劃分爲兩類:

一類是生命週期較短的瞬時對象,這類對象的創建和消亡都非常迅速
另外一類對象的生命週期卻非常長,在某些極端的情況下還能夠與JVM的生命週期保持一致。

  • Java堆區進一步細分的話, 可以劃分爲年輕代(YoungGen)和老年代(OldGen)
  • 其中年輕代又可以劃分爲Eden空間、Survivor0空間和Survivor1空間 (有時也叫做from區、to區)。
  • 配置新生代與老年代在堆結構的佔比。

默認-XX:NewRatio=2, 表示新生代佔1,老年代佔2,新生代佔整個堆的1/3
可以修改-XX:NewRatio=4, 表示新生代佔1,老年代佔4,新生代佔整個堆的1/5

在這裏插入圖片描述
在HotSpot中,Eden空間和另外兩個Survivor空間缺省所佔的比例是8:1:1,當然開發人員可以通過選項“-XX: SurvivorRatio"調整這個空間比例。比如-XX : SurvivorRatio=8
幾乎所有的Java對象都是在Eden區被new出來的。絕大部分的Java對象的銷燬都在新生代進行了。(IBM公司的專門研究表明,新生代中80%的對象都是“朝生夕死”的。)可以使用選項"-Xmn"設置新生代最大內存大小,這個參數一般使用默認值就可以了。

對象分配過程

對象分配一般過程

爲新對象分配內存是一件非 常嚴謹和複雜的任務,JVM的設計者們不僅需要考慮內存如何分配、在哪裏分配等問題,並且由於內存分配算法與內存回收算法密切相關,所以還需要考慮GC執行完內存回收後是否會在內存空間中產生內存碎片。

  1. new的對象先放伊甸園區。此區有大小限制。
  2. 當伊甸園的空間填滿時,程序又需要創建對象,觸發JVM的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC或者Young GC)survivor區滿的時候不會觸發,但是並不是說不會進行垃圾回收,只是說當eden區滿觸發GC時會將eden和survivor區一起進行垃圾回收,survivor區是被動的進行而已),將伊甸園區中的不再被其他對象所引用的對象進行銷燬。再加載新的對象放到伊甸園區。
  3. 然後將伊甸園中的剩餘對象移動到倖存者0區。
  4. 如果再次觸發垃圾回收,此時不僅會對eden區進行判斷,還會對上次倖存下來的放到倖存者0區的對象進行判斷,如果沒有被回收,就會放到倖存者1區。如果再次經歷垃圾回收,此時會重新放回倖存者0區,接着再去倖存者1區……(每個對象分配有一個年齡計數器,存放區換(複製)一次值就加一),S0和S1有個別名是from和to區,這裏說的是在GC過後(上一次複製之後),這兩個區中那個爲空那個就是to區另一個就是from區,表示下次GC時eden區的對象應該往哪裏放。(複製之後有交換,誰空誰是to)(Eden區的對象經過GC後總是將對象放入to區的)
  5. 啥時候能去養老區呢?可以設置次數(最大進入老年的閾值)。默認是15次。可以設置參數: -XX;MaxTenuringThreshold=進行設置。當年齡計數器大於等於這個值時,如果這個對象還沒被銷燬就會存放到老年區中了。
  6. 在養老區,相對悠閒。當養老區內存不足時,再次觸發GC: Major GC,進行養老區的內存清理
  7. 若養老區執行了Major GC之後發現依然無法進行對象的保存,就會

所謂朝生夕死產生OOM異常:java. lang. OutOfMemoryError: Java heap space 關於垃圾回收:頻繁在新生區收集,很少在養老區收集,幾乎不在永久區/元空間收集。
在這裏插入圖片描述

對象分配特殊過程

注意:每次Minor GC或者Young GC後eden區應該是空的,因爲是垃圾對象就被回收了,不是垃圾對象就被放入to區了。
如果此時再往eden區放一個超大對象發現放不下,就會直接放到老年代區,如果老年代也放不下,可能原因老年代空間不足或者就是老年代空間直接小於這個超大對象,這是會出現majorGC或fullGC,如果majorGC或fullGC後還存不下該對象,且此jvm實例不會自動調整內存空間(jvm實際上是有自動調整內存空間機制的),就會出現OOM。

同理Minor GC或者Young GC(eden區和survivor都進行了垃圾回收)後,eden區的對象在放入to區時,如果放不下時,就會把把對象放入老年代區,也有可能survivor1和survivor0在被動的進行GC時發現對象的年齡計數器大於最大進入老年區閾值時也會直接晉升老年區。
在這裏插入圖片描述
代碼演示:

//配置爲:-Xms600m -Xmx600m
public class HeapInstanceTest {
    byte[] buffer=new byte[new Random().nextInt(1024*200)];

    public static void main(String[] args){
        List<HeapInstanceTest> list=new ArrayList<>();
        while (true){
            list.add(new HeapInstanceTest());
            try {
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

在這裏插入圖片描述

GC分類

Java中Stop-The-World機制簡稱STW,是在執行垃圾收集算法時,Java應用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。Java中一種全局暫停現象,全局停頓,所有Java代碼停止,native代碼可以執行,但不能與JVM交互;這些現象多半是由於gc引起。GC時的Stop the World(STW)是大家最大的敵人。

JVM在進行GC時,並非每次都對新生代、老年代、方法區(1.7之前叫永久帶,1.8後叫元空間)一起回收的,大部分時候回收的都是指新生代(80%對象是朝生夕死)。.
針對HotSpot VM的實現,它裏面的GC按照回收區域又分爲兩大種類型:一種是部分收集(Partial GC),一種是整堆收集(Full GC)

  • 部分收集:不是完整收集整個Java堆的垃圾收集。其中又分爲:
  1. 新生代收集(Minor GC / Young GC) :只是新生代的垃圾收集
  2. 老年代收集(Major GC / 0ld GC) :只是老年代的垃圾收集。目前,只有CMS GC會單獨收集老年代的行爲。注意,很多時候Major GC會和Full GC混淆使用,需要具體分辨是老年代回收還是整堆回收。這裏如果別人提到了majorGC就可以問問他:你說的majorGC值的就是老年代的GC還是整堆的GC了
  3. 混合收集(Mixed GC): 收集整個新生代以及部分老年代的垃圾收集。目前,只有G1 GC會有這種行爲
  • 整堆收集(Fu11 GC):收集整個java堆和方法區的垃圾收集。

MinorGC

年輕代GC(Minor GC)觸發機制:

  • 當年輕代空間不足時,就會觸發Minor GC,這裏的年輕代滿指的是Eden代滿,Survivor滿不會引發GC。(每次 Minor GC會清理年輕代(eden和surviv or)的內存。)
  • 因爲Java對象大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。這一定義既清晰又易於理解。
  • Minor GC會引發STW, 暫停其它用戶的線程,等垃圾回收結束,用戶線程才恢復運行。Minor GC頻率雖然很高,但是其執行速度很快,而且年輕代空間較小,所有對用戶線程影響其實不是多大

MajorGC

老年代GC (Major GC/Fu1l GC)觸發機制:

  • 指發生在老年代的GC,對象從老年代消失時,我們說“Major GC”或“Fu1l GC”發生了。
  • 出現了Major GC,經常會伴隨至少一次的Minor GC (但非絕對的,在Parallel Scavenge收集器的收集策略裏就有直接進行Major GC的策略選擇過程)。也就是在老年代空間不足時,會先嚐試觸發Minor GC。如果之後空間還不足,則觸發Major GC
  • Major GC的速度一般會比Minor GC慢10倍以 上,STW的時間更長。老年代空間比年輕代大一些。
  • 如果Major GC後,內存還不足,就報OOM了。

FullGC

Fu11 GC觸發機制: ( 後面細講)
觸發Full GC執行的情況有如下五種:

(1) 調用System.gc()時,系統建議執行Full GC,但是不必然執行
(2) 老年代空間不足(老年代空間不足,在不GC就OOM,這其實可能是Major GC會和Full GC混淆使用情況)
(3) 方法區空間不足
(4) 通過Minor GC後進入老年代的平均大小大於老年代的可用內存
(5) 由Eden區、survivor space0 (From Space) 區向survivor space1 (To Space)區複製時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小於該對象大小,其實也就是老年代空間不足的情況而已。

說明: full gc是開發或調優中儘量要避免的。這樣暫停時間會短一些。(方法區的回收包括不用的類回收和類加載器回收等,使stw時間增長)

爲什麼需要把Java堆分代?不分代就不能正常工作了嗎?

其實不分代完全可以,分代的唯一理由就是優化GC性能。如果沒有分代,那所有的對象都在一塊,就如同把一個學校的人都關在一個教室。GC的時候要找到哪些對象沒用這樣就會對堆的所有區域進行掃描。而很多對象都是朝生夕死的,如果分代的話,把新創建的對象放到某一地方, 當GC的時候先把這塊存儲“朝生夕死”對象的區域進行回收,這樣就會騰出很大的空間出來

TLAB

TLAB:( Thread Local Allocation Buffer ) 從內存模型而不是垃圾收集的角度,對Eden區域繼續進行劃分,JVM爲每個線程分配了一個私有緩存區域,它包含在Eden空間內

  • 多線程同時分配內存時,使用TLAB可以避免一系列的非線程安全問題,同時還能夠提升內存分配的吞吐量,因此我們可以將這種內存分配方式稱之爲快速分配策略。據瞭解貌似所有openJDK衍生出來的JVM都提供了TLAB的設計。
    在這裏插入圖片描述
  • 儘管不是所有的對象實例都能夠在TLABR成功分配內存,但**JVM確實是將TLAB作爲內存分配的首選。**在程序中,開發人員可以通過選項“-XX:UseTLAB"設置是否開啓TLAB空間,默認是開啓。
  • 默認情況下,TLAB空間的內存非常小,僅佔有整個Eden空間的1%,當然我們可以通過選項“-XX: TLABWasteTargetPercent”設置TLAB空間所佔用Eden空間的百分比大小。
  • 一旦對象在TLAB空間分配內存失敗時,JVM就會嘗試着通過使用加鎖機制確保數據操作的原子性,從而直接在Eden空間中分配內存。

爲什麼有TLAB ( Thread Local Allocation Buffer ) ?
區是線程共享區域,任何線程都可以訪問到堆區中的共享數據
●由於對象實例的創建在JVM中非常頻繁,因此在併發環境下從堆區中劃分內存空間是線程不安全
●爲避免多個線程操作同一地址,需要使用加鎖等機制,但是進而影響分配速度。

在這裏插入圖片描述
問:堆都是共享的嗎 ?不是,TLAB是線程私有的。

堆空間的參數設置

官網說明:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
-XX: +PrintFlagsInitial : 查看所有的參數的默認初始值I
-XX: +PrintFlagsFinal : 查看所有的參數的最終值(可能會存在修改,不再是初始值)
-Xms:初始堆空間內存( 默認爲物理內存的1/64)
-Xmx:最大堆空間內存(默認爲物理內存的1/4)
-Xmn:設置新生代的大小。(初始值及最大值)
-XX: NewRatio:配置新生代與老年代在堆結構的佔比
-XX:SurvivorRatio:設置新生代中Eden和s0/s1空間的比例
-XX:MaxTenuringThreshold:設置新生代垃圾的最大年齡
-XX:+PrintGCDetails:輸出詳細的GC處理日誌
打印Igc簡要信息:①-Xx:+PrintGC ②- verbose:gc
-XX:HandlePromotionFailure:是否設置空間分配擔保

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