本篇文章介紹,對象的內存分配也就是在堆上分配,在堆上是如何進行分配的以及分代策略
普遍的內存分配規則
- 在堆上分配(但也可能通過JIT編譯後被拆散爲標量類型並簡介的棧上分配)
- 對象主要分配在新生代的Eden區上
- 如果啓動了本地線程分配緩存,將按線程優先在TLAB上分配
- 少數情況下也可能會直接分配在年老代
內存分配的規則不是固定的,影響內存分配的規則和細節的因素有
- 垃圾回收器組合
- 虛擬機中的內存相關的參數的設置
以下基於Seril/Serial Old 收集器下的內存分配規則
對象優先在Eden分配
年輕代分爲三部分:
- 1個Eden區
- 2個大小相同的Survivor區(倖存者區,兩個分別叫from和to),默認比例8:1:1,這個比例可以通過參數進行設置
由來
- 年輕代中有兩個Survivor區的原因在於年輕代的垃圾回收算法使用的是"複製算法"
新生代中的對象98%是"朝生夕死"的,所以並不需要按照1:1的比例來劃分內存空間,而是將內存空間分爲一塊較大的Eden空間和兩塊較小的Surviror 空間,每次使用Eden和其中一塊Surviror, - 當回收時,將 Eden 和 Survivor 中還存活着的對象一次性的複製到另一塊Survivor 空間上,最後清理掉Eden和剛纔用過的Survivor空間
- 默認Eden和Survivor的大小比例爲8:1,也就是每次新生代中可用內存空間爲整個新生代容量的90%(80%+10%),只有10%的內存會被浪費
- 如果Survivor 空間不夠用時需要依賴其它內存(老年代)進行分配擔保
Minor GC 與 Full GC 區別
- 新生代GC(Minor GC):指發生在新生代的垃圾收集動作,因爲java對象大多都是具備朝生夕死的特徵,所以Minor GC非常頻繁,一般回收速度也比較快
- 年老代GC (Major GC /Full GC ):指發生在年老代的GC,出現了Maior GC 通常會伴隨至少一次Minor GC (但非絕對),Major GC 的速度一般會比Minor GC慢10倍以上
大對象直接進入老年代
大對象:是指需要大量連續內存空間的java對象,最典型的大對象就是很長的字符串和數組
經常出現大對象容易導致內存還有不少空間時就提前觸發垃圾收集以獲取足夠的連續空間來"安置"他們
直接進入老年代的原因:
目的是避免在Eden區及兩個Survivor 區之間發生大量的內存複製
有些垃圾收集器提供了一個 -XX:PretenureSizeThreshold參數,大於這個設置值的對象直接在老年代分配
長期存活的對象將進入老年代
年齡計數器
- 如果對象在Eden出生並經過第一次Minor GC 後仍然存活,並且被Survivor 容納的話,將被移動到Survivor空間中,並且對象年齡設爲1
- 對象在Survivor 區中每熬過一次Minor GC,年齡就會增加1歲
- 當它的年齡增加到一定程度(默認爲15歲),就將會被普生到年老代中
- 對象普升老年代的年齡閥值,可以通過參數-XX:MaxTenuringThreshold 設置
動態對象年齡判定
- 虛擬機並不是永遠的要求對象的年齡必須達到了MaxTenuringThreshol才能普升老年代
- 如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年紀大於或等於該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中設置的年齡
年代轉移過程
- GC開始的時候,對象只會存在於Eden區和名爲From的Survivor區,Survivor區的To區是空的,
- Eden 區的空間耗盡了,觸發Minor GC,Eden和from 中所有存活的對象都會被複制到To區
- 而在From區中仍然存活的對象會根據他們的年齡來決定他們的取向,是去年老代還是去To區域
- 經過這次Minor GC 後Eden區和From區已經被清空,下次From 和 To區會交換角色使用,以便確保To區是空的
- 年輕代普升到年老代另一個情況是:不一定達到年齡才能普升,如果在Survivor空間中相同年齡所有對象的大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入年老代