Java 內存分區
- 本地方法棧:native方法調用時的方法調用棧,存儲本地棧幀
- 虛擬機方法棧:Java的棧,每一個線程有一個線程調用棧,棧的元素是棧幀。棧幀包括:局部變量表、操做數棧、指向堆中對象的引用、返回地址、附加信息。每一個方法調用時,迴向當前指向的線程棧頂部壓入一個棧幀,棧幀的大小是固定的,虛擬機經過解析.class文件能夠得知。
- 堆:堆是全部Java線程共享的一個內存區域,用於分配對象。
- 方法區:存儲類的信息(類名、方法信息、字段信息)、靜態變量、常量池。
- 程序計數器:用於記錄下一條要執行的指令,每一個線程都有本身的程序計數器,配合線程棧用於在線程調度時的線程上下文切換。執行本地方法時程序計數器中沒有值或爲undefined。
HotSpot堆內存
1.8以前,在HotSpot中堆內存分爲新生代、老年代、永久代。永久代就是方法區的實現,佔用一部分堆內存,但永久代不參與垃圾回收。
1.8及置換,HotSpot將堆內存分爲新生代、老年代。用元數據區實現方法區,且元數據區佔用堆外內存。java
常見的GC算法
- 引用計數:每一個對象引用被持有時,引用計數+1。引用賦值爲null或銷燬時引用計數-1。引用計數爲0說明沒有再被使用,能夠回收。沒法解決循環引用問題。
- 標記-清除算法:第一個階段標記,將存活的對象標記出來;第二個階段是清除,將死亡對象佔用的內存回收利用。
- 標記-整理算法:第一個節點標記,標記處存貨的對象;第二個階段整理,將存活的對象整理放到另外一處空間中去,而後把當前空間的內存所有回收。
- 複製算法:每次只利用內存的一半,當內存滿時,將存貨對象複製到另外一半中去,這一半內存所有回收。
垃圾回收算法並無最好的一種,爲了達到下降總體GC時間的目的,通常是採用對內存分代進行垃圾回收,每一個代採用不一樣的回收算法。算法
HotSpot堆內存分代
- 新生代
新生代與老年代內存佔比默認爲1:2。
新生代又可分爲Eden區、Survivor0、Survivor1區,默認佔用新生代內存比例爲8:1:1。
- 老年代
老年代是一大塊連續內存,沒有再細分。
HotSpotGC類型
- MinorGC,也叫作Young GC,YGC,
是發生在新生代的GC,當Eden區滿時觸發,HotSpot中新生代均GC採用複製算法實現。
新分配的對象通常是在Eden區,除非知足條件時,對象會直接分配到堆上。Eden區滿以後會將Eden存活對象拷貝到survivor0區。當survivor0內存不足時,將survivor0中存活的對象拷貝到survivor1區,並回收survivor0區,而後交換survivor0和survivor1的名稱。通常也叫from和to。
每次從from到to的拷貝,都會記錄對象的年齡+1,當對象年齡達到一個設定的值後(默認15),對象會被分配到老年代。
若是從from向to的拷貝過程當中發現to內存不夠用,也會將對象轉存到老年代。
直接老年代分配內存的狀況:shell
- 對象所需內存大小超過Eden區大小,全部收集器都支持。
- Eden區內存不足,且對象所需內存大小超過Eden區一半,直接分配到老年代,且不觸發MinorGC。使用ParallelScavenge時支持。
- 對象大小超過XX:PretenureSizeThreshold設置時,直接進入老年代分配。Serial、ParNew、CMS收集器支持。
FullGC
FullGC是發生在老年代的GC,觸發時機有:數組
- 調用System.gc()時,系統會建議執行GC,但不是當即執行。
- 重新生代晉升到老年代的對象所需內存大於老年代可用內存時。
GC吞吐量 = 用戶代碼執行時間 / (用戶代碼執行時間 + GC時間)多線程
HotSpot垃圾收集器
jdk1.7和1.8默認採用ParallelGC,也就是Parallel Scavenge(新生代)+Parallel Old(老年代)GC。
jdk9採用G1垃圾收集器。併發
其餘的垃圾收集器還有CMS、ParaNew、Serial等
查看java默認垃圾收集器命令:工具
java -XX:+PrintCommandLineFlags -version
-XX:+UseParallelGC 表示使用Parallel Scavenge + Parallel Old
-XX:+UseConcMarkSweepGC 在啓動應用加上這個參數表示使用CMS垃圾收集器
-XX:+UseG1GC 在啓動應用時添加這個參數表示使用G1垃圾收集器,jdk8中支持開啓
Serial、Serial Old
使用單線程進行垃圾回收,GC時中止用於線程工做。單CPU環境下表現好,由於沒有GC線程間的交互。
Serial用於新生代,複製算法。Serial Old用於老年代,基於標記-整理oop
ParNew
Serial的多線程版本,採用複製算法。在單CPU時不如Serial,多CPU效果較好。
ParNew用於新生代。線程
Parallel Scavenge
Parallel Scavenge採用複製算法。算法目的是提升吞吐量,下降GC時間(但可能提高GC次數),讓用戶代碼獲得更多執行時機。
Parallel表明並行,即多個GC線程同時進行,但仍是會暫停用戶線程,在GC完成後用戶線程才繼續執行。
相比較於ParNew收集器,能夠添加啓動參數XX+UseAdaptiveSizePolicy,添加參數後能夠不用設置Eden和Survivor區比例,也不用設置晉升老年代對象年齡等細節參數,該算法會自動根據系統運行狀況動態調節參數。code
Parallel Old
Parallel Old是Parallel Scavenge的老年代版本,採用標記-整理算法。能夠併發GC,整個GC過程是暫停用戶線程執行的。
CMS垃圾收集器
CMS全名Concurrent Mark Sweep,併發標記-回收垃圾收集器。是老年代的GC處理器。
CMS分爲四個步驟:
- 初始標記:僅標記GCRoots和新生代可以直接引用到的老年代對象,速度較快,觸發STW。
- 併發標記:此階段GC標記線程與用戶線程同時執行,遍歷初始標記的對象,並遞歸標記這些對象引用的所有對象,這個過程比較慢,可是不觸發STW。
由於這個階段是併發標記,可能發生 對象重新生代晉升到老年代、直接在老年代分配對象、老年代引用對象關係發生變化、新生代對老年代對象引用發生變化 等現象,對於這些對象都是要從新標記的。
作法是將這些對象所在的Card Table中的位設置爲dirty,把併發標記階段新產生的對象和對象引用關係的變化記錄下來(記錄到Mod-Union Table)。Card Table:將老年代內存分爲相等大小的CardPage,每一個CardPage用一個二進制位表示其內部的對象在併發標記階段是否發生了引用變化,這個二進制位數組就是CardTable。
Mod-Union Table:是一個和CardTable相似的結構;
- 併發預清理:從Mod-Union Table中找到併發標記階段標記爲dirty的內存區域,從新標記這些引用關係發生過變化的對象。能夠經過參數CMSPrecleaningEnabled來關閉這個階段,默認開啓的。
併發可中斷預清理:循環處理From和To區對象,標記可達的老年代對象;而且循環處理DirtyCard的標記,不觸發STW。循環的退出條件有三種
- 循環次數超過CMSMaxAbortablePrecleanLoops設置,默認0,沒有次數限制;
- 循環時間超過CMSMaxAbortablePrecleanTime設置,默認5s,超過會退出;
- 併發預清理時Eden使用率低於10%,而某一次循環後Eden使用率達到CMSScheduleRemarkEdenPenetration(默認50%)後會退出;
這個階段循環執行的目的是儘可能減少下一個從新標記階段須要處理的新生代對象引用老年代對象的狀況,由於下一個階段會觸發STW,爲提升吞吐量天然是dirty狀態的內存越少越好。若是恰好在這個循環執行的5秒內觸發了YGC,而後這個階段又併發了處理了dirty的引用,則下個節點須要從新標記的對象就沒那麼大,STW時間也就短了。
上一個併發預清理節點能夠不要的緣由就是這一步其實也能達到預清理的效果,並且是循環操做的。
從新標記:較慢,CMS的瓶頸點,觸發STW,併發從新標記。雖然Preclean和AbortablePreclean已經儘可能處理了DirtyCard,可是不能保證徹底處理掉,所以還須要從新標記,要進行以下處理:
- 遍歷新生代對象,從新標記;
這個節點要遍歷新生代對象,若是新生代使用率很高,對象不少的話會耗時好久去遍歷,所以若是在進行從新標記以前觸發了YGC,這個步驟的耗時會減小不少。CMS能夠經過參數CMSScavengeBeforeRemark來強制在從新標記階段以前進行一次YGC,該配置默認關閉。開啓的話可能會出現連續的兩次YGC,也挺浪費時間。
進行一次YGC以後並非要把DirtyCard交給從新標記階段的5.3執行,而是交給4可中斷預處理來執行。
- 遍歷GCRoots,從新標記;
- 遍歷DirtyCard,從新標記,這時候通過前兩個階段的處理,DirtyCard已經減小了不少。
- 併發清除:根據標記結果清除垃圾對象,不觸發STW,速度較慢。
- 併發重置:重置CMS內部的數據,如CardTable、Mod-Uniod Table等,爲下一次GC作準備,不觸發STW。
CMS的兩個標記階段,都會觸發STW,不同之處在於,初始標記僅標記GCRoots和新生代可達的老年代對象,沒有再遞歸遍歷標記,較快完成;而從新標記階段,標記GCRoots、新生代可達、DirtyCard中的老年代對象,且遞歸遍歷標記,比較耗時。
本文由博客羣發一文多發等運營工具平臺
OpenWrite 發佈