JVM內(nèi)存區(qū)域以及各區(qū)域的內(nèi)存溢出異常,內(nèi)存分代策略,垃圾收集算法,各種垃圾收集器

本文整理自周志明老師的《深入理解Java虛擬機(jī)-JVM高級(jí)特性與最佳實(shí)踐》第3版的第二章和第三章。
加上了一些網(wǎng)上拼拼湊湊的圖片,個(gè)人認(rèn)為很多博客復(fù)制來(lái)復(fù)制去,最后的東西都看不懂,所以從書(shū)里碼了一下知識(shí)點(diǎn),也用作自己記憶。

一、一個(gè)命令

上面的結(jié)果顯示了 jvm 的模式:

Client VM(-client),為在客戶端環(huán)境中減少啟動(dòng)時(shí)間而優(yōu)化;
Server VM(-server),為在服務(wù)器環(huán)境中最大化程序執(zhí)行速度而設(shè)計(jì)。

在文件路徑:jdk-11.0.7+10\lib 下面可以更改 jvm.cfg 文件來(lái)決定是采用哪個(gè)模式,具體操作就是更改文件里面 Client 和 Server 這兩行的位置,誰(shuí)在上就是選擇誰(shuí)。

二、JVM 的內(nèi)存區(qū)域與內(nèi)存溢出異常

如上圖所示,是 Java 虛擬機(jī)規(guī)范規(guī)定的,jvm 管理的內(nèi)存區(qū)域。

  • 灰色部分,即方法區(qū)這兩個(gè)數(shù)據(jù)區(qū),是所有線程共享的數(shù)據(jù)區(qū)。
  • 而白色部分,包括程序計(jì)數(shù)器、java虛擬機(jī)棧、本地方法棧,叫線程隔離的數(shù)據(jù)區(qū),或者叫線程私有的內(nèi)存。這三塊內(nèi)存區(qū)域隨線程生,隨線程死。

每個(gè)部分的詳細(xì)介紹如下:

2.1 pc 寄存器( Program Counter)

也可叫程序計(jì)數(shù)器。是一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程執(zhí)行的字節(jié)碼的行號(hào)指示器。

在虛擬機(jī)的概念模型(注意只是概念)里,字節(jié)碼解釋器工作的時(shí)候就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取嚇一跳需要執(zhí)行的字節(jié)碼指令,顯然,分支循環(huán)等基礎(chǔ)功能都要靠這個(gè)計(jì)數(shù)器。

由于多線程實(shí)際上是線程輪流切換實(shí)現(xiàn)的,所以線程切換后為了能恢復(fù)到正確的執(zhí)行位置,每個(gè)線程都要有一個(gè)獨(dú)立的程序計(jì)數(shù)器。如果線程執(zhí)行的是一個(gè) java 方法,計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是本地方法,計(jì)數(shù)器的值則為空(Undefined)。

此內(nèi)存區(qū)域是唯一個(gè)在java虛擬機(jī)規(guī)范里沒(méi)有規(guī)定任何 OutOfMemoryError情況的區(qū)域。

2.2 java 虛擬機(jī)棧

棧是方法執(zhí)行的線程內(nèi)存模型。每個(gè)方法執(zhí)行的時(shí)候,jvm都會(huì)同步創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧等到,方法被調(diào)用直到執(zhí)行完畢,就是對(duì)應(yīng)一個(gè)棧幀在虛擬機(jī)棧里從入棧到出棧的過(guò)程。

大多情況棧主要指的是虛擬機(jī)棧里局部變量表的部分(實(shí)際上的劃分要更復(fù)雜)。局部變量表存放了各種基本java數(shù)據(jù)類型、對(duì)象引用和 returnAddress 類型(指向了一條字節(jié)碼指令的地址)。這些數(shù)據(jù)類型在局部變量表中以局部變量槽(Slot)來(lái)表示,其中64位長(zhǎng)的long和double類型占用兩個(gè)槽,其他的占一個(gè)。在編譯期間,局部變量表的空間就會(huì)分配完成,方法運(yùn)行期間不會(huì)改變局部變量表的大小。

java虛擬機(jī)規(guī)范對(duì)這個(gè)內(nèi)存區(qū)域規(guī)定了兩種異常:如果線程請(qǐng)求的棧深度大于虛擬機(jī)允許的深度,會(huì)拋出StackOverflowError;如果Java棧容量可以動(dòng)態(tài)擴(kuò)展,當(dāng)擴(kuò)展的時(shí)候無(wú)法申請(qǐng)到足夠的內(nèi)存會(huì)拋出OutOfMemoryError。

2.3 本地方法棧

本地方法棧和 java 虛擬機(jī)棧類似,區(qū)別只是虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法,本地方法棧是為虛擬機(jī)使用到的本地方法服務(wù)。

因此本地方法棧也會(huì)在棧深度溢出或者棧擴(kuò)展失敗時(shí)分別拋出拋出 StackOverflowError 和 OutOfMemoryError 異常。

2.4 java 堆

java 堆在虛擬機(jī)啟動(dòng)的時(shí)候建立,它是 java 程序最主要的內(nèi)存工作區(qū)域。

java堆的唯一目的就是存放對(duì)象實(shí)例。在JVM所管理的內(nèi)存中,堆區(qū)是最大的一塊,堆區(qū)也是Java GC機(jī)制所管理的內(nèi)存區(qū)域。需要注意,java堆只是邏輯上的連續(xù)區(qū)域,物理上可以不連續(xù)。

提到垃圾回收的時(shí)候總會(huì)說(shuō)堆的區(qū)域劃分,但是實(shí)際上java虛擬機(jī)規(guī)范沒(méi)有規(guī)定,所謂的劃分是各種虛擬機(jī)實(shí)現(xiàn)的風(fēng)格決定的。這部分后面垃圾回收的時(shí)候還會(huì)講。

java堆可以固定大小,也可以實(shí)現(xiàn)成可擴(kuò)展,當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來(lái)實(shí)現(xiàn),基于 -Xmx和-Xms參數(shù)來(lái)設(shè)定。

異常:如果堆內(nèi)存不夠,并且堆也無(wú)法擴(kuò)展,拋出OutOfMemoryError。

2.5 方法區(qū)

用來(lái)存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類型信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù)。

在java虛擬機(jī)里把他描述為堆的一個(gè)邏輯部分,但是又要和堆區(qū)分開(kāi),還有一個(gè)別名叫“非堆”。類加載子系統(tǒng)負(fù)責(zé)從文件系統(tǒng)或者網(wǎng)絡(luò)中加載 Class 信息( ClassLoader 就是這個(gè)區(qū)域下的組件),加載的類信息就存放于方法區(qū)。(可以看到,這里保存的東西都是唯一份的東西)

關(guān)于垃圾回收的永久代,一般都是指的方法區(qū),原因是當(dāng)時(shí)的hotspot虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把垃圾收集器的分代設(shè)計(jì)擴(kuò)展到了這里,或者說(shuō)使用永久代實(shí)現(xiàn)了方法區(qū),后來(lái)因?yàn)檫@種方法更容易內(nèi)存溢出,永久代的設(shè)計(jì)已經(jīng)被取消:到j(luò)dk8完全放棄永久代,使用本地內(nèi)存的中元空間來(lái)替代這部分的功能。

異常:無(wú)法滿足新的內(nèi)存分配需求,拋出OutOfMemoryError。

  • 運(yùn)行時(shí)常量池是方法區(qū)的一部分,用來(lái)存放編譯器生成的各種字面量和符號(hào)引用,在類加載后這些內(nèi)容都會(huì)進(jìn)入方法區(qū)的常量池。

既然是方法區(qū)的一部分,顯然是受到方法區(qū)內(nèi)存的限制,如果常量池?zé)o法再申請(qǐng)到內(nèi)存,會(huì)拋出拋出OutOfMemoryError。

2.6 直接內(nèi)存

直接內(nèi)存指的就已經(jīng)不屬于虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)域的部分了,java虛擬機(jī)規(guī)范也沒(méi)有定義這塊內(nèi)存。

java在jdk1.4 后,引入了 **NIO **類,允許 java 程序通過(guò)native函數(shù)庫(kù)直接分配堆外的內(nèi)存,然后通過(guò)java堆里的 DirectByteBuffer對(duì)象作為對(duì)這一塊內(nèi)存的引用進(jìn)行操作,在某些場(chǎng)景中能夠提高性能,因?yàn)楸苊饬?java 堆和 native 堆的數(shù)據(jù)來(lái)回復(fù)制。

異常:頻繁使用也可能導(dǎo)致拋出OutOfMemoryError。畢竟雖然沒(méi)有收到j(luò)ava堆的限制,可是還是會(huì)受到本機(jī)的內(nèi)存、以及處理器尋址空間的限制

三、垃圾回收算法

3.1 概述

上面的內(nèi)存區(qū)域里,線程獨(dú)有的三個(gè)區(qū)域,并不需要過(guò)多考慮回收問(wèn)題,因?yàn)榉峙浜突厥毡容^確定。

Java堆和方法區(qū)這兩個(gè)區(qū)域則有著很顯著的不確定性:一個(gè)接口的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能會(huì)不一樣,一個(gè)方法所執(zhí)行的不同條件分支所需要的內(nèi)存也可能不一樣,只有處于運(yùn)行期間,我們才能知道程序究竟會(huì)創(chuàng)建哪些對(duì)象,創(chuàng)建多少個(gè)對(duì)象,這部分內(nèi)存的分配和回收是動(dòng)態(tài)的。

對(duì)于方法區(qū),永久代的遺留問(wèn)題關(guān)注比較多,最主要的垃圾回收算法還都是關(guān)注堆內(nèi)存。

垃圾收集器所關(guān)注的正是這部分內(nèi)存該如何管理,我們討論的相關(guān)算法也是針對(duì)這部分內(nèi)存。

從如何判定對(duì)象消亡的角度處罰,垃圾收集算法可以分為“引用計(jì)數(shù)式”(Reference Counting GC)和 “ 追蹤式”(Tracing GC)兩類。主流的 java 虛擬機(jī)都采用的第二種。所以下面講的算法都是這種模式下面的。

3.2 判斷對(duì)象是否需要回收

垃圾回收第一件事要做的就是,確定哪些對(duì)象死了,哪些活著,死了的才要進(jìn)行回收。對(duì)于判斷,一般有兩種算法。

  1. 引用計(jì)數(shù)法(Reference Counting)

給對(duì)象添加一引用計(jì)數(shù)器,被引用一次計(jì)數(shù)器值就加 1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減 1;計(jì)數(shù)器為 0 時(shí),對(duì)象就是不可能再被使用的,簡(jiǎn)單高效。

存在問(wèn)題:無(wú)法解決對(duì)象之間相互循環(huán)引用的問(wèn)題,要想采用這個(gè)算法,還需要很多的額外處理。

  1. 可達(dá)性分析算法

通過(guò)一系列的稱為 "GC Roots" 的對(duì)象作為起始節(jié)點(diǎn)集合,從這些節(jié)點(diǎn)開(kāi)始,根據(jù)引用關(guān)系向下搜索,搜索所走過(guò)的路徑稱為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連時(shí),則證明此對(duì)象是不可能再被使用的。

image

可達(dá)性分析算法是當(dāng)前主流商用程序語(yǔ)言的內(nèi)存管理子系統(tǒng)采用的算法。

  1. 哪些對(duì)象可以作為 GC Roots 呢?

java 技術(shù)體系里,固定可作為 GC Roots 的對(duì)象包括以下幾種:

  • 在虛擬機(jī)棧中引用的對(duì)象。比如各個(gè)線程被調(diào)用的方法堆棧中使用到的參數(shù)、局部變量、臨時(shí)變量等;
  • 方法區(qū)中類靜態(tài)屬性引用的對(duì)象,比如java類的引用類型靜態(tài)變量;
  • 方法區(qū)中常量引用的對(duì)象,比如字符串常量池里的引用;
  • 本地方法棧JNI(也就是通常說(shuō)的本地方法)中引用的對(duì)象;
  • java虛擬機(jī)內(nèi)部的引用,比如基本數(shù)據(jù)類型對(duì)應(yīng)的 Class 對(duì)象,一些常駐的異常對(duì)象,和系統(tǒng)類加載器;
  • 所有被同步鎖(synchronized關(guān)鍵字)持有的對(duì)象;
  • 反應(yīng) java 虛擬機(jī)內(nèi)部情況的 JMXBean、JVMTI 中注冊(cè)的回調(diào)、本地代碼緩存等。

除了這些,還會(huì)有一些臨時(shí)加入的對(duì)象,共同構(gòu)成 GC Roots 集合。

  1. 方法區(qū)的垃圾回收

前面已經(jīng)說(shuō)過(guò),主要的收集區(qū)域是堆,而且方法區(qū)垃圾收集的性價(jià)比也比較低。比如在 hotspot 虛擬機(jī)采用了元空間來(lái)實(shí)現(xiàn)永久代,在這個(gè)區(qū)域沒(méi)有垃圾收集行為。

如果要回收,方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:廢棄的常量和不再使用的類型。

回收廢棄常量與回收J(rèn)ava堆中的對(duì)象非常類似。都是基于判斷是否還有對(duì)象引用指向這個(gè)常量。常量池中其他類(接口)、方法、字段的符號(hào)引用也與此類似。

而判斷類型的回收要滿足三個(gè)條件:

  1. 該類的所有實(shí)例已經(jīng)被回收,也就是堆中不再存在該類以及任何派生子類的實(shí)例;
  2. 加載該類的類加載器已經(jīng)被回收(這個(gè)條件很難達(dá)成);
  3. 該類對(duì)應(yīng)的 java.lang.Class 對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法。

3.3 分代收集理論

網(wǎng)上很多都說(shuō)是分代收集算法,但是顯然這并不是具體的算法,更像一種策略,選擇組合各種具體的算法,周志明老師的書(shū)上寫的是分代收集理論。

分代就是結(jié)合堆的區(qū)域劃分,然后講回收對(duì)象根據(jù)年齡不同放到不同區(qū)域,這樣的基礎(chǔ)上可以對(duì)某一區(qū)域單獨(dú)進(jìn)行垃圾回收,當(dāng)然,分代收集策略、對(duì)應(yīng)的內(nèi)存分代,都有消亡的趨勢(shì)。

分代至少會(huì)分為新生代和老年代兩個(gè)區(qū)域,新生代垃圾收集結(jié)束后,還存活的對(duì)象會(huì)逐步晉升到老年代存放,具體結(jié)合這一節(jié)的收集算法,區(qū)域的劃分下一節(jié)會(huì)講解。

在內(nèi)存分出不同的區(qū)域后,對(duì)不同區(qū)域的回收也起了不同的名字:

  • Minor GC / Young GC(新生代收集):目標(biāo)只是新生代區(qū)域的收集;
  • Major GC / Old GC(老年代收集):目標(biāo)只是老年代的垃圾收集;
  • Mix GC(混合收集):目標(biāo)是收集整個(gè)新生代+部分老年代的垃圾收集。目前只有 G1 收集器有這種行為。

以上三個(gè)都叫 Partial GC,也就是部分收集。還有一種收集:

  • Full GC(整堆收集):收集整個(gè) java 堆和方法區(qū)的垃圾收集。

3.4 具體的垃圾回收算法

之前看網(wǎng)上有的說(shuō)法講最早、基本的垃圾回收算法是

引用計(jì)數(shù)(Reference Counting):有一個(gè)引用就加一個(gè)技術(shù),少一個(gè)引用就減一個(gè)計(jì)數(shù),垃圾回收的時(shí)候就收集計(jì)數(shù)為 0 的。

但是現(xiàn)在我明白了,這玩意確實(shí)劃分的有點(diǎn)亂,引用計(jì)數(shù)正如 3.2 講到的,應(yīng)該算到 如何判斷垃圾是否需要回收的算法里,不應(yīng)該算在垃圾回收算法里。

所以仍然按照深入理解java虛擬機(jī)書(shū)里講的,分為三個(gè)算法。

3.4.1 標(biāo)記-清除算法(Mark-Sweep)

掃描GC Roots集合:

  • 第一階段,從引用根節(jié)點(diǎn),開(kāi)始標(biāo)記所有被引用的對(duì)象;
  • 第二階段,遍歷整個(gè)堆,把未標(biāo)記的對(duì)象清除。
  • 也可以反過(guò)來(lái),標(biāo)記未被引用的對(duì)象,然后清除未被標(biāo)記的。
image

缺點(diǎn)

  • 效率很不穩(wěn)定,如果堆包含大量對(duì)象,而大部分都要被收集,那么這個(gè)操作過(guò)程執(zhí)行效率一直降低;
  • 內(nèi)存空間碎片化,如上圖可以看的很明顯,垃圾收集執(zhí)行完后空間碎片過(guò)多,可能會(huì)導(dǎo)致以后程序運(yùn)行的時(shí)候需要分配大對(duì)象的時(shí)候找不到連續(xù)內(nèi)存從而又提前觸發(fā)垃圾收集。

3.4.2 標(biāo)記-復(fù)制算法(簡(jiǎn)稱復(fù)制算法)

把內(nèi)存劃分為兩個(gè)相等的區(qū)域,每次只用一個(gè)區(qū)域,一個(gè)內(nèi)存用完就開(kāi)始執(zhí)行算法:

  • 把這個(gè)區(qū)域仍然存活的對(duì)象復(fù)制到另一個(gè)區(qū)域(這一步顯然還是要先標(biāo)記的);
  • 然后把這個(gè)區(qū)域一次清理(省掉了上一種方法的第二次遍歷)。
image

優(yōu)點(diǎn)

簡(jiǎn)單、高效,而且解決了產(chǎn)生空間碎片的問(wèn)題。

缺點(diǎn)

需要 2 倍內(nèi)存,總是有一半空的,可用的也只有一半,太浪費(fèi)了。

3.4.3 標(biāo)記-整理(Mark-Compact)

結(jié)合標(biāo)記清除算法的第一步,第二步并不采用直接清理,而是讓所有對(duì)象都向內(nèi)存空間的固定一端挪動(dòng),最后清理掉邊界之外的內(nèi)存。

image

優(yōu)點(diǎn)

顯然,進(jìn)行垃圾收集后不會(huì)產(chǎn)生碎片。

缺點(diǎn)

“整理”的過(guò)程,或者說(shuō)移動(dòng),如果是在老年代,每次都沉積著大量對(duì)象,移動(dòng)的過(guò)程顯然會(huì)是一個(gè)很負(fù)重的操作,必須全程暫停用戶應(yīng)用程序這種停頓還被設(shè)計(jì)者描述為 Stop the world。

權(quán)衡:

  • 如果移動(dòng),那么缺點(diǎn)已經(jīng)說(shuō)過(guò)了;
  • 如果不移動(dòng),那么要通過(guò)更復(fù)雜的策略解決內(nèi)存碎片問(wèn)題,而內(nèi)存的訪問(wèn)本身又是用戶程序最頻繁的操作,額外的負(fù)擔(dān)會(huì)影響應(yīng)用程序的吞吐量。

也就是說(shuō),如果移動(dòng),內(nèi)存回收會(huì)更復(fù)雜,如果不移動(dòng),內(nèi)存分配會(huì)更復(fù)雜。從整個(gè)程序的吞吐量來(lái)看,移動(dòng)會(huì)更劃算。

注意:因?yàn)橛?strong>標(biāo)記的過(guò)程,通常都是需要停頓用戶線程來(lái)進(jìn)行的,只是總體來(lái)說(shuō),最后一種有整理的過(guò)程,前兩種的停頓時(shí)間就會(huì)短一些。

四、JVM堆內(nèi)存分代策略

需要再次強(qiáng)調(diào)的是:

從回收內(nèi)存的角度看,由于現(xiàn)代垃圾收集器大部分都是基于上一節(jié)所說(shuō)的,分代收集理論設(shè)計(jì)的,區(qū)域劃分僅僅是一部分垃圾收集器的共同特性或者說(shuō)設(shè)計(jì)風(fēng)格而已,而非某個(gè)Java虛擬機(jī)具體實(shí)現(xiàn)的固有內(nèi)存布局,更不是《Java虛擬機(jī)規(guī)范》里對(duì)Java堆的進(jìn)一步細(xì)致劃分。

尤其到 G1 收集器的出現(xiàn)后,已經(jīng)打破了固有的策略,往后,垃圾收集器技術(shù)的更新也會(huì)帶來(lái)更多的策略,而不是分代。

因此我們從分水嶺的前后來(lái)分別介紹。

內(nèi)存分代策略:也就是根據(jù)對(duì)象存活的周期不同,將堆內(nèi)存劃分為幾塊,一般分為新生代、老年代、永久代。

4.1 為什么要分代?

很好理解,因?yàn)楦鞣N對(duì)象示例需要回收的頻率是不一樣的,分區(qū)操作能縮小操作的范圍,結(jié)合上一節(jié)的垃圾回收策略,更好理解。

  • 如果沒(méi)有區(qū)域劃分,頻繁進(jìn)行垃圾收集的時(shí)候,遍歷范圍都是所有的對(duì)象,會(huì)嚴(yán)重影響的 GC 效率。
  • 有了內(nèi)存分代,根據(jù)不同區(qū)域,采用不同的垃圾收集算法。

4.2 內(nèi)存劃分具體策略

新生代、老年代、永久代(如上一節(jié)所介紹的,永久代后來(lái)已經(jīng)被取締)。

image

4.2.1 新生代(Young)

新生代又分為了三塊區(qū)域,他們的空間比例默認(rèn)為 8:1:1。

  • Eden(伊甸園,人類創(chuàng)建的地方),就是所有對(duì)象產(chǎn)生的地方;
  • From,屬于第一塊 Survivor 區(qū)域;
  • To,屬于第二塊 Survivor 區(qū)域。

這么個(gè)比例劃分是因?yàn)樾律睦厥账惴ㄊ?strong>標(biāo)記-復(fù)制算法,設(shè)置這個(gè)比例是為了充分利用內(nèi)存空間。

新生對(duì)象在 Eden 區(qū)分配,除了大對(duì)象,大對(duì)象直接進(jìn)入老年代。

大對(duì)象就是指需要大量連續(xù)內(nèi)存的對(duì)象,就是很大的數(shù)組,或者很長(zhǎng)的字符串。比大對(duì)象更糟糕的就是遇到一個(gè)朝生夕滅的大對(duì)象。

結(jié)合一般在這個(gè)區(qū)域采用標(biāo)記-復(fù)制算法,看一看新生代的垃圾收集過(guò)程:

  • 如果 Eden 區(qū)不夠了,就會(huì)開(kāi)始一次 Minor GC,將 Eden 里存活的復(fù)制到 From(Eden空了);
  • 下次 Eden 區(qū)滿了,再執(zhí)行一次 Minor GC,將存活的對(duì)象復(fù)制到 To 中 (Eden空了),同時(shí),將 From 中消亡的對(duì)象清理掉,將存活的對(duì)象也復(fù)制到 To 區(qū),然后清空 From 區(qū)(此時(shí) From空);

在 From 和 To 兩個(gè)區(qū)域的這種切換,顯然就是標(biāo)記復(fù)制的算法,他們兩個(gè)的空間也確實(shí)是 1 : 1。此后從 Eden 區(qū)滿了后再往他們兩個(gè)區(qū)域移動(dòng)的時(shí)候就是交替進(jìn)行。

注意事項(xiàng)

  • 當(dāng)兩個(gè)存活區(qū)切換了幾次(HotSpot虛擬機(jī)默認(rèn)15次)之后,仍然存活的對(duì)象,將被復(fù)制到老年代。實(shí)現(xiàn)方式,就是在不斷的 Minor GC ,這個(gè)復(fù)制的過(guò)程會(huì)給對(duì)象計(jì)算年齡,年齡計(jì)數(shù)器是存儲(chǔ)在對(duì)象頭里的(關(guān)于虛擬機(jī)的對(duì)象頭信息)。
  • 除了年齡判斷,hotspot 虛擬機(jī)還有動(dòng)態(tài)對(duì)象年齡判定的策略,如果 survivor 空間相同年齡所有對(duì)象大小總和 >= Survivor 空間的一半,這部分對(duì)象都直接進(jìn)入老年代。

所以可以總結(jié)出有 3 類對(duì)象都會(huì)進(jìn)入老年代:1.大對(duì)象直接進(jìn);2.在Minor GC 存活15歲后進(jìn);3.相同年齡對(duì)象成為眾數(shù),一起進(jìn)。

4.2.2 老年代(Old)

這里的對(duì)象GC 頻率低。

4.2.3 永久代(Permanent)

正如前面所說(shuō),jdk8以前,很多人愿意把方法區(qū)稱為永久代,本質(zhì)上是因?yàn)楫?dāng)時(shí)的hotspot虛擬機(jī)選擇把垃圾收集的設(shè)計(jì)擴(kuò)展到了方法區(qū),或者說(shuō)使用永久代實(shí)現(xiàn)方法區(qū),使得垃圾收集器能夠管理這部分內(nèi)存,其他虛擬機(jī)不存在這個(gè)概念。

到j(luò)dk8就完全放棄了,因?yàn)閷?shí)現(xiàn)方法區(qū)的內(nèi)容已經(jīng)改為用本地內(nèi)存的元空間。

這里其實(shí)我有一個(gè)疑問(wèn),邏輯上本來(lái)方法區(qū)是屬于堆的一塊特殊區(qū)域,現(xiàn)在改用本地直接內(nèi)存來(lái)實(shí)現(xiàn),那么在內(nèi)存區(qū)域的劃分上,是應(yīng)該定義為直接內(nèi)存的一塊特殊區(qū)域?

反正說(shuō) jvm 的內(nèi)存區(qū)域的時(shí)候迷迷糊糊的。

五、垃圾回收器

這里指的都是“經(jīng)典”垃圾回收器,是因?yàn)槟壳暗男录夹g(shù)實(shí)現(xiàn)的高性能低延遲收集器還處于實(shí)驗(yàn)狀態(tài)。

所以記錄一下時(shí)間:現(xiàn)在是2020.09.04,參考的書(shū)是基于 jdk11 的。

5.1 Serial 收集器(復(fù)制算法)

新生代單線程收集器,標(biāo)記和清理都是單線程,需要其它工作線程暫停,優(yōu)點(diǎn)是簡(jiǎn)單高效。

這也是虛擬機(jī)在Client模式下運(yùn)行的默認(rèn)值,可以通過(guò) -XX:+UseSerialGC 來(lái)強(qiáng)制指定。

5.2 Serial Old 收集器(標(biāo)記-整理算法)

老年代單線程收集器,Serial收集器的老年代版本,需要其它工作線程暫停,簡(jiǎn)單高效。

5.3 ParNew 收集器(復(fù)制算法)

新生代收集器,實(shí)質(zhì)上是 Serial 收集器的多線程版本,各種策略都和 Serial 收集器一樣。除了支持多線程并行,沒(méi)有別的優(yōu)點(diǎn),但是在 jdk7 之前,都會(huì)用他,原因和性能無(wú)關(guān),原因是:只有他能和 CMS 配合工作。(之后有 G1 了,他就沒(méi)這么高地位了)

5.4 Parallel Scavenge 收集器(復(fù)制算法)

新生代收集器,并行,表面上看起來(lái)的特性和 ParNew 一樣。

但是他的特點(diǎn)是,關(guān)注點(diǎn)不在縮短線程停頓時(shí)間,而關(guān)注如何達(dá)到一個(gè)可控制的吞吐量,什么是吞吐量?

image

Parallel Scavenge+Serial Old 收集器組合回收垃圾(這也是在Server模式下的默認(rèn)值)可用 -XX:+UseParallelGC 來(lái)強(qiáng)制指定,用 -XX:ParallelGCThreads=4 來(lái)指定線程數(shù)。

5.5 Parallel Old 收集器(標(biāo)記-整理算法)

Parallel Scavenge 收集器的老年代版本,并行收集器。

Parallel Scavenge 和 Parallel Old 搭配,產(chǎn)生了一種“吞吐量”優(yōu)先的收集器方案。

5.6 CMS(Concurrent Mark Sweep)收集器(標(biāo)記-清除算法)

老年代收集器。從名字就可以看出來(lái),是并發(fā)+標(biāo)記清除。一些官方公開(kāi)文檔里害稱之為Concurrent Low Pause Collector,并發(fā)低停頓收集器。

他的收集過(guò)程比較復(fù)雜,分為四步:

  1. 初始標(biāo)記;(需要停頓用戶線程,標(biāo)記GC roots能直接關(guān)聯(lián)到的對(duì)象,快)
  2. 并發(fā)標(biāo)記;(從上一步關(guān)聯(lián)到的對(duì)象遍歷整個(gè)圖,但是是并發(fā)運(yùn)行的,不用停頓用戶線程,慢)
  3. 重新標(biāo)記;(修正上一個(gè)階段可能因?yàn)橛脩衾^續(xù)操作又產(chǎn)生變動(dòng)的部分,需要停頓用戶線程,快)
  4. 并發(fā)清除。(并發(fā)執(zhí)行,因?yàn)椴恍枰硪苿?dòng)存活對(duì)象)

最大的優(yōu)點(diǎn)就是名字體現(xiàn)出來(lái)的:并發(fā)收集、低停頓。

缺點(diǎn)

  • 對(duì)處理器資源非常敏感,原因就是,雖然你是并發(fā)的,但是你本身相當(dāng)于其他的線程,這是境地總吞吐量的(空間換時(shí)間嘛);
  • 無(wú)法處理浮動(dòng)垃圾。浮動(dòng)垃圾就是說(shuō),他的四個(gè)步驟里,并發(fā)的兩個(gè)步驟,用戶線程都是在同時(shí)產(chǎn)生垃圾的,只能等到下一次才能處理。所以垃圾收集還需要有額外預(yù)留的空間,否則還會(huì)產(chǎn)生問(wèn)題;
  • 因?yàn)槭菢?biāo)記清除算法,所以有空間碎片以及后續(xù)會(huì)產(chǎn)生的問(wèn)題。

5.7 G1/Garbage First 收集器

這是一個(gè)里程碑式的成果。實(shí)驗(yàn)期完成后,正式商用,到j(luò)dk8后,官方稱之為全功能垃圾收集器(Fullly-Featured Garbage Collector)。

jdk 9 后,G1 也替代了Parallel Scavenge 和 Parallel Old 搭配的組合,稱為服務(wù)端模式下的默認(rèn)收集器,CMS 直接淪落到了不推薦使用。

之前垃圾收集的目標(biāo)都是基于分代的內(nèi)存,要么在新生代工作、要么老年代、要么整個(gè) java 堆。G1 則跳出了這個(gè)牢籠,可以面向堆內(nèi)存的任何部分來(lái)組成回收集(Collection Set,簡(jiǎn)稱 CSet),衡量標(biāo)準(zhǔn)不再是哪個(gè)分代,而是哪塊垃圾最多我去哪快,這就是 G1 收集器的 Mixed GC 模式。

G1 把堆內(nèi)存分為了不同的 Region ,這些 Region 大小相等,各自獨(dú)立。這個(gè)劃分不像以前遵循的那種固定比例,這樣,每個(gè) Region 都可能扮演以前的新生代的 Eden 空間、Survivor空間或者老年代空間,然后垃圾收集器采用不同的策略去收集。

缺點(diǎn):比CMS有更高的內(nèi)存占用,更高的額外執(zhí)行負(fù)載。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容