文章目錄
1. 簡述
1. 堆是jvm管理的一塊最大內存空間,且一個jvm只存在一個堆內存
2. 堆空間被所有的線程共享,同時也可以劃分線程私有的緩衝區(Thread Local Allocation Buffer,TLAB)
3. 幾乎所有的對象都在堆空間上分配
4. 方法執行時,棧幀中保存對象引用,對象在堆空間上分配,方法結束後,局部變量被銷燬,
堆上的對象不會立即銷燬,僅在垃圾收集的時候纔會被移除
5. 堆是GC(Garbage Collection)的重點區域
堆空間分類
- 新生區 Young Generation Space
- Eden區
- Survivor區(from和to兩個)
-
養老區 Old Generation Space
-
元空間 Meta Space
元空間是方法區的一個實現,放在方法區講解,jdk1.7以前稱爲永久代
2. 堆空間的設置
- java堆空間的大小,在jvm啓動時設置,默認大小
初始內存大小:物理內存/64
最大內存大小:物理內存/4
- 設置堆內存大小
初始內存大小:-Xms 等價於-XX:InitialHeapSize
最大內存大小: -Xmx 等價於MaxHeapSize
注: -Xms和-Xmx設置爲同一個值,垃圾回收器清理完堆區以後不用重新分隔計算堆區大小,提升效率
- 當堆區內存超過-Xmx設定的值時,將會拋出OutOfMemoryError:Java heap space異常
3. 年輕代與老年代
-
java堆區劃分爲新生代和老年代,新生代又可以分爲Eden區和Survivor0區和Survivow1區(from和to區)
-
年輕代與老年代的空間比例
規則如下,一般不用改,使用默認即可
可以通過-XX:NewRatio來設置老年代與新生代的比例
默認-XX:NewRatio=2表示老年代:新生代=2:1,新生代佔1/3,老年代2/3
設置-XX:NewRatio=4表示老年代:新生代=4:1,新生代佔1/5,老年代佔4/5
- 年輕代內部空間比例
Eden和兩個Survivor區的比例默認是8:1:1
可以通過-XX:SurvivorRatio=8,表示Eden:Survivor=8:1,Surivivor有兩個8:1:1
- 分代切換
1. 幾乎所有的對象都在Eden被創建
2. GC時,Eden剩餘對象被轉移到Survivor區
3. 每經歷過一次GC對象的年齡加1,當年齡爲15時,對象被放到老年代
4. 對象的分配過程
- 分配步驟
1. new的對象先放伊甸園區,此區有大小限制
2. 當伊甸園區的空間填滿時,程序又需要創建對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC),
將伊甸園區中的不再被其他對象所引用的對象進行銷燬。再加載新的對象放到伊甸園區
3. 然後將伊甸園區中的剩餘對象移動到倖存者0區
4. 如果再次觸發垃圾回收,此時上次倖存下來的放到倖存者0區的,如果沒有被回收,就會放到倖存者1區,對象年齡加1
5. 如果再次經歷垃圾回收,此時會重新放回倖存者0區,接着再去1區,每次交換對象年齡加1
6. 當年齡達到15時,將對象放入老年代
(臨界年齡可指定 -XX:MaxTenuringThreshold=<N>)
7. 當養老區空間不足時,會觸發Major GC,進行養老區內存清理
8. 若養老區執行了Major GC之後依然無法進行對象的保存,就會產生OOM異常
(java.lang.OutOfMemoryError:Java heap space)
總結
1. 針對倖存者s0,s1區的總結,複製之後有交換,誰空誰是to
2. 關於垃圾回收,頻繁發生在新生代,很少發生在老年代,幾乎不在永久區/元空間收集
5. Minor GC/ Major GC/ Full GC
- 簡述
JVM在GC時不會每次都對新生代、老年代、方法區三個區域一起回收,HotSpot VM按照回收區域分爲部分收集(Partial GC)和整堆收集(Full GC)
- 部分收集
1. 新生代收集(Minor GC/ Young GC),只是新生代(Eden/s0/s1)收集
2. 老年代收集(Major GC/ Old GC),只是老年代收集
(HotSpot VM 沒有單獨堆老年代收集的行爲,Full GC會觸發Major GC)
- 整堆收集
收集整個java堆和方法區的垃圾收集
- 年輕代GC(Minor GC)的觸發機制
1. 當年輕代空間不足時,會觸發Minor GC,這裏的年輕代指的是Eden,Survivor滿不會觸發GC,但是Minor GC會收集Survivor區
2. 由於對象朝生夕滅的特性,所以Minor GC會很頻繁,回收速度也比較快
3. Minor GC會引發STW(stop the word),暫停其它用戶線程,等垃圾回收結束,用戶線程才恢復運行
- 老年代GC(Major/ Full GC)觸發機制
注:HotSpot虛擬機沒有Major GC
1. 當老年代空間不足時,觸發Major GC,但是HotSpot VM沒有針對這個區的單獨回收機制
2. Major GC的速度一般會比Minor GC慢10倍以上,STW的時間更長
3. Major GC後內存還不足,就報OOM
- Full GC觸發機制
1. 調用System.gc(),系統建議執行Full Gc,但不是必然執行
2. 老年代空間不足
3. 方法區空間不足
4. 通過Minor GC後進入老年代的平均大小大於老年代的可用內存
5. 由Eden區,s0區向s1區複製時,對象大小大於s1(to)區可用內存,則把對象轉到老年代,且老年代的可用內存小於該對象大小
6. 堆空間分代思想
分代的唯一理由就是優化GC性能,如果沒有分代那所有的對象都在一塊,GC的時候要找到哪些對象沒用,這樣就會對堆的所有區域進行掃描,效率較低。如果分代的話,把新創建的對象放在某個地方,當GC的時候先把這塊“朝生夕死”對象的區域進行回收,就能騰出很大的空間出來。
7. 對象提升規則
-
正常通道
如果對象在Eden出生過第一次MinorGC後仍然存活,並且能被Survivor容納,將被移動到Survivor空間中,並將對象年齡設爲1,對象在Survivor區中每熬過一次MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(默認15歲),就會晉升到老年代中
晉升的年齡閾值可以通過–XX:MaxTenuringThreshold來設置 -
特殊通道
1. 對象優先分配到Eden
2. 大對象直接分配到老年代,Eden放不小的對象
3. Surivivor放不小的對象,直接進入老年代
4. 動態對象年齡判斷
如果Survivor區中相同年齡的所有對象大小總和大於Survivor空間的一半,
年齡大於或等於該年齡的對象直接進入老年代,無需等待MaxTenuringThreshold中要求的年齡
8. TLAB(Thread Local Allocation Buffer)
- 爲什麼有TLAB
1. 堆區是線程共享區域,任何線程都可以訪問到堆區中的共享數據
2. 併發環境下從堆區中劃分內存空間是線程不安全的
3. 爲避免多個線程操作同一地址,需要使用加鎖等機制,會影響分配速度
- 什麼是TLAB
1. TLAB是Eden的一個部分,這個部分是線程私有的,每個線程都有自己獨立的TLAB
2. TLAB是線程私有的,可以提高內存分配的吞吐量,這種方式稱爲快速分配策略
3. TLAB是jvm內存分配的首選,但是不是所有的對象實例都能夠在TLAB中成功分配(TLAB內存大小有限)
4. 通過-XX:UserTLAB設置是否開啓TLAB空間,默認開啓
5. 默認情況TLAB內存很小,佔Eden空間的1%,可以通過-XX:TLABWasterTargetPercent設置TLAB所佔用Eden空間的大小
6. TLAB分配內存失敗時,JVM會嘗試通過加鎖機制,從Eden空間中分配內存
9. 常用參數總結
- 常用配置
1. 測試堆空間常用的jvm參數:
-XX:+PrintFlagsInitial : 查看所有的參數的默認初始值
-XX:+PrintFlagsFinal :查看所有的參數的最終值(可能會存在修改,不再是初始值)
具體查看某個參數的指令: jps:查看當前運行中的進程
jinfo -flag SurvivorRatio 進程id
2. -Xms:初始堆空間內存 (默認爲物理內存的1/64)
3. -Xmx:最大堆空間內存(默認爲物理內存的1/4)
4. -Xmn:設置新生代的大小。(初始值及最大值)
5. -XX:NewRatio:配置新生代與老年代在堆結構的佔比
6. -XX:SurvivorRatio:設置新生代中Eden和S0/S1空間的比例
7. -XX:MaxTenuringThreshold:設置新生代垃圾的最大年齡
8. -XX:+PrintGCDetails:輸出詳細的GC處理日誌
打印gc簡要信息:① -XX:+PrintGC ② -verbose:gc
9. -XX:HandlePromotionFailure:是否設置空間分配擔保
- 空間擔保
如果老年代最大可用的連續空間大於新生代所有對象的總空間
或者老年代最大可用連續空間大於歷次晉升到老年代的對象的平均大小進行MinorGC
否則進行FullGC
10. 堆是分配對象的唯一選擇嗎
- 背景
在《深入理解Java虛擬機》關於Java堆內存有這樣一段描述:隨着JIT編譯期的發展與逃逸分析技術逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化,所有的對象都分配到堆上也漸漸變得不那麼絕對了
如果經過逃逸分析(Escape Analysis)後發現,一個對象並沒有逃逸出方法的話,那麼就可能被優化成棧上分配
- 逃逸分析
1. 當一個對象在方法中被定義,只在方法內部使用,則沒有發生逃逸
2. 當一個對象在方法中被定義,被外部方法引用,則發生逃逸
- 代碼優化
-
棧上分配(暫不支持)
方法中的對象經過逃逸分析,不會逃逸則對象可以在棧上分配 -
同步省略(暫不支持)
經過逃逸分析,方法內的鎖不發生逃逸,則可以不考慮同步
-
分離對象或標量替換(支持)
經過逃逸分析,不發生逃逸的對象,可以被分解爲若干個成員變量代替
經過標量替換以後
4. 結論
逃逸分析技術尚不成熟,目前的對象還都是分配在堆空間上