JVM-剖析对象内存分配流程
文章目錄
- Pre
- 對象分配流程總覽
- 流程分解
- 棧上分配對象 (逃逸分析)
- Eden區分配對象
- -XX:+UseAdaptiveSizePolicy 默認開啟
- Eden區域分配對象Demo
- 大對象直接進入老年代
- 什么是大對象?
- 如何定義 “大”?
- 作用的垃圾收集器
- 好處
- 經驗之談
- 長期存活的對象將進入老年代
- 概述
- jvm參數 -XX:MaxTenuringThreshold
- 經驗之談
- 對象動態年齡判斷
- 對象動態年齡概述
- 對象動態年齡引起的頻繁Full GC及優化
- 老年代空間分配擔保機制
Pre
JVM-09自動內存管理機制【內存分配和回收策略】
對象分配流程總覽
流程分解
棧上分配對象 (逃逸分析)
眾所周知, JAVA中的對象都是在堆上進行分配,當對象沒有被引用的時候,需要GC。
如果對象數量較多的時候, GC 壓力較大,也間接影響了應用的性能 。
為了減少臨時對象在堆內分配的數量,JVM通過逃逸分析確定該對象不會被外部訪問 . 如果不會逃逸可以將該對象在棧上分配內存,這樣該對象所占用的內存空間就可以隨棧幀出棧而銷毀,從而減輕GC的壓力。
JVM-徹底搞懂 逃逸分析&標量替換
Eden區分配對象
絕大部分情況,新生的對象都是存放在Eden區域。 當Eden區域沒有足夠的空間來給新生對象進行內存分配的時候,JVM會進行一次Minor GC . (Minor GC 也會STW,只不過速度非常快)
我們常看到的 Minor GC / Young GC 可以理解為是等價的。
Major GC / Full GC 也可以理解為是等價的。
-
Young GC : 回收新生代區域,速度快
-
Full GC : 回收 老年代、新生代 和 方法區的垃圾 ,速度較慢。
新生代:
Eden : Survivor (S1 + S2) = 8:1:1
大部分對象是朝生夕死的,沒有死亡的對象被回收存放到S1 區域,所以 8:1:1 合適,其目的就是為了讓eden區盡量的大,同時survivor區夠用即可。
-XX:+UseAdaptiveSizePolicy 默認開啟
JVM默認有這個參數-XX:+UseAdaptiveSizePolicy(默認開啟),會導致這個8:1:1比例自動變化 。
禁用: -XX:-UseAdaptiveSizePolicy 自動變化。 不推薦修改。
Eden區域分配對象Demo
不設置具體的Xms Xmx 時 ,JVM會自動根據你電腦的內存,設置一個值。
可以看到,JVM給新生代自動根據你的電腦配置了47M 左右的內存。
程序運行后,我們分配了new byte[36 * _1M] 36 M的對象 。
total 47616K, used 40960K
eden space 40960K, 100% used
40960K, 100% used ? JVM本身內部的對象也要占用內存空間,不僅僅是你應用分配的對象。
這個時候Eden區域已經被應用的對象占滿了。
再分配個 5M
我們來解釋一下為什么會這個樣子哈:
那在分配對象呢?
可以知道
Minor GC后,新分配的對象如果eden區足夠的話,還是會在eden區分配內存。
大對象直接進入老年代
什么是大對象?
大對象就是需要大量連續內存空間的對象 ,比如一個大的字符串,一個大的數組等等 都可以稱之為大對象
如何定義 “大”?
JVM通過 -XX:PretenureSizeThreshold 設置大對象的大小 。
如果對象超過設置大小會直接進入老年代,
比如設置JVM參數:-XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC ,【 單位是字節】
設置1M以上的對象為大對象
測試一下
-XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC -XX:+PrintGCDetails作用的垃圾收集器
-XX:PretenureSizeThreshold 只在 Serial 和ParNew兩個收集器下有效。
好處
這樣做的好處是什么呢? ----> 避免為大對象分配內存時的復制操作而降低效率 .
經驗之談
如果你能確保你的應用中有大對象,可以直接通過該參數讓它直接接入老年代,避免大量的GC
長期存活的對象將進入老年代
概述
我們知道采用了分代收集的思想來管理內存,那么內存回收時就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。為了做到這一點,虛擬機給每個對象一個對象年齡(Age)計數器。
如果對象在 Eden 出生并經過第一次 Minor GC 后仍然能夠存活,并且能被 Survivor 容納的話,將被移動到 Survivor 空間中,并將對象年齡設為1。
對象在 Survivor 中每熬過一次 MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲,CMS收集器默認6歲,不同的垃圾收集器會略微有點不同),就會被晉升到老年代中。
jvm參數 -XX:MaxTenuringThreshold
對象晉升到老年代的年齡閾值,可以通過參數 -XX:MaxTenuringThreshold 來設置。
經驗之談
可以設置較短的年齡,盡快讓大對象到老年代中去。
對象動態年齡判斷
對象動態年齡概述
當前放對象的Survivor區域里(S1 S2中的一塊區域,放對象的那塊s區),一批對象的總大小大于這塊Survivor區域內存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此時大于等于這批對象年齡最大值的對象,就可以直接進入老年代了 。
例如Survivor區域里現在有一批對象,年齡1+年齡2+年齡n的多個年齡對象總和超過了Survivor區域的50%,此時就會把年齡n(含)以上的對象都放入老年代。
這樣做的目的就是希望那些可能是長期存活的對象,盡早進入老年代。
對象動態年齡判斷機制一般是在minor gc之后觸發。
對象動態年齡引起的頻繁Full GC及優化
舉個例子哈 關于這個知識點的
選擇方式二優化
我們來分析一下通過增加新生代的是如何避免FullGC的
每秒60M的對象----> Eden 區域 1.6G ,需要約25秒占滿Eden —> 滿了就得minor GC -----> 前24秒的對象(24*60M)約1.5G對象,已經是垃圾對象了,肯定可以被回收,只有之后一秒產生的60M對象無法被回收【應用還在跑著,還有引用】 ------> 將60M 嘗試放入 S0區域 ----> 60 < 200的50% ------> 可以存放下去,那放入S0 ----->等下個周期 又有60M的無法回收的對象進來 ,放到S1區域, 上個剛才的60M對象已經執行結束了,已經是垃圾對象了,在S0區域可以回收了,這時候連同24秒產生的對象和這個第一次產生的60M對象一起被回收掉。
如此循環往復,老年代根本就不會有對象寫入,基本避免了Full GC .
老年代空間分配擔保機制
年輕代每次minor gc之前JVM都會計算下老年代剩余可用空間。
如果這個可用空間小于年輕代里現有的所有對象大小之和(包括垃圾對象) ,就會檢查 -XX:-HandlePromotionFailure 【jdk1.8默設置】 參數是否設置了。
如果設置,就會看看老年代的可用內存大小,是否大于之前每一次minor gc后進入老年代的對象的平均大小。
如果上一步結果是小于或者之前說的參數沒有設置,那么就會觸發一次Full gc,對老年代和年輕代一起回收一次垃圾,如果回收完還是沒有足夠空間存放新的對象就會發生OOM
當然,如果minor gc之后剩余存活的需要挪動到老年代的對象大小還是大于老年代可用空間,那么也會觸發full gc,full gc完之后如果還是沒有空間放minor gc之后的存活對象,則也會發生OOM
總結
以上是生活随笔為你收集整理的JVM-剖析对象内存分配流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM - 剖析Java对象头Objec
- 下一篇: JVM - 再聊GC垃圾收集算法及垃圾收