# JVM和GC

JVM和GC

JVM運(yùn)行時(shí)內(nèi)存區(qū)

image

一、線程私有數(shù)據(jù)區(qū)

1、程序計(jì)數(shù)器

在JVM中,多線程是通過(guò)線程輪流切換來(lái)獲得CPU執(zhí)行時(shí)間的,因此,在任一具體時(shí)刻,一個(gè)CPU的內(nèi)核只會(huì)執(zhí)行一條線程中的指令,因此為了能夠使得每個(gè)線程都在線程切換后能夠恢復(fù)在切換之前的程序執(zhí)行位置,每個(gè)線程都需要有自己獨(dú)立的程序計(jì)數(shù)器,并且不能相互干擾,否則就會(huì)影響到程序的正確執(zhí)行次序。程序計(jì)數(shù)器中記錄的是正在執(zhí)行的線程的虛擬機(jī)字節(jié)碼指令的地址,字節(jié)碼的解釋器工作的時(shí)候就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令。程序計(jì)數(shù)器是每個(gè)線程私有的。

2、虛擬機(jī)棧

虛擬機(jī)棧也就是我們常說(shuō)的棧。虛擬機(jī)棧是Java方法執(zhí)行的內(nèi)存模型。Java棧中存放的是一個(gè)個(gè)棧幀。并且是線程私有的,生命周期與線程相同,描述的是Java方法執(zhí)行的內(nèi)存模型:每一個(gè)方法執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame),用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法的執(zhí)行就是對(duì)應(yīng)棧幀在虛擬機(jī)棧中的入棧、出棧的過(guò)程。下圖表示了一個(gè)Java棧的模型:

image

3、本地方法棧

本地方法棧與虛擬機(jī)棧所發(fā)揮的作用很相似,他們的區(qū)別在于虛擬機(jī)棧為執(zhí)行Java代碼方法服務(wù),而本地方法棧為Native方法服務(wù)。

二、線程共享區(qū)域

1、Java堆

Java堆可以說(shuō)是虛擬機(jī)中最大的一塊內(nèi)存了。它是所有線程共享的內(nèi)存區(qū)域,幾乎所有的實(shí)例對(duì)象都是在這塊區(qū)域中存放。堆可以處理物理上不連續(xù)的內(nèi)存空間,只要邏輯上連續(xù)的就可以。當(dāng)然,隨著JIT(just in time,及時(shí)編譯技術(shù)) 編譯器的發(fā)展,所有對(duì)象在"堆"上分配也變得不那么"絕對(duì)"了。同時(shí)Java堆也是垃圾收集器管理的主要區(qū)域。由于現(xiàn)在收集器基本上采用的都是分代收集算法,所有Java堆又可以細(xì)分為:"新生代"和"老年代"。再細(xì)致分就是把新生代分為:Eden空間、From Survivor空間、To Survivor空間。

2、方法區(qū)

方法區(qū)在JVM中也是一個(gè)非常重要的區(qū)域,在方法區(qū)中,存儲(chǔ)了每個(gè)類的信息(包括類的名稱、方法信息、字段信息)、靜態(tài)變量、常量以及編譯器編譯后的代碼等。它與堆一樣,是被線程共享的區(qū)域,很容易理解,我們?cè)趯慗ava代碼時(shí),每個(gè)線程都可以訪問(wèn)同一個(gè)類的靜態(tài)變量。在Class文件中除了類的字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用來(lái)存儲(chǔ)編譯期間生成的字面量和符號(hào)引用。

垃圾回收

哪些對(duì)象需要回收

1、引用計(jì)數(shù)法:判斷對(duì)象的引用數(shù)量

引用計(jì)數(shù)法是通過(guò)判斷對(duì)象的引用數(shù)量來(lái)決定對(duì)象是否可以被回收

給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用他時(shí),計(jì)數(shù)器值就+1,;當(dāng)引用失效時(shí),計(jì)數(shù)器值就-1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能在被使用。

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

判定效率很高

  • 確定:

不會(huì)完全準(zhǔn)確,因?yàn)槿绻霈F(xiàn)兩個(gè)對(duì)象相互引用的問(wèn)題就不行了,如下圖所示:

image

如上圖對(duì)象A和對(duì)象B相互引用,導(dǎo)致他們的引用計(jì)數(shù)都不為0,那么垃圾收集器就永遠(yuǎn)不會(huì)回收他們。

2、可達(dá)性分析算法:判斷對(duì)象的引用鏈?zhǔn)欠窨蛇_(dá)

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

image

上圖中,ObjD和ObjE都是不可用的,可以被GC回收掉。

在Java中,可作為 GC Root 的對(duì)象包括以下幾種:

  • 虛擬機(jī)棧(棧幀中的局部變量表)中引用的對(duì)象
  • 方法區(qū)中靜態(tài)屬性引用的對(duì)象
  • 方法區(qū)中常量引用的對(duì)象
  • 本地方法棧中Native引用的對(duì)象

垃圾收集算法

1、標(biāo)記清除算法

標(biāo)記清除即Mark-Sweep,是一種最簡(jiǎn)單的收集算法。在經(jīng)歷過(guò)對(duì)象判活以后,我們把需要回收的對(duì)象標(biāo)記出來(lái),然后在統(tǒng)一時(shí)刻回收所有被標(biāo)記的對(duì)象。如圖所示:

image

黑色標(biāo)記的可回收對(duì)象在回收后全部變成未使用空間,但是這樣回收后有木有發(fā)現(xiàn)空間碎片很多,碎片太多就會(huì)導(dǎo)致再分配稍微大點(diǎn)的空間時(shí),找不到這樣的連續(xù)內(nèi)存,從而導(dǎo)致GC會(huì)被頻繁調(diào)用,所以標(biāo)記清除是一種基礎(chǔ)的垃圾收集算法,其它算法基本都是以它為基礎(chǔ)優(yōu)化產(chǎn)生。

2、復(fù)制算法

復(fù)制算法的思想就是把內(nèi)存分為兩塊,每次只在一邊分配內(nèi)存,當(dāng)一邊的內(nèi)存用完了,就把所有還存活的對(duì)象復(fù)制到另一半去,這時(shí)候把原來(lái)使用過(guò)的這一邊的所有空間一次性清理掉,所以也就不存在內(nèi)存碎片的問(wèn)題了。缺點(diǎn)就是會(huì)浪費(fèi)一半的內(nèi)存空間?;舅悸啡鐖D:

image

其實(shí)分代GC算法在新生代區(qū)域就用了復(fù)制算法,并且也沒(méi)有分成1:1,而是8:1,也就是所謂的Eden區(qū)和survivor區(qū),新生代中大多數(shù)對(duì)象都是“朝生夕死”的,所以在minorGC時(shí),只把存活下來(lái)的對(duì)象全部復(fù)制到survivor區(qū)。

3、標(biāo)記整理算法

上面提到的復(fù)制算法也有它的弱點(diǎn),就是當(dāng)對(duì)象存活率很高的時(shí)候,就會(huì)存在很多的復(fù)制操作,從而影響了效率。所以這種算法運(yùn)用在老年代的話很明顯不合適,于是又有了標(biāo)記整理算法,這種算法的主要思路就是把活躍對(duì)象標(biāo)記出來(lái),之后再向內(nèi)存的一側(cè)移動(dòng),然后直接清理掉邊界以外的內(nèi)存,具體思路如下:


image

4、分代收集算法

新生代中的對(duì)象每次回收都基本上只有10%左右的對(duì)象存活,所以需要復(fù)制的對(duì)象很少,效率還不錯(cuò)。實(shí)踐中會(huì)將新生代分為一塊較大的Eden空間和兩塊較小的Surivor空間,每次使用Eden和其中一塊Survivor。當(dāng)回收時(shí),將Eden和Survivor中還存活著對(duì)象一次地復(fù)制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過(guò)的Survivor空間。HotSpot虛擬機(jī)默認(rèn)的Eden和Survivor的大小比例是8:1:1。也就是每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%(80%+10%),只有10%的內(nèi)存會(huì)被"浪費(fèi)"。

image

對(duì)于一個(gè)大型的系統(tǒng),當(dāng)創(chuàng)建的對(duì)象和方法變量比較多時(shí),堆內(nèi)存中的對(duì)象也會(huì)比較多,如果逐一分析對(duì)象是否該回收,那么勢(shì)必造成效率低下。分代收集算法是基于這樣一個(gè)事實(shí):不同的對(duì)象的生命周期(存活情況)是不一樣的,故而不同生命周期的對(duì)象位于堆中不同的區(qū)域,因此對(duì)堆內(nèi)存不同區(qū)域采用不同的策略進(jìn)行回收可以提高JVM的執(zhí)行效率。當(dāng)代商用虛擬機(jī)使用的都是分代收集算法:新生代對(duì)象存活率低,就采用復(fù)制算法;老年代存活率高,就采用標(biāo)記清除算法或者標(biāo)記整理算法。Java堆內(nèi)存一般可以分為新生代、老年代和永久帶三個(gè)模塊。如下所示:

image
  • 1、新生代(Young Generation)

新生代的目標(biāo)是盡可能快速收集掉那些生命周期短的對(duì)象,一般情況下,所有新生成的對(duì)象首先都是放在新生代的。新生代內(nèi)存按照8:1:1的比例分成一個(gè)eden區(qū)和兩個(gè)Survivor(s0,s1)區(qū),大部分對(duì)象在Eden區(qū)中生成。在進(jìn)行垃圾回收時(shí),先將eden區(qū)存活對(duì)象復(fù)制到s0區(qū),然后清空eden區(qū),當(dāng)這個(gè)s0也滿了時(shí),則將eden區(qū)和s0區(qū)存對(duì)象復(fù)制到s1區(qū),然后清空eden和s0。此時(shí)s0區(qū)是空的,然后交換s0區(qū)和s1區(qū)的角色(即下次垃圾回收時(shí)會(huì)掃描Eden區(qū)和s1區(qū)),即保持s0區(qū)為空,如此往返。特別地,當(dāng)s1區(qū)也不足以存放eden區(qū)和s0區(qū)的存活對(duì)象時(shí),就將存活對(duì)象直接存放到老年代。如果老年代也滿了,就會(huì)觸發(fā)一次FullGC,也就是新生代、老年代都進(jìn)行回收。注意,新生代發(fā)生的GC也叫MinorGC,MinorGC發(fā)生頻率比較高,不一定等到Eden區(qū)滿了才觸發(fā)。

  • 2、老年代(Old Generation)

老年代存放的都是一些生命周期長(zhǎng)的對(duì)象,就像上面的所敘述的那樣,在新生代中經(jīng)歷了N次垃圾回收后仍然存活的對(duì)象就會(huì)被放到老年代中。此外,老年代的內(nèi)存也比新生代大很多,大概比例是(1:2),當(dāng)老年代滿時(shí)會(huì)觸發(fā)Major GC/Full GC,老年代對(duì)象存活時(shí)間比較長(zhǎng),因此Major GC/Full GC發(fā)生的頻率比較低。

  • 3、永久代(Permanent Generation)

永久代主要用于存放靜態(tài)文件,如Java類、方法等。永久代對(duì)垃圾回收沒(méi)有顯著影響,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class,例如使用反射、動(dòng)態(tài)代理、GCLib等bytecode框架時(shí),在這種時(shí)候需要設(shè)置一個(gè)比較大的永久代空間來(lái)存放這些運(yùn)行過(guò)程中新增的類。

  • 4、小結(jié)

由于對(duì)象進(jìn)行了分代處理,因此垃圾回收區(qū)域、時(shí)間也不一樣。垃圾回收有兩種類型,Minor GC 和Major GC/Full GC。

Minor GC:對(duì)新生代進(jìn)行回收,不會(huì)影響到老年代。因?yàn)樾律腏ava對(duì)象大多死亡頻繁,所以 Minor GC 非常頻繁,一般在這里使用速度快、效率高的算法,使垃圾回收能盡快完成。

Major GC/Full GC:對(duì)整個(gè)堆進(jìn)行回收,包括新生代和老年代。由于Full GC需要對(duì)整個(gè)堆進(jìn)行回收,所以比Minor GC要慢,因此應(yīng)該盡可能減少Full GC的次數(shù),導(dǎo)致Full GC的原因包括:老年代要被寫滿、永久代被寫滿和System.gc()被顯式調(diào)用等。

更多垃圾收集算法參考這篇文章
參考

?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 原文閱讀 前言 這段時(shí)間懈怠了,罪過(guò)! 最近看到有同事也開(kāi)始用上了微信公眾號(hào)寫博客了,挺好的~給他們點(diǎn)贊,這博客我...
    碼農(nóng)戲碼閱讀 6,163評(píng)論 2 31
  • JVM架構(gòu) 當(dāng)一個(gè)程序啟動(dòng)之前,它的class會(huì)被類裝載器裝入方法區(qū)(Permanent區(qū)),執(zhí)行引擎讀取方法區(qū)的...
    cocohaifang閱讀 1,852評(píng)論 0 7
  • jvm原理 Java虛擬機(jī)是整個(gè)java平臺(tái)的基石,是java技術(shù)實(shí)現(xiàn)硬件無(wú)關(guān)和操作系統(tǒng)無(wú)關(guān)的關(guān)鍵環(huán)節(jié),是java...
    AI喬治閱讀 17,588評(píng)論 21 486
  • 1 CPU和內(nèi)存的交互 了解jvm內(nèi)存模型前,了解下cpu和計(jì)算機(jī)內(nèi)存的交互情況?!疽?yàn)镴ava虛擬機(jī)內(nèi)存模型定義...
    Garwer閱讀 374,451評(píng)論 54 551
  • 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別 內(nèi)存溢出:out of memory,是指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用,...
    Aimerwhy閱讀 808評(píng)論 0 1

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