JVM

1. 介紹下 Java 內(nèi)存區(qū)域(運(yùn)行時(shí)數(shù)據(jù)區(qū))

Java 虛擬機(jī)在執(zhí)行 Java 程序的過程中會(huì)把它管理的內(nèi)存劃分成若干個(gè)不同的數(shù)據(jù)區(qū)域殴边。JDK. 1.8 和之前的版本略有不同,下面會(huì)介紹到。

JDK 1.8 之前:

image.png

JDK 1.8 :


image.png

線程私有的:

  1. 程序計(jì)數(shù)器
  2. 虛擬機(jī)棧
  3. 本地方法棧

線程共享的:

  1. 方法區(qū)
  2. 直接內(nèi)存 (非運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分)

1.1 程序計(jì)數(shù)器

程序計(jì)數(shù)器是一塊較小的內(nèi)存空間饲化,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器即碗。字節(jié)碼解釋器工作時(shí)通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令嗅绸,分支、循環(huán)箍镜、跳轉(zhuǎn)、異常處理煎源、線程恢復(fù)等功能都需要依賴這個(gè)計(jì)數(shù)器來完成色迂。

另外,為了線程切換后能恢復(fù)到正確的執(zhí)行位置薪夕,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器脚草,各線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)原献,我們稱這類內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存馏慨。

從上面的介紹中我們知道程序計(jì)數(shù)器主要有兩個(gè)作用:

  1. 字節(jié)碼解釋器通過改變程序計(jì)數(shù)器來依次讀取指令,從而實(shí)現(xiàn)代碼的流程控制姑隅,如:順序執(zhí)行写隶、選擇、循環(huán)讲仰、異常處理慕趴。
  2. 在多線程的情況下,程序計(jì)數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置,從而當(dāng)線程被切換回來的時(shí)候能夠知道該線程上次運(yùn)行到哪兒了冕房。

注意:程序計(jì)數(shù)器是唯一一個(gè)不會(huì)出現(xiàn) OutOfMemoryError 的內(nèi)存區(qū)域躏啰,它的生命周期隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而死亡耙册。

1.2 Java 虛擬機(jī)棧

與程序計(jì)數(shù)器一樣给僵,Java 虛擬機(jī)棧也是線程私有的,它的生命周期和線程相同详拙,描述的是 Java 方法執(zhí)行的內(nèi)存模型帝际,每次方法調(diào)用的數(shù)據(jù)都是通過棧傳遞的。

局部變量表主要存放了編譯期可知的各種數(shù)據(jù)類型(boolean饶辙、byte蹲诀、char、short弃揽、int脯爪、float、long蹋宦、double)披粟、對(duì)象引用(reference 類型,它不同于對(duì)象本身冷冗,可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔樖靥耄部赡苁侵赶蛞粋€(gè)代表對(duì)象的句柄或其他與此對(duì)象相關(guān)的位置)。

Java 虛擬機(jī)棧會(huì)出現(xiàn)兩種錯(cuò)誤:StackOverFlowError 和 OutOfMemoryError蒿辙。

StackOverFlowError: 若 Java 虛擬機(jī)棧的內(nèi)存大小不允許動(dòng)態(tài)擴(kuò)展拇泛,那么當(dāng)線程請(qǐng)求棧的深度超過當(dāng)前 Java 虛擬機(jī)棧的最大深度的時(shí)候,就拋出 StackOverFlowError 錯(cuò)誤思灌。
OutOfMemoryError: 若 Java 虛擬機(jī)堆中沒有空閑內(nèi)存俺叭,并且垃圾回收器也無法提供更多內(nèi)存的話。就會(huì)拋出 OutOfMemoryError 錯(cuò)誤泰偿。

Java 虛擬機(jī)棧也是線程私有的熄守,每個(gè)線程都有各自的 Java 虛擬機(jī)棧,而且隨著線程的創(chuàng)建而創(chuàng)建耗跛,隨著線程的死亡而死亡裕照。

擴(kuò)展:那么方法/函數(shù)如何調(diào)用?

Java 椀魉可用類比數(shù)據(jù)結(jié)構(gòu)中棧晋南,Java 棧中保存的主要內(nèi)容是棧幀,每一次函數(shù)調(diào)用都會(huì)有一個(gè)對(duì)應(yīng)的棧幀被壓入 Java 棧羔砾,每一個(gè)函數(shù)調(diào)用結(jié)束后负间,都會(huì)有一個(gè)棧幀被彈出偶妖。

Java 方法有兩種返回方式:

  1. return 語句
  2. 拋出異常

不管哪種返回方式都會(huì)導(dǎo)致棧幀被彈出。

1.3 本地方法棧

和虛擬機(jī)棧所發(fā)揮的作用非常相似政溃,區(qū)別是: 虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法 (也就是字節(jié)碼)服務(wù)趾访,而本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù)。 在 HotSpot 虛擬機(jī)中和 Java 虛擬機(jī)棧合二為一玩祟。

本地方法被執(zhí)行的時(shí)候腹缩,在本地方法棧也會(huì)創(chuàng)建一個(gè)棧幀,用于存放該本地方法的局部變量表空扎、操作數(shù)棧、動(dòng)態(tài)鏈接润讥、出口信息转锈。

方法執(zhí)行完畢后相應(yīng)的棧幀也會(huì)出棧并釋放內(nèi)存空間,也會(huì)出現(xiàn) StackOverFlowError 和 OutOfMemoryError 兩種錯(cuò)誤楚殿。

2. 堆

Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊撮慨,Java 堆是所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建脆粥。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例砌溺,幾乎所有的對(duì)象實(shí)例以及數(shù)組都在這里分配內(nèi)存。

Java世界中“幾乎”所有的對(duì)象都在堆中分配变隔,但是规伐,隨著JIT編譯期的發(fā)展與逃逸分析技術(shù)逐漸成熟,棧上分配匣缘、標(biāo)量替換優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化猖闪,所有的對(duì)象都分配到堆上也漸漸變得不那么“絕對(duì)”了。從jdk 1.7開始已經(jīng)默認(rèn)開啟逃逸分析肌厨,如果某些方法中的對(duì)象引用沒有被返回或者未被外面使用(也就是未逃逸出去)培慌,那么對(duì)象可以直接在棧上分配內(nèi)存。

Java 堆是垃圾收集器管理的主要區(qū)域柑爸,因此也被稱作GC 堆(Garbage Collected Heap).從垃圾回收的角度吵护,由于現(xiàn)在收集器基本都采用分代垃圾收集算法,所以 Java 堆還可以細(xì)分為:新生代和老年代:再細(xì)致一點(diǎn)有:Eden 空間表鳍、From Survivor馅而、To Survivor 空間等。進(jìn)一步劃分的目的是更好地回收內(nèi)存进胯,或者更快地分配內(nèi)存用爪。

在 JDK 7 版本及JDK 7 版本之前,堆內(nèi)存被通常被分為下面三部分:

  1. 新生代內(nèi)存(Young Generation)
  2. 老生代(Old Generation)
  3. 永生代(Permanent Generation)
image.png
image.png

上圖所示的 Eden 區(qū)胁镐、兩個(gè) Survivor 區(qū)都屬于新生代(為了區(qū)分偎血,這兩個(gè) Survivor 區(qū)域按照順序被命名為 from 和 to)诸衔,中間一層屬于老年代。

大部分情況颇玷,對(duì)象都會(huì)首先在 Eden 區(qū)域分配笨农,在一次新生代垃圾回收后,如果對(duì)象還存活帖渠,則會(huì)進(jìn)入 s0 或者 s1谒亦,并且對(duì)象的年齡還會(huì)加 1(Eden 區(qū)->Survivor 區(qū)后對(duì)象的初始年齡變?yōu)?1),當(dāng)它的年齡增加到一定程度(默認(rèn)為 15 歲)空郊,就會(huì)被晉升到老年代中份招。對(duì)象晉升到老年代的年齡閾值,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)

堆這里最容易出現(xiàn)的就是 OutOfMemoryError 錯(cuò)誤狞甚,并且出現(xiàn)這種錯(cuò)誤之后的表現(xiàn)形式還會(huì)有幾種锁摔,比如:

  1. OutOfMemoryError: GC Overhead Limit Exceeded : 當(dāng)JVM花太多時(shí)間執(zhí)行垃圾回收并且只能回收很少的堆空間時(shí),就會(huì)發(fā)生此錯(cuò)誤哼审。
  2. java.lang.OutOfMemoryError: Java heap space :假如在創(chuàng)建新的對(duì)象時(shí), 堆內(nèi)存中的空間不足以存放新創(chuàng)建的對(duì)象, 就會(huì)引發(fā)java.lang.OutOfMemoryError: Java heap space 錯(cuò)誤谐腰。(和本機(jī)物理內(nèi)存無關(guān),和你配置的內(nèi)存大小有關(guān)涩盾!)

3. 方法區(qū)

方法區(qū)與 Java 堆一樣十气,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息春霍、常量砸西、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)终畅。雖然 Java 虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分籍胯,但是它卻有一個(gè)別名叫做 Non-Heap(非堆),目的應(yīng)該是與 Java 堆區(qū)分開來离福。

方法區(qū)也被稱為永久代杖狼。很多人都會(huì)分不清方法區(qū)和永久代的關(guān)系,為此我也查閱了文獻(xiàn)妖爷。

方法區(qū)和永久代的關(guān)系

《Java 虛擬機(jī)規(guī)范》只是規(guī)定了有方法區(qū)這么個(gè)概念和它的作用蝶涩,并沒有規(guī)定如何去實(shí)現(xiàn)它。那么絮识,在不同的 JVM 上方法區(qū)的實(shí)現(xiàn)肯定是不同的了绿聘。 方法區(qū)和永久代的關(guān)系很像 Java 中接口和類的關(guān)系,類實(shí)現(xiàn)了接口次舌,而永久代就是 HotSpot 虛擬機(jī)對(duì)虛擬機(jī)規(guī)范中方法區(qū)的一種實(shí)現(xiàn)方式熄攘。 也就是說,永久代是 HotSpot 的概念彼念,方法區(qū)是 Java 虛擬機(jī)規(guī)范中的定義挪圾,是一種規(guī)范浅萧,而永久代是一種實(shí)現(xiàn),一個(gè)是標(biāo)準(zhǔn)一個(gè)是實(shí)現(xiàn)哲思,其他的虛擬機(jī)實(shí)現(xiàn)并沒有永久代這一說法洼畅。

常用參數(shù)
JDK 1.8 之前永久代還沒被徹底移除的時(shí)候通常通過下面這些參數(shù)來調(diào)節(jié)方法區(qū)大小

-XX:PermSize=N //方法區(qū) (永久代) 初始大小
-XX:MaxPermSize=N //方法區(qū) (永久代) 最大大小,超過這個(gè)值將會(huì)拋出 OutOfMemoryError 異常:java.lang.OutOfMemoryError: PermGen

相對(duì)而言,垃圾收集行為在這個(gè)區(qū)域是比較少出現(xiàn)的棚赔,但并非數(shù)據(jù)進(jìn)入方法區(qū)后就“永久存在”了帝簇。

JDK 1.8 的時(shí)候,方法區(qū)(HotSpot 的永久代)被徹底移除了(JDK1.7 就已經(jīng)開始了)靠益,取而代之是元空間丧肴,元空間使用的是直接內(nèi)存。

下面是一些常用參數(shù):

-XX:MetaspaceSize=N //設(shè)置 Metaspace 的初始(和最小大须屎蟆)
-XX:MaxMetaspaceSize=N //設(shè)置 Metaspace 的最大大小

與永久代很大的不同就是闪湾,如果不指定大小的話,隨著更多類的創(chuàng)建绩卤,虛擬機(jī)會(huì)耗盡所有可用的系統(tǒng)內(nèi)存。

3.1 為什么要將永久代 (PermGen) 替換為元空間 (MetaSpace) 呢?

整個(gè)永久代有一個(gè) JVM 本身設(shè)置固定大小上限江醇,無法進(jìn)行調(diào)整濒憋,而元空間使用的是直接內(nèi)存,受本機(jī)可用內(nèi)存的限制陶夜,雖然元空間仍舊可能溢出凛驮,但是比原來出現(xiàn)的幾率會(huì)更小。

當(dāng)你元空間溢出時(shí)會(huì)得到如下錯(cuò)誤: java.lang.OutOfMemoryError: MetaSpace

你可以使用 -XX:MaxMetaspaceSize 標(biāo)志設(shè)置最大元空間大小条辟,默認(rèn)值為 unlimited黔夭,這意味著它只受系統(tǒng)內(nèi)存的限制。-XX:MetaspaceSize 調(diào)整標(biāo)志定義元空間的初始大小如果未指定此標(biāo)志羽嫡,則 Metaspace 將根據(jù)運(yùn)行時(shí)的應(yīng)用程序需求動(dòng)態(tài)地重新調(diào)整大小本姥。

元空間里面存放的是類的元數(shù)據(jù),這樣加載多少類的元數(shù)據(jù)就不由 MaxPermSize 控制了, 而由系統(tǒng)的實(shí)際可用空間來控制杭棵,這樣能加載的類就更多了婚惫。

在 JDK8,合并 HotSpot 和 JRockit 的代碼時(shí), JRockit 從來沒有一個(gè)叫永久代的東西, 合并之后就沒有必要額外的設(shè)置這么一個(gè)永久代的地方了魂爪。

3.2 運(yùn)行時(shí)常量池

運(yùn)行時(shí)常量池是方法區(qū)的一部分先舷。Class 文件中除了有類的版本、字段滓侍、方法蒋川、接口等描述信息外,還有常量池表(用于存放編譯期生成的各種字面量和符號(hào)引用)

既然運(yùn)行時(shí)常量池是方法區(qū)的一部分撩笆,自然受到方法區(qū)內(nèi)存的限制捺球,當(dāng)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí)會(huì)拋出 OutOfMemoryError 錯(cuò)誤缸浦。

JDK1.7之前運(yùn)行時(shí)常量池邏輯包含字符串常量池存放在方法區(qū), 此時(shí)hotspot虛擬機(jī)對(duì)方法區(qū)的實(shí)現(xiàn)為永久代
JDK1.7 字符串常量池被從方法區(qū)拿到了堆中, 這里沒有提到運(yùn)行時(shí)常量池,也就是說字符串常量池被單獨(dú)拿到堆,運(yùn)行時(shí)常量池剩下的東西還在方法區(qū), 也就是hotspot中的永久代 。
JDK1.8 hotspot移除了永久代用元空間(Metaspace)取而代之, 這時(shí)候字符串常量池還在堆, 運(yùn)行時(shí)常量池還在方法區(qū), 只不過方法區(qū)的實(shí)現(xiàn)從永久代變成了元空間(Metaspace)

4. 直接內(nèi)存

直接內(nèi)存并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分懒构,也不是虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域餐济,但是這部分內(nèi)存也被頻繁地使用。而且也可能導(dǎo)致 OutOfMemoryError 錯(cuò)誤出現(xiàn)胆剧。

JDK1.4 中新加入的 NIO(New Input/Output) 類絮姆,引入了一種基于通道(Channel) 與緩存區(qū)(Buffer) 的 I/O 方式,它可以直接使用 Native 函數(shù)庫直接分配堆外內(nèi)存秩霍,然后通過一個(gè)存儲(chǔ)在 Java 堆中的 DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作篙悯。這樣就能在一些場景中顯著提高性能,因?yàn)楸苊饬嗽?Java 堆和 Native 堆之間來回復(fù)制數(shù)據(jù)铃绒。

本機(jī)直接內(nèi)存的分配不會(huì)受到 Java 堆的限制鸽照,但是,既然是內(nèi)存就會(huì)受到本機(jī)總內(nèi)存大小以及處理器尋址空間的限制颠悬。

5. 說一下Java對(duì)象的創(chuàng)建過程

下圖便是 Java 對(duì)象的創(chuàng)建過程矮燎,我建議最好是能默寫出來,并且要掌握每一步在做什么赔癌。

image.png

Step1:類加載檢查
虛擬機(jī)遇到一條 new 指令時(shí)诞外,首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到這個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載過灾票、解析和初始化過峡谊。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程刊苍。

Step2:分配內(nèi)存
在類加載檢查通過后既们,接下來虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需的內(nèi)存大小在類加載完成后便可確定正什,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來啥纸。分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇哪種分配方式由 Java 堆是否規(guī)整決定埠忘,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定脾拆。

內(nèi)存分配的兩種方式:(補(bǔ)充內(nèi)容,需要掌握)
選擇以上兩種方式中的哪一種莹妒,取決于 Java 堆內(nèi)存是否規(guī)整名船。而 Java 堆內(nèi)存是否規(guī)整,取決于 GC 收集器的算法是"標(biāo)記-清除"旨怠,還是"標(biāo)記-整理"(也稱作"標(biāo)記-壓縮")渠驼,值得注意的是,復(fù)制算法內(nèi)存也是規(guī)整的

image.png

內(nèi)存分配并發(fā)問題(補(bǔ)充內(nèi)容鉴腻,需要掌握)
在創(chuàng)建對(duì)象的時(shí)候有一個(gè)很重要的問題迷扇,就是線程安全百揭,因?yàn)樵趯?shí)際開發(fā)過程中,創(chuàng)建對(duì)象是很頻繁的事情蜓席,作為虛擬機(jī)來說器一,必須要保證線程是安全的,通常來講厨内,虛擬機(jī)采用兩種方式來保證線程安全:

  1. CAS+失敗重試: CAS 是樂觀鎖的一種實(shí)現(xiàn)方式祈秕。所謂樂觀鎖就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作雏胃,如果因?yàn)闆_突失敗就重試请毛,直到成功為止。虛擬機(jī)采用 CAS 配上失敗重試的方式保證更新操作的原子性瞭亮。
  2. TLAB: 為每一個(gè)線程預(yù)先在 Eden 區(qū)分配一塊兒內(nèi)存方仿,JVM 在給線程中的對(duì)象分配內(nèi)存時(shí),首先在 TLAB 分配统翩,當(dāng)對(duì)象大于 TLAB 中的剩余內(nèi)存或 TLAB 的內(nèi)存已用盡時(shí)仙蚜,再采用上述的 CAS 進(jìn)行內(nèi)存分配

Step3:初始化零值
內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭)厂汗,這一步操作保證了對(duì)象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用鳍征,程序能訪問到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。

Step4:設(shè)置對(duì)象頭
初始化零值完成之后面徽,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例匣掸、如何才能找到類的元數(shù)據(jù)信息趟紊、對(duì)象的哈希碼、對(duì)象的 GC 分代年齡等信息碰酝。 這些信息存放在對(duì)象頭中霎匈。 另外,根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同送爸,如是否啟用偏向鎖等铛嘱,對(duì)象頭會(huì)有不同的設(shè)置方式。

Step5:執(zhí)行 init 方法
在上面工作都完成之后袭厂,從虛擬機(jī)的視角來看墨吓,一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了,但從 Java 程序的視角來看纹磺,對(duì)象創(chuàng)建才剛開始帖烘,<init> 方法還沒有執(zhí)行,所有的字段都還為零橄杨。所以一般來說秘症,執(zhí)行 new 指令之后會(huì)接著執(zhí)行 <init> 方法照卦,把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全產(chǎn)生出來乡摹。

5.1 對(duì)象的訪問定位有哪兩種方式?

建立對(duì)象就是為了使用對(duì)象役耕,我們的Java程序通過棧上的 reference 數(shù)據(jù)來操作堆上的具體對(duì)象。對(duì)象的訪問方式有虛擬機(jī)實(shí)現(xiàn)而定聪廉,目前主流的訪問方式有①使用句柄和②直接指針兩種

  1. 句柄: 如果使用句柄的話瞬痘,那么Java堆中將會(huì)劃分出一塊內(nèi)存來作為句柄池,reference 中存儲(chǔ)的就是對(duì)象的句柄地址锄列,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息图云;


    image.png
  2. 直接指針: 如果使用直接指針訪問,那么 Java 堆對(duì)象的布局中就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息邻邮,而reference 中存儲(chǔ)的直接就是對(duì)象的地址竣况。

這兩種對(duì)象訪問方式各有優(yōu)勢。使用句柄來訪問的最大好處是 reference 中存儲(chǔ)的是穩(wěn)定的句柄地址筒严,在對(duì)象被移動(dòng)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針丹泉,而 reference 本身不需要修改。使用直接指針訪問方式最大的好處就是速度快鸭蛙,它節(jié)省了一次指針定位的時(shí)間開銷摹恨。

6. 簡單聊聊 JVM 內(nèi)存分配與回收

Java 的自動(dòng)內(nèi)存管理主要是針對(duì)對(duì)象內(nèi)存的回收和對(duì)象內(nèi)存的分配。同時(shí)娶视,Java 自動(dòng)內(nèi)存管理最核心的功能是 堆 內(nèi)存中對(duì)象的分配與回收晒哄。

Java 堆是垃圾收集器管理的主要區(qū)域,因此也被稱作GC 堆(Garbage Collected Heap).從垃圾回收的角度肪获,由于現(xiàn)在收集器基本都采用分代垃圾收集算法寝凌,所以 Java 堆還可以細(xì)分為:新生代和老年代:再細(xì)致一點(diǎn)有:Eden 空間、From Survivor孝赫、To Survivor 空間等较木。進(jìn)一步劃分的目的是更好地回收內(nèi)存,或者更快地分配內(nèi)存。

堆空間的基本結(jié)構(gòu):


image.png

上圖所示的 Eden 區(qū)、From Survivor0("From") 區(qū)驻民、To Survivor1("To") 區(qū)都屬于新生代,Old Memory 區(qū)屬于老年代峰锁。

大部分情況,對(duì)象都會(huì)首先在 Eden 區(qū)域分配双戳,在一次新生代垃圾回收后祖今,如果對(duì)象還存活,則會(huì)進(jìn)入 s0 或者 s1,并且對(duì)象的年齡還會(huì)加 1(Eden 區(qū)->Survivor 區(qū)后對(duì)象的初始年齡變?yōu)?1)千诬,當(dāng)它的年齡增加到一定程度(默認(rèn)為 15 歲)耍目,就會(huì)被晉升到老年代中。對(duì)象晉升到老年代的年齡閾值徐绑,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)置邪驮。

過這次 GC 后,Eden 區(qū)和"From"區(qū)已經(jīng)被清空傲茄。這個(gè)時(shí)候毅访,"From"和"To"會(huì)交換他們的角色,也就是新的"To"就是上次 GC 前的“From”盘榨,新的"From"就是上次 GC 前的"To"喻粹。不管怎樣,都會(huì)保證名為 To 的 Survivor 區(qū)域是空的草巡。Minor GC 會(huì)一直重復(fù)這樣的過程守呜,直到“To”區(qū)被填滿,"To"區(qū)被填滿之后山憨,會(huì)將所有對(duì)象移動(dòng)到老年代中查乒。

6.1 說一下堆內(nèi)存中對(duì)象的分配的基本策略

image.png
6.1.1 對(duì)象優(yōu)先在 eden 區(qū)分配

目前主流的垃圾收集器都會(huì)采用分代回收算法,因此需要將堆內(nèi)存分為新生代和老年代郁竟,這樣我們就可以根據(jù)各個(gè)年代的特點(diǎn)選擇合適的垃圾收集算法玛迄。

大多數(shù)情況下,對(duì)象在新生代中 eden 區(qū)分配棚亩。當(dāng) eden 區(qū)沒有足夠空間進(jìn)行分配時(shí)蓖议,虛擬機(jī)將發(fā)起一次 Minor GC.下面我們來進(jìn)行實(shí)際測試以下。

6.1.2 大對(duì)象直接進(jìn)入老年代

大對(duì)象就是需要大量連續(xù)內(nèi)存空間的對(duì)象(比如:字符串讥蟆、數(shù)組)拒担。

為什么要這樣呢?

為了避免為大對(duì)象分配內(nèi)存時(shí)由于分配擔(dān)保機(jī)制帶來的復(fù)制而降低效率攻询。

6.1.3 長期存活的對(duì)象將進(jìn)入老年代

動(dòng)態(tài)對(duì)象年齡判定
大部分情況,對(duì)象都會(huì)首先在 Eden 區(qū)域分配州弟,在一次新生代垃圾回收后钧栖,如果對(duì)象還存活,則會(huì)進(jìn)入 s0 或者 s1婆翔,并且對(duì)象的年齡還會(huì)加 1(Eden 區(qū)->Survivor 區(qū)后對(duì)象的初始年齡變?yōu)?1)拯杠,當(dāng)它的年齡增加到一定程度(默認(rèn)為 15 歲),就會(huì)被晉升到老年代中啃奴。對(duì)象晉升到老年代的年齡閾值潭陪,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)置。

6.1.4 主要進(jìn)行 gc 的區(qū)域

部分收集 (Partial GC):

  • 新生代收集(Minor GC / Young GC):只對(duì)新生代進(jìn)行垃圾收集;
  • 老年代收集(Major GC / Old GC):只對(duì)老年代進(jìn)行垃圾收集依溯。需要注意的是 Major GC 在有的語境中也用于指代整堆收集老厌;
    混合收集(Mixed GC):對(duì)整個(gè)新生代和部分老年代進(jìn)行垃圾收集。
    整堆收集 (Full GC):收集整個(gè) Java 堆和方法區(qū)黎炉。

6.2 如何判斷對(duì)象是否死亡?(兩種方法)

堆中幾乎放著所有的對(duì)象實(shí)例枝秤,對(duì)堆垃圾回收前的第一步就是要判斷哪些對(duì)象已經(jīng)死亡(即不能再被任何途徑使用的對(duì)象)。

  1. 引用計(jì)數(shù)法
    給對(duì)象中添加一個(gè)引用計(jì)數(shù)器慷嗜,每當(dāng)有一個(gè)地方引用它淀弹,計(jì)數(shù)器就加1;當(dāng)引用失效庆械,計(jì)數(shù)器就減1薇溃;任何時(shí)候計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。

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

這個(gè)算法的基本思想就是通過一系列的稱為 “GC Roots” 的對(duì)象作為起點(diǎn)缭乘,從這些節(jié)點(diǎn)開始向下搜索沐序,節(jié)點(diǎn)所走過的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到 GC Roots 沒有任何引用鏈相連的話忿峻,則證明此對(duì)象是不可用的薄啥。

image.png

7. 簡單的介紹一下強(qiáng)引用,軟引用,弱引用,虛引用

無論是通過引用計(jì)數(shù)法判斷對(duì)象引用數(shù)量,還是通過可達(dá)性分析法判斷對(duì)象的引用鏈?zhǔn)欠窨蛇_(dá)逛尚,判定對(duì)象的存活都與“引用”有關(guān)垄惧。

JDK1.2之前,Java中引用的定義很傳統(tǒng):如果reference類型的數(shù)據(jù)存儲(chǔ)的數(shù)值代表的是另一塊內(nèi)存的起始地址绰寞,就稱這塊內(nèi)存代表一個(gè)引用到逊。

JDK1.2以后,Java對(duì)引用的概念進(jìn)行了擴(kuò)充滤钱,將引用分為強(qiáng)引用觉壶、軟引用、弱引用件缸、虛引用四種(引用強(qiáng)度逐漸減弱)

  1. 強(qiáng)引用(StrongReference)
    以前我們使用的大部分引用實(shí)際上都是強(qiáng)引用铜靶,這是使用最普遍的引用。如果一個(gè)對(duì)象具有強(qiáng)引用他炊,那就類似于必不可少的生活用品争剿,垃圾回收器絕不會(huì)回收它。當(dāng)內(nèi)存空 間不足痊末,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤蚕苇,使程序異常終止,也不會(huì)靠隨意回收具有強(qiáng)引用的對(duì)象來解決內(nèi)存不足問題凿叠。

  2. 軟引用(SoftReference)
    如果一個(gè)對(duì)象只具有軟引用涩笤,那就類似于可有可無的生活用品嚼吞。如果內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它蹬碧,如果內(nèi)存空間不足了舱禽,就會(huì)回收這些對(duì)象的內(nèi)存。只要垃圾回收器沒有回收它锰茉,該對(duì)象就可以被程序使用呢蔫。軟引用可用來實(shí)現(xiàn)內(nèi)存敏感的高速緩存。

軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用飒筑,如果軟引用所引用的對(duì)象被垃圾回收片吊,JAVA虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。

  1. 弱引用(WeakReference)
    如果一個(gè)對(duì)象只具有弱引用协屡,那就類似于可有可無的生活用品俏脊。弱引用與軟引用的區(qū)別在于:只具有弱引用的對(duì)象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中肤晓,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象爷贫,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存补憾。不過漫萄,由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程, 因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象盈匾。

弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用腾务,如果弱引用所引用的對(duì)象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中削饵。

  1. 虛引用(PhantomReference)
    "虛引用"顧名思義岩瘦,就是形同虛設(shè),與其他幾種引用都不同窿撬,虛引用并不會(huì)決定對(duì)象的生命周期启昧。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒有任何引用一樣劈伴,在任何時(shí)候都可能被垃圾回收密末。

虛引用主要用來跟蹤對(duì)象被垃圾回收的活動(dòng)。

虛引用與軟引用和弱引用的一個(gè)區(qū)別在于: 虛引用必須和引用隊(duì)列(ReferenceQueue)聯(lián)合使用跛璧。當(dāng)垃 圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí)严里,如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前赡模,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。程序可以通過判斷引用隊(duì)列中是 否已經(jīng)加入了虛引用师抄,來了解被引用的對(duì)象是否將要被垃圾回收漓柑。程序如果發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對(duì)象的內(nèi)存被回收之前采取必要的行動(dòng)。

特別注意辆布,在程序設(shè)計(jì)中一般很少使用弱引用與虛引用瞬矩,使用軟引用的情況較多,這是因?yàn)檐浺每梢约铀貸VM對(duì)垃圾內(nèi)存的回收速度锋玲,可以維護(hù)系統(tǒng)的運(yùn)行安全景用,防止內(nèi)存溢出(OutOfMemory)等問題的產(chǎn)生。

8. 如何判斷一個(gè)常量是廢棄常量?

運(yùn)行時(shí)常量池主要回收的是廢棄的常量惭蹂。那么伞插,我們?nèi)绾闻袛嘁粋€(gè)常量是廢棄常量呢?

假如在常量池中存在字符串 "abc"盾碗,如果當(dāng)前沒有任何String對(duì)象引用該字符串常量的話媚污,就說明常量 "abc" 就是廢棄常量,如果這時(shí)發(fā)生內(nèi)存回收的話而且有必要的話廷雅,"abc" 就會(huì)被系統(tǒng)清理出常量池耗美。

9. 如何判斷一個(gè)類是無用的類?

方法區(qū)主要回收的是無用的類,那么如何判斷一個(gè)類是無用的類的呢航缀?

判定一個(gè)常量是否是“廢棄常量”比較簡單商架,而要判定一個(gè)類是否是“無用的類”的條件則相對(duì)苛刻許多。類需要同時(shí)滿足下面 3 個(gè)條件才能算是 “無用的類” :

  1. 該類所有的實(shí)例都已經(jīng)被回收芥玉,也就是 Java 堆中不存在該類的任何實(shí)例蛇摸。
  2. 加載該類的 ClassLoader 已經(jīng)被回收。
  3. 該類對(duì)應(yīng)的 java.lang.Class 對(duì)象沒有在任何地方被引用飞傀,無法在任何地方通過反射訪問該類的方法皇型。

虛擬機(jī)可以對(duì)滿足上述 3 個(gè)條件的無用類進(jìn)行回收,這里說的僅僅是“可以”砸烦,而并不是和對(duì)象一樣不使用了就會(huì)必然被回收弃鸦。

9. 垃圾收集有哪些算法,各自的特點(diǎn)幢痘?

9.1 標(biāo)記-清除算法

該算法分為“標(biāo)記”和“清除”階段:首先標(biāo)記出所有不需要回收的對(duì)象唬格,在標(biāo)記完成后統(tǒng)一回收掉所有沒有被標(biāo)記的對(duì)象。它是最基礎(chǔ)的收集算法颜说,后續(xù)的算法都是對(duì)其不足進(jìn)行改進(jìn)得到购岗。這種垃圾收集算法會(huì)帶來兩個(gè)明顯的問題:

  1. 效率問題
  2. 空間問題(標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的碎片)


    image.png

9.2 復(fù)制算法

為了解決效率問題,“復(fù)制”收集算法出現(xiàn)了门粪。它可以將內(nèi)存分為大小相同的兩塊喊积,每次使用其中的一塊。當(dāng)這一塊的內(nèi)存使用完后玄妈,就將還存活的對(duì)象復(fù)制到另一塊去乾吻,然后再把使用的空間一次清理掉髓梅。這樣就使每次的內(nèi)存回收都是對(duì)內(nèi)存區(qū)間的一半進(jìn)行回收。


image.png

9.3 標(biāo)記-整理算法

根據(jù)老年代的特點(diǎn)提出的一種標(biāo)記算法绎签,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣枯饿,但后續(xù)步驟不是直接對(duì)可回收對(duì)象回收,而是讓所有存活的對(duì)象向一端移動(dòng)诡必,然后直接清理掉端邊界以外的內(nèi)存奢方。


image.png

9.4 分代收集算法

當(dāng)前虛擬機(jī)的垃圾收集都采用分代收集算法,這種算法沒有什么新的思想爸舒,只是根據(jù)對(duì)象存活周期的不同將內(nèi)存分為幾塊蟋字。一般將 java 堆分為新生代和老年代,這樣我們就可以根據(jù)各個(gè)年代的特點(diǎn)選擇合適的垃圾收集算法碳抄。

比如在新生代中愉老,每次收集都會(huì)有大量對(duì)象死去,所以可以選擇復(fù)制算法剖效,只需要付出少量對(duì)象的復(fù)制成本就可以完成每次垃圾收集嫉入。而老年代的對(duì)象存活幾率是比較高的,而且沒有額外的空間對(duì)它進(jìn)行分配擔(dān)保璧尸,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集咒林。

延伸面試問題: HotSpot 為什么要分為新生代和老年代?
根據(jù)上面的對(duì)分代收集算法的介紹回答爷光。

9.5 HotSpot 為什么要分為新生代和老年代垫竞?

主要是為了提升 GC 效率。上面提到的分代收集算法已經(jīng)很好的解釋了這個(gè)問題蛀序。

10. 常見的垃圾回收器有那些?

image.png

如果說收集算法是內(nèi)存回收的方法論欢瞪,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。

雖然我們對(duì)各個(gè)收集器進(jìn)行比較徐裸,但并非要挑選出一個(gè)最好的收集器遣鼓。因?yàn)橹垃F(xiàn)在為止還沒有最好的垃圾收集器出現(xiàn),更加沒有萬能的垃圾收集器重贺,我們能做的就是根據(jù)具體應(yīng)用場景選擇適合自己的垃圾收集器骑祟。試想一下:如果有一種四海之內(nèi)、任何場景下都適用的完美收集器存在气笙,那么我們的 HotSpot 虛擬機(jī)就不會(huì)實(shí)現(xiàn)那么多不同的垃圾收集器了次企。

10.1 Serial 收集器

Serial(串行)收集器是最基本、歷史最悠久的垃圾收集器了潜圃。大家看名字就知道這個(gè)收集器是一個(gè)單線程收集器了缸棵。它的 “單線程” 的意義不僅僅意味著它只會(huì)使用一條垃圾收集線程去完成垃圾收集工作,更重要的是它在進(jìn)行垃圾收集工作的時(shí)候必須暫停其他所有的工作線程( "Stop The World" )谭期,直到它收集結(jié)束堵第。

新生代采用復(fù)制算法稚晚,老年代采用標(biāo)記-整理算法。


image.png

虛擬機(jī)的設(shè)計(jì)者們當(dāng)然知道 Stop The World 帶來的不良用戶體驗(yàn)型诚,所以在后續(xù)的垃圾收集器設(shè)計(jì)中停頓時(shí)間在不斷縮短(仍然還有停頓,尋找最優(yōu)秀的垃圾收集器的過程仍然在繼續(xù))鸳劳。

但是 Serial 收集器有沒有優(yōu)于其他垃圾收集器的地方呢狰贯?當(dāng)然有,它簡單而高效(與其他收集器的單線程相比)赏廓。Serial 收集器由于沒有線程交互的開銷涵紊,自然可以獲得很高的單線程收集效率。Serial 收集器對(duì)于運(yùn)行在 Client 模式下的虛擬機(jī)來說是個(gè)不錯(cuò)的選擇幔摸。

10.2 ParNew 收集器

ParNew 收集器其實(shí)就是 Serial 收集器的多線程版本摸柄,除了使用多線程進(jìn)行垃圾收集外,其余行為(控制參數(shù)既忆、收集算法驱负、回收策略等等)和 Serial 收集器完全一樣。

新生代采用復(fù)制算法患雇,老年代采用標(biāo)記-整理算法跃脊。

image.png

它是許多運(yùn)行在 Server 模式下的虛擬機(jī)的首要選擇,除了 Serial 收集器外苛吱,只有它能與 CMS 收集器(真正意義上的并發(fā)收集器酪术,后面會(huì)介紹到)配合工作。

并行和并發(fā)概念補(bǔ)充:
并行(Parallel) :指多條垃圾收集線程并行工作翠储,但此時(shí)用戶線程仍然處于等待狀態(tài)绘雁。

并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行,可能會(huì)交替執(zhí)行)援所,用戶程序在繼續(xù)運(yùn)行庐舟,而垃圾收集器運(yùn)行在另一個(gè) CPU 上。

10.3 Parallel Scavenge 收集器

Parallel Scavenge 收集器也是使用復(fù)制算法的多線程收集器任斋,它看上去幾乎和 ParNew 都一樣继阻。 那么它有什么特別之處呢?

-XX:+UseParallelGC

    使用 Parallel 收集器+ 老年代串行

-XX:+UseParallelOldGC

    使用 Parallel 收集器+ 老年代并行

Parallel Scavenge 收集器關(guān)注點(diǎn)是吞吐量(高效率的利用 CPU)废酷。CMS 等垃圾收集器的關(guān)注點(diǎn)更多的是用戶線程的停頓時(shí)間(提高用戶體驗(yàn))瘟檩。所謂吞吐量就是 CPU 中用于運(yùn)行用戶代碼的時(shí)間與 CPU 總消耗時(shí)間的比值。 Parallel Scavenge 收集器提供了很多參數(shù)供用戶找到最合適的停頓時(shí)間或最大吞吐量澈蟆,如果對(duì)于收集器運(yùn)作不太了解墨辛,手工優(yōu)化存在困難的時(shí)候,使用 Parallel Scavenge 收集器配合自適應(yīng)調(diào)節(jié)策略趴俘,把內(nèi)存管理優(yōu)化交給虛擬機(jī)去完成也是一個(gè)不錯(cuò)的選擇睹簇。

新生代采用復(fù)制算法奏赘,老年代采用標(biāo)記-整理算法。

image.png

這是 JDK1.8 默認(rèn)收集器
使用java -XX:+PrintCommandLineFlags -version命令查看

-XX:InitialHeapSize=262921408 -XX:MaxHeapSize=4206742528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)

JDK1.8 默認(rèn)使用的是 Parallel Scavenge + Parallel Old太惠,如果指定了-XX:+UseParallelGC 參數(shù)磨淌,則默認(rèn)指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC 來禁用該功能

10.3 Serial Old 收集器

Serial 收集器的老年代版本凿渊,它同樣是一個(gè)單線程收集器梁只。它主要有兩大用途:一種用途是在 JDK1.5 以及以前的版本中與 Parallel Scavenge 收集器搭配使用,另一種用途是作為 CMS 收集器的后備方案埃脏。

10.4 Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本搪锣。使用多線程和“標(biāo)記-整理”算法。在注重吞吐量以及 CPU 資源的場合彩掐,都可以優(yōu)先考慮 Parallel Scavenge 收集器和 Parallel Old 收集器构舟。

10.5 CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。它非常符合在注重用戶體驗(yàn)的應(yīng)用上使用堵幽。

CMS(Concurrent Mark Sweep)收集器是 HotSpot 虛擬機(jī)第一款真正意義上的并發(fā)收集器狗超,它第一次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作。

從名字中的Mark Sweep這兩個(gè)詞可以看出朴下,CMS 收集器是一種 “標(biāo)記-清除”算法實(shí)現(xiàn)的抡谐,它的運(yùn)作過程相比于前面幾種垃圾收集器來說更加復(fù)雜一些。整個(gè)過程分為四個(gè)步驟:

  1. 初始標(biāo)記: 暫停所有的其他線程桐猬,并記錄下直接與 root 相連的對(duì)象麦撵,速度很快 ;
  2. 并發(fā)標(biāo)記: 同時(shí)開啟 GC 和用戶線程溃肪,用一個(gè)閉包結(jié)構(gòu)去記錄可達(dá)對(duì)象免胃。但在這個(gè)階段結(jié)束,這個(gè)閉包結(jié)構(gòu)并不能保證包含當(dāng)前所有的可達(dá)對(duì)象惫撰。因?yàn)橛脩艟€程可能會(huì)不斷的更新引用域羔沙,所以 GC 線程無法保證可達(dá)性分析的實(shí)時(shí)性。所以這個(gè)算法里會(huì)跟蹤記錄這些發(fā)生引用更新的地方厨钻。
  3. 重新標(biāo)記: 重新標(biāo)記階段就是為了修正并發(fā)標(biāo)記期間因?yàn)橛脩舫绦蚶^續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄扼雏,這個(gè)階段的停頓時(shí)間一般會(huì)比初始標(biāo)記階段的時(shí)間稍長,遠(yuǎn)遠(yuǎn)比并發(fā)標(biāo)記階段時(shí)間短
  4. 并發(fā)清除: 開啟用戶線程夯膀,同時(shí) GC 線程開始對(duì)未標(biāo)記的區(qū)域做清掃诗充。
image.png

從它的名字就可以看出它是一款優(yōu)秀的垃圾收集器,主要優(yōu)點(diǎn):并發(fā)收集诱建、低停頓蝴蜓。但是它有下面三個(gè)明顯的缺點(diǎn):

  1. 對(duì) CPU 資源敏感;
  2. 無法處理浮動(dòng)垃圾;
  3. 它使用的回收算法-“標(biāo)記-清除”算法會(huì)導(dǎo)致收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生茎匠。

10.5 G1 收集器

G1 (Garbage-First) 是一款面向服務(wù)器的垃圾收集器,主要針對(duì)配備多顆處理器及大容量內(nèi)存的機(jī)器. 以極高概率滿足 GC 停頓時(shí)間要求的同時(shí),還具備高吞吐量性能特征.

被視為 JDK1.7 中 HotSpot 虛擬機(jī)的一個(gè)重要進(jìn)化特征格仲。它具備一下特點(diǎn):

  1. 并行與并發(fā):G1 能充分利用 CPU、多核環(huán)境下的硬件優(yōu)勢诵冒,使用多個(gè) CPU(CPU 或者 CPU 核心)來縮短 Stop-The-World 停頓時(shí)間凯肋。部分其他收集器原本需要停頓 Java 線程執(zhí)行的 GC 動(dòng)作,G1 收集器仍然可以通過并發(fā)的方式讓 java 程序繼續(xù)執(zhí)行汽馋。
  2. 分代收集:雖然 G1 可以不需要其他收集器配合就能獨(dú)立管理整個(gè) GC 堆否过,但是還是保留了分代的概念。
  3. 空間整合:與 CMS 的“標(biāo)記--清理”算法不同惭蟋,G1 從整體來看是基于“標(biāo)記整理”算法實(shí)現(xiàn)的收集器;從局部上來看是基于“復(fù)制”算法實(shí)現(xiàn)的药磺。
  4. 可預(yù)測的停頓:這是 G1 相對(duì)于 CMS 的另一個(gè)大優(yōu)勢告组,降低停頓時(shí)間是 G1 和 CMS 共同的關(guān)注點(diǎn),但 G1 除了追求低停頓外癌佩,還能建立可預(yù)測的停頓時(shí)間模型木缝,能讓使用者明確指定在一個(gè)長度為 M 毫秒的時(shí)間片段內(nèi)。

G1 收集器的運(yùn)作大致分為以下幾個(gè)步驟:

  1. 初始標(biāo)記
  2. 并發(fā)標(biāo)記
  3. 最終標(biāo)記
  4. 篩選回收

G1 收集器在后臺(tái)維護(hù)了一個(gè)優(yōu)先列表围辙,每次根據(jù)允許的收集時(shí)間我碟,優(yōu)先選擇回收價(jià)值最大的 Region(這也就是它的名字 Garbage-First 的由來)。這種使用 Region 劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式姚建,保證了 G1 收集器在有限時(shí)間內(nèi)可以盡可能高的收集效率(把內(nèi)存化整為零)矫俺。

10.6 ZGC 收集器

與 CMS 中的 ParNew 和 G1 類似,ZGC 也采用標(biāo)記-復(fù)制算法掸冤,不過 ZGC 對(duì)該算法做了重大改進(jìn)厘托。

在 ZGC 中出現(xiàn) Stop The World 的情況會(huì)更少!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末稿湿,一起剝皮案震驚了整個(gè)濱河市铅匹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饺藤,老刑警劉巖包斑,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涕俗,居然都是意外死亡罗丰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門再姑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丸卷,“玉大人,你說我怎么就攤上這事询刹∶占担” “怎么了萎坷?”我有些...
    開封第一講書人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沐兰。 經(jīng)常有香客問我哆档,道長,這世上最難降的妖魔是什么住闯? 我笑而不...
    開封第一講書人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任瓜浸,我火速辦了婚禮,結(jié)果婚禮上比原,老公的妹妹穿的比我還像新娘插佛。我一直安慰自己,他們只是感情好量窘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開白布雇寇。 她就那樣靜靜地躺著,像睡著了一般蚌铜。 火紅的嫁衣襯著肌膚如雪锨侯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,021評(píng)論 1 291
  • 那天冬殃,我揣著相機(jī)與錄音囚痴,去河邊找鬼。 笑死审葬,一個(gè)胖子當(dāng)著我的面吹牛深滚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涣觉,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼成箫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了旨枯?” 一聲冷哼從身側(cè)響起蹬昌,我...
    開封第一講書人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎攀隔,沒想到半個(gè)月后皂贩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昆汹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年明刷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片满粗。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辈末,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挤聘,我是刑警寧澤轰枝,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站组去,受9級(jí)特大地震影響鞍陨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜从隆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一诚撵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧键闺,春花似錦寿烟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至购桑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氏淑,已是汗流浹背勃蜘。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留假残,地道東北人缭贡。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像辉懒,于是被迫代替她去往敵國和親阳惹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容