文章目錄
Pre
對象分配流程總覽
流程分解
棧上分配對象 (逃逸分析)
衆所周知, JAVA中的對象都是在堆上進行分配,當對象沒有被引用的時候,需要GC。
如果對象數量較多的時候, GC 壓力較大,也間接影響了應用的性能 。
爲了減少臨時對象在堆內分配的數量,JVM通過逃逸分析確定該對象不會被外部訪問 . 如果不會逃逸可以將該對象在棧上分配內存,這樣該對象所佔用的內存空間就可以隨棧幀出棧而銷燬,從而減輕GC的壓力。
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
我們來解釋一下爲什麼會這個樣子哈:
- 分配allocation1 對象的時候 ,已經把Eden區域佔滿了, 100%
- 當再次給allocation2分配內存空間的時候,需要5M的空間,但eden區間 100%了,沒有足夠的空間了 ,所以jvm發生了一次 young gc .
- GC期間,發下 allocation1 ,無法放入 Survior空間 , 所以提前將這個大對象存入到 老年代
- 老年代上的空間足夠存放allocation1,所以不會出現Full GC
那在分配對象呢?
可以知道
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