[JAVA]JAVA章5 JVM內存模型、GC、老年代、新生代一次聊個夠

 1、JVM內存管理javascript

  一張圖看看:css

 

2、 五大內存區域

1 程序計數器

程序計數器是一塊很小的內存空間,它是線程私有的,能夠認做爲當前線程的行號指示器。

2 Java棧(虛擬機棧)

同計數器也爲線程私有,生命週期與相同,就是咱們平時說的棧,棧描述的是Java方法執行的內存模型。java

每一個方法被執行的時候都會建立一個棧幀用於存儲局部變量表,操做棧,動態連接,方法出口等信息。每個方法被調用的過程就對應一個棧幀在虛擬機棧中從入棧到出棧的過程。python

3 本地方法棧

本地方法棧是與虛擬機棧發揮的做用十分類似,區別是虛擬機棧執行的是Java方法(也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的native方法服務,可能底層調用的c或者c++,咱們打開jdk安裝目錄能夠看到也有不少用c編寫的文件,可能就是native方法所調用的c代碼。c++

4 堆

對於大多數應用來講,堆是java虛擬機管理內存最大的一塊內存區域,由於堆存放的對象是線程共享的,因此多線程的時候也須要同步機制。算法

堆是 JVM 所管理的最大的一塊內存空間,主要用於存放各類類的實例對象。 在 Java 中,堆被劃分紅兩個不一樣的區域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被劃分爲三個區域:Eden、From Survivor、To Survivor。   這樣劃分的目的是爲了使 JVM 可以更好的管理堆內存中的對象,包括內存的分配以及回收;數組

5 方法區

方法區同堆同樣,是全部線程共享的內存區域,爲了區分堆,又被稱爲非堆。ruby

用於存儲已被虛擬機加載的類信息、常量、靜態變量,如static修飾的變量加載類的時候就被加載到方法區中。老版jdk,方法區也被稱爲永久代【由於沒有強制要求方法區必須實現垃圾回收,HotSpot虛擬機以永久代來實現方法區,從而JVM的垃圾收集器能夠像管理堆區同樣管理這部分區域,從而不須要專門爲這部分設計垃圾回收機制。不過自從JDK7以後,Hotspot虛擬機便將運行時常量池從永久代移除了。】jdk8真正開始廢棄永久代,而使用元空間(Metaspace)bash

 
在上面介紹的五個內存區域中,有3個是不須要進行垃圾回收的:本地方法棧、程序計數器、虛擬機棧。由於他們的生命週期是和線程同步的,隨着線程的銷燬,他們佔用的內存會自動釋放。因此,只有方法區和堆區須要進行垃圾回收,回收的對象就是那些不存在任何引用的對象

 

3、GC

1 垃圾收集算法


新生代採用複製算法
老年代採用或1.標記/清除算法【最基礎】 2.複製算法 3.標記/整理算法 附:
jvm採用`分代收集算法`對不一樣區域採用不一樣的回收算法。
標記/清除算法標記/整理算法

2 垃圾收集器



年輕代收集器 Serial、ParNew、Parallel Scavenge 老年代收集器 Serial Old、Parallel Old、CMS收集器 特殊收集器 G1收集器[新型,不在年輕、老年代範疇內]

2.1.新生代收集器

2.1.1 Serial

最基本、發展最久的收集器,在jdk3之前是gc收集器的惟一選擇,Serial是單線程收集器,Serial收集器只能使用一條線程進行收集工做,在收集的時候必須得停掉其它線程,等待收集工做完成其它線程才能夠繼續工做。多線程

2.1.2 ParNew收集器

能夠認爲是Serial的升級版,由於它支持多線程[GC線程],並且收集算法、Stop The World、回收策略和Serial同樣,就是能夠有多個GC線程併發運行,它是HotSpot第一個真正意義實現併發的收集器。默認開啓線程數和當前cpu數量相同【幾核就是幾個,超線程cpu的話就不清楚了 - -】,若是cpu核數不少不想用那麼多,能夠經過-XX:ParallelGCThreads來控制垃圾收集線程的數量。

2.1.3 Parallel Scavenge

採用複製算法的收集器,和ParNew同樣支持多線程。

可是該收集器重點關心的是吞吐量【吞吐量 = 代碼運行時間 / (代碼運行時間 + 垃圾收集時間) 若是代碼運行100min垃圾收集1min,則爲99%】

對於用戶界面,適合使用GC停頓時間短,否則由於卡頓致使交互界面卡頓將很影響用戶體驗。

對於後臺 高吞吐量能夠高效率的利用cpu儘快完成程序運算任務,適合後臺運算

 

2.2老年代收集器

2.2.1 Serial Old

和新生代的Serial同樣爲單線程,Serial的老年代版本,不過它採用"標記-整理算法",這個模式主要是給Client模式下的JVM使用。

若是是Server模式有兩大用途

1.jdk5前和Parallel Scavenge搭配使用,jdk5前也只有這個老年代收集器能夠和它搭配。

2.做爲CMS收集器的後備。

2.2.2 Parallel Old

支持多線程,Parallel Scavenge的老年版本,jdk6開始出現, 採用"標記-整理算法"【老年代的收集器大都採用此算法】

在jdk6之前,新生代的Parallel Scavenge只能和Serial Old配合使用【根據圖,沒有這個的話只剩Serial Old,而Parallel Scavenge又不能和CMS配合使用】,並且Serial Old爲單線程Server模式下會拖後腿【多核cpu下沒法充分利用】,這種結合並不能讓應用的吞吐量最大化。

2.2.3 CMS

CMS收集器(Concurrent Mark Sweep)是以一種獲取最短回收停頓時間爲目標的收集器。【重視響應,能夠帶來好的用戶體驗,被sun稱爲併發低停頓收集器】

啓用CMS:-XX:+UseConcMarkSweepGC 

正如其名,CMS採用的是"標記-清除"(Mark Sweep)算法,並且是支持併發(Concurrent)的

它的運做分爲4個階段

1.初始標記:標記一下GC Roots能直接關聯到的對象,速度很快 2.併發標記:GC Roots Tarcing過程,便可達性分析 3.從新標記:爲了修正因併發標記期間用戶程序運做而產生變更的那一部分對象的標記記錄,會有些許停頓,時間上通常 初始標記 < 從新標記 < 併發標記 4.併發清除 

以上初始標記和從新標記須要stw(停掉其它運行java線程)

之因此說CMS的用戶體驗好,是由於CMS收集器的內存回收工做是能夠和用戶線程一塊兒併發執行。

整體上CMS是款優秀的收集器,可是它也有些缺點。

1.cms堆cpu特別敏感,cms運行線程和應用程序併發執行須要多核cpu,若是cpu核數多的話能夠發揮它併發執行的優點,可是cms默認配置啓動的時候垃圾線程數爲 (cpu數量+3)/4,它的性能很容易受cpu核數影響,當cpu的數目少的時候好比說爲爲2核,若是這個時候cpu運算壓力比較大,還要分一半給cms運做,這可能會很大程度的影響到計算機性能。

2.cms沒法處理浮動垃圾,可能致使Concurrent Mode Failure(併發模式故障)而觸發full GC

3.因爲cms是採用"標記-清除「算法,所以就會存在垃圾碎片的問題,爲了解決這個問題cms提供了 -XX:+UseCMSCompactAtFullCollection選項,這個選項至關於一個開關【默認開啓】,用於CMS頂不住要進行full GC時開啓內存碎片合併,內存整理的過程是沒法併發的,且開啓這個選項會影響性能(好比停頓時間變長)

浮動垃圾:因爲cms支持運行的時候用戶線程也在運行,程序運行的時候會產生新的垃圾,這裏產生的垃圾就是浮動垃圾,cms沒法當次處理,得等下次才能夠。

2.3 G1收集器

G1(garbage first:儘量多收垃圾,避免full gc)收集器是當前最爲前沿的收集器之一(1.7之後纔開始有),同cms同樣也是關注下降延遲,是用於替代cms功能更爲強大的新型收集器,由於它解決了cms產生空間碎片等一系列缺陷。

g1是區域化的,它將java堆內存劃分爲若干個大小相同的區域【region】,jvm能夠設置每一個region的大小(1-32m,大小得看堆內存大小,必須是2的冪),它會根據當前的堆內存分配合理的region大小。

 

g1經過併發(並行)標記階段查找老年代存活對象,經過並行複製壓縮存活對象【這樣能夠省出連續空間供大對象使用】。

g1將一組或多組區域中存活對象以增量並行的方式複製到不一樣區域進行壓縮,從而減小堆碎片,目標是儘量多回收堆空間【垃圾優先】,且儘量不超出暫停目標以達到低延遲的目的。

g1提供三種垃圾回收模式 young gc、mixed gc 和 full gc,不像其它的收集器,根據區域而不是分代,新生代老年代的對象它都能回收。

 

3.Minor GC、Major GC、FULL GC、mixed gc

3.1 Minor GC

在年輕代Young space(包括Eden區和Survivor區)中的垃圾回收稱之爲 Minor GC,Minor GC只會清理年輕代.

3.2 Major GC

Major GC清理老年代(old GC),可是一般也能夠指和Full GC是等價,由於收集老年代的時候每每也會伴隨着升級年輕代,收集整個Java堆。因此有人問的時候需問清楚它指的是full GC仍是old GC。

3.3 Full GC

full gc是對新生代、老年代、永久代【jdk1.8後沒有這個概念了】統一的回收。

【知乎R大的回答:收集整個堆,包括young gen、old gen、perm gen(若是存在的話)、元空間(1.8及以上)等全部部分的模式】

3.4 mixed GC【g1特有】

混合GC

收集整個young gen以及部分old gen的GC。只有G1有這個模式

 

4、一些問題

1.如何判斷對象是否存活算法

1.引用計數算法 早期判斷對象是否存活大多都是以這種算法,這種算法判斷很簡單,簡單來講就是給對象添加一個引用計數器,每當對象被引用一次就加1,引用失效時就減1。當爲0的時候就判斷對象不會再被引用。 優勢:實現簡單效率高,被普遍使用與如python何遊戲腳本語言上。 缺點:難以解決循環引用的問題,就是假如兩個對象互相引用已經不會再被其它其它引用,致使一直不會爲0就沒法進行回收。 2.可達性分析算法 目前主流的商用語言[如java、c#]採用的是可達性分析算法判斷對象是否存活。這個算法有效解決了循環利用的弊端。 它的基本思路是經過一個稱爲「GC Roots」的對象爲起始點,搜索所通過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用跟它鏈接則證實對象是不可用的。

2.何時觸發GC

minor GC(young GC):當年輕代中eden區分配滿的時候觸發[值得一提的是由於young GC後部分存活的對象會已到老年代(好比對象熬過15輪),因此事後old gen的佔用量一般會變高] full GC: ①手動調用System.gc()方法 [增長了full GC頻率,不建議使用而是讓jvm本身管理內存,能夠設置-XX:+ DisableExplicitGC來禁止RMI調用System.gc] ②發現perm gen(若是存在永久代的話)需分配空間但已經沒有足夠空間 ③老年代空間不足,好比說新生代的大對象大數組晉升到老年代就可能致使老年代空間不足。 ④CMS GC時出現Promotion Faield[pf] ⑤統計獲得的Minor GC晉升到舊生代的平均大小大於老年代的剩餘空間。 這個比較難理解,這是HotSpot爲了不因爲新生代晉升到老年代致使老年代空間不足而觸發的FUll GC。 好比程序第一次觸發Minor GC後,有5m的對象晉升到老年代,姑且如今平均算5m,那麼下次Minor GC發生時,先判斷如今老年代剩餘空間大小是否超過5m,若是小於5m,則HotSpot則會觸發full GC(這點挺智能的) 
Promotion Faield:minor GC時 survivor space放不下[滿了或對象太大],對象只能放到老年代,而老年代也放不下會致使這個錯誤。 Concurrent Model Failure:cms時特有的錯誤,由於cms時垃圾清理和用戶線程能夠是併發執行的,若是在清理的過程當中 可能緣由: 1 cms觸發太晚,能夠把XX:CMSInitiatingOccupancyFraction調小[好比-XX:CMSInitiatingOccupancyFraction=70 是指設定CMS在對內存佔用率達到70%的時候開始GC(由於CMS會有浮動垃圾,因此通常都較早啓動GC)] 2 垃圾產生速度大於清理速度,多是晉升閾值設置太小,Survivor空間小致使跑到老年代,eden區過小,存在大對象、數組對象等狀況 3.空間碎片過多,能夠開啓空間碎片整理併合理設置週期時間
FULL GC致使了concurrent mode failure,而不是由於concurrent mode failure錯誤致使觸發full gc,真正觸發full gc的緣由多是ygc時發生的promotion failure
 
 

3.cms收集器是否會掃描年輕代

會,在初始標記的時候會掃描新生代。

雖然cms是老年代收集器,可是咱們知道年輕代的對象是能夠晉升爲老年代的,爲了空間分配擔保,仍是有必要去掃描年輕代

 

4.爲何複製算法要分兩個Survivor,而不直接移到老年代

這樣作的話效率可能會更高,可是old區通常都是熬過屢次可達性分析算法事後的存活的對象,要求比較苛刻且空間有限,而不能直接移過去,這將致使一系列問題(好比老年代容易被撐爆)

分兩個Survivor(from/to),天然是爲了保證複製算法運行以提升效率


5.新生代什麼樣的狀況會晉升爲老年代

對象優先分配在eden區,eden區滿時會觸發一次minor GC

對象晉升規則

1 長期存活的對象進入老年代,對象每熬過一次GC年齡+1(默認年齡閾值15,可配置)。
2 對象太大新生代沒法容納則會分配到老年代
3 eden區滿了,進行minor gc後,eden和一個survivor區仍然存活的對象沒法放到(to survivor區)則會經過分配擔保機制放到老年代,這種狀況通常是minor gc後新生代存活的對象太多。
4 動態年齡斷定,爲了使內存分配更靈活,jvm不必定要求對象年齡達到MaxTenuringThreshold(15)才晉升爲老年代,若survior區相同年齡對象總大小大於survior區空間的一半,則大於等於這個年齡的對象將會在minor gc時移到老年
 

6.怎麼理解g1,適用於什麼場景

g1再也不區分老年代、年輕代這樣的內存空間,這是較以往收集器很大的差別,全部的內存空間就是一塊劃分爲不一樣子區域,每一個區域大小爲1m-32m,最多支持的內存爲64g左右,且因爲它爲了的特性適用於大內存機器

 

7.各區域大小設置

 

 

-Xms

初始堆大小。如:-Xms256m

-Xmx

最大堆大小。如:-Xmx512m

-Xmn

新生代大小。一般爲 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 個 Survivor 空間。實際可用空間爲 = Eden + 1 個 Survivor,即 90% 

-Xss

JDK1.5+ 每一個線程堆棧大小爲 1M,通常來講若是棧不是很深的話, 1M 是絕對夠用了的。

-XX:NewRatio

新生代與老年代的比例,如 –XX:NewRatio=2,則新生代佔整個堆空間的1/3,老年代佔2/3

-XX:SurvivorRatio

新生代中 Eden 與 Survivor 的比值。默認值爲 8。即 Eden 佔新生代空間的 8/10,另外兩個 Survivor 各佔 1/10 

-XX:PermSize

永久代(方法區)的初始大小

-XX:MaxPermSize

永久代(方法區)的最大值

-XX:+PrintGCDetails

GC 信息 打印

-XX:+HeapDumpOnOutOfMemoryError

讓虛擬機在發生內存溢出時 Dump 出當前的內存堆轉儲快照,以便分析用

相關文章
相關標籤/搜索