Java虛擬機(jī)詳解——JVM常見(jiàn)問(wèn)題總結(jié)

2021-03-15    分類(lèi): 網(wǎng)站建設(shè)

聲明:本文只是做一個(gè)總結(jié),有關(guān)jvm的詳細(xì)知識(shí)可以參考本人之前的系列文章,尤其是那篇:Java虛擬機(jī)詳解04—-GC算法和種類(lèi)。那篇文章和本文是面試時(shí)的重點(diǎn)。
面試必問(wèn)關(guān)鍵詞:JVM垃圾回收、類(lèi)加載機(jī)制。

JVM

先把本文的目錄畫(huà)一個(gè)思維導(dǎo)圖:(圖的源文件在本文末尾)

一、Java引用的四種狀態(tài):

強(qiáng)引用:
  用的最廣。我們平時(shí)寫(xiě)代碼時(shí),new一個(gè)Object存放在堆內(nèi)存,然后用一個(gè)引用指向它,這就是強(qiáng)引用。
*  如果一個(gè)對(duì)象具有強(qiáng)引用,那垃圾回收器絕不會(huì)回收它*。當(dāng)內(nèi)存空間不足,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤,使程序異常終止,也不會(huì)靠隨意回收具有強(qiáng)引用的對(duì)象來(lái)解決內(nèi)存不足的問(wèn)題。
軟引用:
  如果一個(gè)對(duì)象只具有軟引用,則內(nèi)存空間足夠時(shí),垃圾回收器就不會(huì)回收它;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。(備注:如果內(nèi)存不足,隨時(shí)有可能被回收。)
  只要垃圾回收器沒(méi)有回收它,該對(duì)象就可以被程序使用。軟引用可用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高速緩存。
弱引用:
  弱引用與軟引用的區(qū)別在于:只具有弱引用的對(duì)象擁有更短暫的生命周期。
*  每次執(zhí)行GC的時(shí)候,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。不過(guò),由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線(xiàn)程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象*。
虛引用:
  “虛引用”顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。
  虛引用主要用來(lái)跟蹤對(duì)象被垃圾回收器回收的活動(dòng)。
 

二、Java中的內(nèi)存劃分:

Java程序在運(yùn)行時(shí),需要在內(nèi)存中的分配空間。為了提高運(yùn)算效率,就對(duì)數(shù)據(jù)進(jìn)行了不同空間的劃分,因?yàn)槊恳黄瑓^(qū)域都有特定的處理數(shù)據(jù)方式和內(nèi)存管理方式。

上面這張圖就是jvm運(yùn)行時(shí)的狀態(tài)。具體劃分為如下5個(gè)內(nèi)存空間:(非常重要)
- 程序計(jì)數(shù)器:保證線(xiàn)程切換后能恢復(fù)到原來(lái)的執(zhí)行位置
- 虛擬機(jī)棧:(棧內(nèi)存)為虛擬機(jī)執(zhí)行java方法服務(wù):方法被調(diào)用時(shí)創(chuàng)建棧幀–>局部變量表->局部變量、對(duì)象引用
- 本地方法棧:為虛擬機(jī)執(zhí)使用到的Native方法服務(wù)
- 堆內(nèi)存:存放所有new出來(lái)的東西
- 方法區(qū):存儲(chǔ)被虛擬機(jī)加載的類(lèi)信息、常量、靜態(tài)常量、靜態(tài)方法等。
- 運(yùn)行時(shí)常量池(方法區(qū)的一部分)

GC對(duì)它們的回收:
內(nèi)存區(qū)域中的程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧這3個(gè)區(qū)域隨著線(xiàn)程而生,線(xiàn)程而滅;棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊地執(zhí)行著出棧和入棧的操作,每個(gè)棧幀中分配多少內(nèi)存基本是在類(lèi)結(jié)構(gòu)確定下來(lái)時(shí)就已知的。在這幾個(gè)區(qū)域不需要過(guò)多考慮回收的問(wèn)題,因?yàn)榉椒ńY(jié)束或者線(xiàn)程結(jié)束時(shí),內(nèi)存自然就跟著回收了。
GC回收的主要對(duì)象:而J**ava堆和方法區(qū)**則不同,一個(gè)接口中的多個(gè)實(shí)現(xiàn)類(lèi)需要的內(nèi)存可能不同,一個(gè)方法中的多個(gè)分支需要的內(nèi)存也可能不一樣,我們只有在程序處于運(yùn)行期間時(shí)才能知道會(huì)創(chuàng)建哪些對(duì)象,這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的,GC關(guān)注的也是這部分內(nèi)存,后面的文章中如果涉及到“內(nèi)存”分配與回收也僅指著一部分內(nèi)存。

1、程序計(jì)數(shù)器:(線(xiàn)程私有)

每個(gè)線(xiàn)程擁有一個(gè)程序計(jì)數(shù)器,在線(xiàn)程創(chuàng)建時(shí)創(chuàng)建,
指向下一條指令的地址
執(zhí)行本地方法時(shí),其值為undefined

說(shuō)的通俗一點(diǎn),我們知道,Java是支持多線(xiàn)程的,程序先去執(zhí)行A線(xiàn)程,執(zhí)行到一半,然后就去執(zhí)行B線(xiàn)程,然后又跑回來(lái)接著執(zhí)行A線(xiàn)程,那程序是怎么記住A線(xiàn)程已經(jīng)執(zhí)行到哪里了呢?這就需要程序計(jì)數(shù)器了。因此,為了線(xiàn)程切換后能夠恢復(fù)到正確的執(zhí)行位置,每條線(xiàn)程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器,這塊兒屬于“線(xiàn)程私有”的內(nèi)存。

2、Java虛擬機(jī)棧:(線(xiàn)程私有)

每個(gè)方法被調(diào)用的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表、操作棧、動(dòng)態(tài)鏈接、方法出口等信息。局部變量表存放的是:編譯期可知的基本數(shù)據(jù)類(lèi)型、對(duì)象引用類(lèi)型。
每個(gè)方法被調(diào)用直到執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)中從入棧到出棧的過(guò)程。
在Java虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常情況:
 ?。?)如果線(xiàn)程請(qǐng)求的棧深度太深,超出了虛擬機(jī)所允許的深度,就會(huì)出現(xiàn)StackOverFlowError(比如無(wú)限遞歸。因?yàn)槊恳粚訔颊加靡欢臻g,而 Xss 規(guī)定了棧的大空間,超出這個(gè)值就會(huì)報(bào)錯(cuò))
  (2)虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,如果擴(kuò)展到無(wú)法申請(qǐng)足夠的內(nèi)存空間,會(huì)出現(xiàn)OOM

3、本地方法棧:
(1)本地方法棧與java虛擬機(jī)棧作用非常類(lèi)似,其區(qū)別是:java虛擬機(jī)棧是為虛擬機(jī)執(zhí)行java方法服務(wù)的,而本地方法棧則為虛擬機(jī)執(zhí)使用到的Native方法服務(wù)。
(2)Java虛擬機(jī)沒(méi)有對(duì)本地方法棧的使用和數(shù)據(jù)結(jié)構(gòu)做強(qiáng)制規(guī)定,Sun HotSpot虛擬機(jī)就把java虛擬機(jī)棧和本地方法棧合二為一。
(3)本地方法棧也會(huì)拋出StackOverFlowError和OutOfMemoryError。

4、Java堆:即堆內(nèi)存(線(xiàn)程共享)
(1)堆是java虛擬機(jī)所管理的內(nèi)存區(qū)域中大的一塊,java堆是被所有線(xiàn)程共享的內(nèi)存區(qū)域,在java虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,堆內(nèi)存的唯一目的就是存放對(duì)象實(shí)例幾乎所有的對(duì)象實(shí)例都在堆內(nèi)存分配。
(2)堆是GC管理的主要區(qū)域,從垃圾回收的角度看,由于現(xiàn)在的垃圾收集器都是采用的分代收集算法,因此java堆還可以初步細(xì)分為新生代和老年代。
(3)Java虛擬機(jī)規(guī)定,堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上連續(xù)的即可。在實(shí)現(xiàn)上既可以是固定的,也可以是可動(dòng)態(tài)擴(kuò)展的。如果在堆內(nèi)存沒(méi)有完成實(shí)例分配,并且堆大小也無(wú)法擴(kuò)展,就會(huì)拋出OutOfMemoryError異常。

5、方法區(qū):(線(xiàn)程共享)
(1)用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
(2)Sun HotSpot虛擬機(jī)把方法區(qū)叫做永久代(Permanent Generation),方法區(qū)中最終要的部分是運(yùn)行時(shí)常量池。

6、運(yùn)行時(shí)常量池:
(1)運(yùn)行時(shí)常量池是方法區(qū)的一部分,自然受到方法區(qū)內(nèi)存的限制,當(dāng)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí)就會(huì)拋出OutOfMemoryError異常。

三、Java對(duì)象在內(nèi)存中的狀態(tài):

可達(dá)的/可觸及的:
  Java對(duì)象被創(chuàng)建后,如果被一個(gè)或多個(gè)變量引用,那就是可達(dá)的。即從根節(jié)點(diǎn)可以觸及到這個(gè)對(duì)象。
  其實(shí)就是從根節(jié)點(diǎn)掃描,只要這個(gè)對(duì)象在引用鏈中,那就是可觸及的。
可恢復(fù)的:
  Java對(duì)象不再被任何變量引用就進(jìn)入了可恢復(fù)狀態(tài)。
  在回收該對(duì)象之前,該對(duì)象的finalize()方法進(jìn)行資源清理。如果在finalize()方法中重新讓變量引用該對(duì)象,則該對(duì)象再次變?yōu)榭蛇_(dá)狀態(tài),否則該對(duì)象進(jìn)入不可達(dá)狀態(tài)
不可達(dá)的:
  Java對(duì)象不被任何變量引用,且系統(tǒng)在調(diào)用對(duì)象的finalize()方法后依然沒(méi)有使該對(duì)象變成可達(dá)狀態(tài)(該對(duì)象依然沒(méi)有被變量引用),那么該對(duì)象將變成不可達(dá)狀態(tài)。
  當(dāng)Java對(duì)象處于不可達(dá)狀態(tài)時(shí),系統(tǒng)才會(huì)真正回收該對(duì)象所占有的資源。

四、判斷對(duì)象死亡的兩種常用算法:

當(dāng)對(duì)象不被引用的時(shí)候,這個(gè)對(duì)象就是死亡的,等待GC進(jìn)行回收。
1、引用計(jì)數(shù)算法:
概念:
  給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。
但是:
  主流的java虛擬機(jī)并沒(méi)有選用引用計(jì)數(shù)算法來(lái)管理內(nèi)存,其中最主要的原因是:它很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題。
優(yōu)點(diǎn):
  算法的實(shí)現(xiàn)簡(jiǎn)單,判定效率也高,大部分情況下是一個(gè)不錯(cuò)的算法。很多地方應(yīng)用到它
缺點(diǎn):

引用和去引用伴隨加法和減法,影響性能
致命的缺陷:對(duì)于循環(huán)引用的對(duì)象無(wú)法進(jìn)行回收

2、根搜索算法:(jvm采用的算法)
概念:
  設(shè)立若干種根對(duì)象,當(dāng)任何一個(gè)根對(duì)象(GC Root)到某一個(gè)對(duì)象均不可達(dá)時(shí),則認(rèn)為這個(gè)對(duì)象是可以被回收的。
注:這里提到,設(shè)立若干種根對(duì)象,當(dāng)任何一個(gè)根對(duì)象到某一個(gè)對(duì)象均不可達(dá)時(shí),則認(rèn)為這個(gè)對(duì)象是可以被回收的。我們?cè)诤竺娼榻B標(biāo)記-清理算法/標(biāo)記整理算法時(shí),也會(huì)一直強(qiáng)調(diào)從根節(jié)點(diǎn)開(kāi)始,對(duì)所有可達(dá)對(duì)象做一次標(biāo)記,那什么叫做可達(dá)呢?
可達(dá)性分析:
  從根(GC Roots)的對(duì)象作為起始點(diǎn),開(kāi)始向下搜索,搜索所走過(guò)的路徑稱(chēng)為“引用鏈”,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連(用圖論的概念來(lái)講,就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí),則證明此對(duì)象是不可用的。

如上圖所示,ObjectD和ObjectE是互相關(guān)聯(lián)的,但是由于GC roots到這兩個(gè)對(duì)象不可達(dá),所以最終D和E還是會(huì)被當(dāng)做GC的對(duì)象,上圖若是采用引用計(jì)數(shù)法,則A-E五個(gè)對(duì)象都不會(huì)被回收。

根(GC Roots):
說(shuō)到GC roots(GC根),在JAVA語(yǔ)言中,可以當(dāng)做GC roots的對(duì)象有以下幾種:

1、棧(棧幀中的本地變量表)中引用的對(duì)象。
2、方法區(qū)中的靜態(tài)成員。
3、方法區(qū)中的常量引用的對(duì)象(全局變量)
4、本地方法棧中JNI(一般說(shuō)的Native方法)引用的對(duì)象。

注:第一和第四種都是指的方法的本地變量表,第二種表達(dá)的意思比較清晰,第三種主要指的是聲明為final的常量值。
在根搜索算法的基礎(chǔ)上,現(xiàn)代虛擬機(jī)的實(shí)現(xiàn)當(dāng)中,垃圾搜集的算法主要有三種,分別是標(biāo)記-清除算法、復(fù)制算法、標(biāo)記-整理算法。這三種算法都擴(kuò)充了根搜索算法,不過(guò)它們理解起來(lái)還是非常好理解的。

五、垃圾回收算法:

1、標(biāo)記-清除算法:
概念:
標(biāo)記階段:先通過(guò)根節(jié)點(diǎn),標(biāo)記所有從根節(jié)點(diǎn)開(kāi)始的可達(dá)對(duì)象。因此,未被標(biāo)記的對(duì)象就是未被引用的垃圾對(duì)象;
清除階段:清除所有未被標(biāo)記的對(duì)象。

缺點(diǎn):
標(biāo)記和清除的過(guò)程效率不高(標(biāo)記和清除都需要從頭遍歷到尾)
標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的碎片。

2、復(fù)制算法:(新生代的GC)
概念:
  將原有的內(nèi)存空間分為兩塊,每次只使用其中一塊,在垃圾回收時(shí),將正在使用的內(nèi)存中的存活對(duì)象復(fù)制到未使用的內(nèi)存塊中,然后清除正在使用的內(nèi)存塊中的所有對(duì)象。
優(yōu)點(diǎn):

這樣使得每次都是對(duì)整個(gè)半?yún)^(qū)進(jìn)行回收,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等情況
只要移動(dòng)堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行效率高

缺點(diǎn):空間的浪費(fèi)
  從以上描述不難看出,復(fù)制算法要想使用,最起碼對(duì)象的存活率要非常低才行。
  現(xiàn)在的商業(yè)虛擬機(jī)都采用這種收集算法來(lái)回收新生代,新生代中的對(duì)象98%都是“朝生夕死”的,所以并不需要按照1:1的比例來(lái)劃分內(nèi)存空間,而是將內(nèi)存分為一塊比較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當(dāng)回收時(shí),將Eden和Survivor中還存活著的對(duì)象一次性地復(fù)制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過(guò)的Survivor空間。HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1,也就是說(shuō),每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%(80%+10%),只有10%的空間會(huì)被浪費(fèi)。
當(dāng)然,98%的對(duì)象可回收只是一般場(chǎng)景下的數(shù)據(jù),我們沒(méi)有辦法保證每次回收都只有不多于10%的對(duì)象存活,當(dāng)Survivor空間不夠用時(shí),需要依賴(lài)于老年代進(jìn)行分配擔(dān)保,所以大對(duì)象直接進(jìn)入老年代。整個(gè)過(guò)程如下圖所示:

3、標(biāo)記-整理算法:(老年代的GC)
復(fù)制算法在對(duì)象存活率高的時(shí)候要進(jìn)行較多的復(fù)制操作,效率將會(huì)降低,所以在老年代中一般不能直接選用這種算法。
概念:
標(biāo)記階段:先通過(guò)根節(jié)點(diǎn),標(biāo)記所有從根節(jié)點(diǎn)開(kāi)始的可達(dá)對(duì)象。因此,未被標(biāo)記的對(duì)象就是未被引用的垃圾對(duì)象
整理階段:將*將所有的存活對(duì)象壓縮到內(nèi)存的一端*;之后,清理邊界外所有的空間

優(yōu)點(diǎn):
  不會(huì)產(chǎn)生內(nèi)存碎片。
缺點(diǎn):
  在標(biāo)記的基礎(chǔ)之上還需要進(jìn)行對(duì)象的移動(dòng),成本相對(duì)較高,效率也不高。

它們的區(qū)別如下:(>表示前者要優(yōu)于后者,=表示兩者效果一樣)
(1)效率:復(fù)制算法 > 標(biāo)記/整理算法 > 標(biāo)記/清除算法(此處的效率只是簡(jiǎn)單的對(duì)比時(shí)間復(fù)雜度,實(shí)際情況不一定如此)。
(2)內(nèi)存整齊度:復(fù)制算法=標(biāo)記/整理算法>標(biāo)記/清除算法。
(3)內(nèi)存利用率:標(biāo)記/整理算法=標(biāo)記/清除算法>復(fù)制算法。

注1:標(biāo)記-整理算法不僅可以彌補(bǔ)標(biāo)記-清除算法當(dāng)中,內(nèi)存區(qū)域分散的缺點(diǎn),也消除了復(fù)制算法當(dāng)中,內(nèi)存減半的高額代價(jià)。
注2:可以看到標(biāo)記/清除算法是比較落后的算法了,但是后兩種算法卻是在此基礎(chǔ)上建立的。
注3:時(shí)間與空間不可兼得。

4、分代收集算法:
  當(dāng)前商業(yè)虛擬機(jī)的GC都是采用的“分代收集算法”,這并不是什么新的思想,只是根據(jù)對(duì)象的存活周期的不同將內(nèi)存劃分為幾塊兒。一般是把Java堆分為新生代和老年代:短命對(duì)象歸為新生代,長(zhǎng)命對(duì)象歸為老年代。
存活率低:少量對(duì)象存活,適合復(fù)制算法:在新生代中,每次GC時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,只有少量存活(新生代中98%的對(duì)象都是“朝生夕死”),那就選用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成GC。
存活率高:大量對(duì)象存活,適合用標(biāo)記-清理/標(biāo)記-整理:在老年代中,因?yàn)閷?duì)象存活率高、沒(méi)有額外空間對(duì)他進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記-清理”/“標(biāo)記-整理”算法進(jìn)行GC。

注:老年代的對(duì)象中,有一小部分是因?yàn)樵谛律厥諘r(shí),老年代做擔(dān)保,進(jìn)來(lái)的對(duì)象;絕大部分對(duì)象是因?yàn)楹芏啻蜧C都沒(méi)有被回收掉而進(jìn)入老年代。

六、垃圾收集器:

如果說(shuō)收集算法時(shí)內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。
雖然我們?cè)趯?duì)各種收集器進(jìn)行比較,但并非為了挑出一個(gè)最好的收集器。因?yàn)橹钡浆F(xiàn)在位置還沒(méi)有最好的收集器出現(xiàn),更加沒(méi)有萬(wàn)能的收集器,所以我們選擇的只是對(duì)具體應(yīng)用最合適的收集器。
1、Serial收集器:(串行收集器)
這個(gè)收集器是一個(gè)單線(xiàn)程的收集器,但它的單線(xiàn)程的意義并不僅僅說(shuō)明它只會(huì)使用一個(gè)CPU或一條收集線(xiàn)程去完成垃圾收集工作,更重要的是在它進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線(xiàn)程(Stop-The-World:將用戶(hù)正常工作的線(xiàn)程全部暫停掉),直到它收集結(jié)束。收集器的運(yùn)行過(guò)程如下圖所示:


上圖中:
- 新生代采用復(fù)制算法,Stop-The-World
- 老年代采用標(biāo)記-整理算法,Stop-The-World

當(dāng)它進(jìn)行GC工作的時(shí)候,雖然會(huì)造成Stop-The-World,但它存在有存在的原因:正是因?yàn)樗暮?jiǎn)單而高效(與其他收集器的單線(xiàn)程比),對(duì)于限定單個(gè)CPU的環(huán)境來(lái)說(shuō),沒(méi)有線(xiàn)程交互的開(kāi)銷(xiāo),專(zhuān)心做GC,自然可以獲得高的單線(xiàn)程手機(jī)效率。所以Serial收集器對(duì)于運(yùn)行在client模式下是一個(gè)很好的選擇(它依然是虛擬機(jī)運(yùn)行在client模式下的默認(rèn)新生代收集器)。

**2、ParNew收集器:**Serial收集器的多線(xiàn)程版本(使用多條線(xiàn)程進(jìn)行GC)
  ParNew收集器是Serial收集器的多線(xiàn)程版本。
*  它是運(yùn)行在server模式下的選新生代收集器*,除了Serial收集器外,目前只有它能與CMS收集器配合工作。CMS收集器是一個(gè)被認(rèn)為具有劃時(shí)代意義的并發(fā)收集器,因此如果有一個(gè)垃圾收集器能和它一起搭配使用讓其更加好,那這個(gè)收集器必然也是一個(gè)不可或缺的部分了。收集器的運(yùn)行過(guò)程如下圖所示:

上圖中:
- 新生代采用復(fù)制算法,Stop-The-World
- 老年代采用標(biāo)記-整理算法,Stop-The-World

3、ParNew *Scanvenge*收集器
  類(lèi)似ParNew,但更加關(guān)注吞吐量。目標(biāo)是:達(dá)到一個(gè)可控制吞吐量的收集器。
停頓時(shí)間和吞吐量不可能同時(shí)調(diào)優(yōu)。我們一方買(mǎi)希望停頓時(shí)間少,另外一方面希望吞吐量高,其實(shí)這是矛盾的。因?yàn)椋涸贕C的時(shí)候,垃圾回收的工作總量是不變的,如果將停頓時(shí)間減少,那頻率就會(huì)提高;既然頻率提高了,說(shuō)明就會(huì)頻繁的進(jìn)行GC,那吞吐量就會(huì)減少,性能就會(huì)降低。
吞吐量:CPU用于用戶(hù)代碼的時(shí)間/CPU總消耗時(shí)間的比值,即=運(yùn)行用戶(hù)代碼的時(shí)間/(運(yùn)行用戶(hù)代碼時(shí)間+垃圾收集時(shí)間)。比如,虛擬機(jī)總共運(yùn)行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

4、G1收集器:
  是當(dāng)今收集器發(fā)展的最前言成果之一,知道jdk1.7,sun公司才認(rèn)為它達(dá)到了足夠成熟的商用程度。
優(yōu)點(diǎn):
  它大的優(yōu)點(diǎn)是結(jié)合了空間整合,不會(huì)產(chǎn)生大量的碎片,也降低了進(jìn)行g(shù)c的頻率。
  二是可以讓使用者明確指定指定停頓時(shí)間。(可以指定一個(gè)最小時(shí)間,超過(guò)這個(gè)時(shí)間,就不會(huì)進(jìn)行回收了)
它有了這么高效率的原因之一就是:對(duì)垃圾回收進(jìn)行了劃分優(yōu)先級(jí)的操作,這種有優(yōu)先級(jí)的區(qū)域回收方式保證了它的高效率。
如果你的應(yīng)用追求停頓,那G1現(xiàn)在已經(jīng)可以作為一個(gè)可嘗試的選擇;如果你的應(yīng)用追求吞吐量,那G1并不會(huì)為你帶來(lái)什么特別的好處。
注:以上所有的收集器當(dāng)中,當(dāng)執(zhí)行GC時(shí),都會(huì)stop the world,但是下面的CMS收集器卻不會(huì)這樣。

5、CMS收集器:(老年代收集器)
CMS收集器(Concurrent Mark Sweep:并發(fā)標(biāo)記清除)是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。適合應(yīng)用在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)器上,這類(lèi)應(yīng)用尤其重視服務(wù)器的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間最短。
CMS收集器運(yùn)行過(guò)程:(著重實(shí)現(xiàn)了標(biāo)記的過(guò)程)
(1)初始標(biāo)記
  根可以直接關(guān)聯(lián)到的對(duì)象
  速度快
(2)并發(fā)標(biāo)記(和用戶(hù)線(xiàn)程一起)
  主要標(biāo)記過(guò)程,標(biāo)記全部對(duì)象
(3)重新標(biāo)記
  由于并發(fā)標(biāo)記時(shí),用戶(hù)線(xiàn)程依然運(yùn)行,因此在正式清理前,再做修正
(4)并發(fā)清除(和用戶(hù)線(xiàn)程一起)
  基于標(biāo)記結(jié)果,直接清理對(duì)象
整個(gè)過(guò)程如下圖所示:

上圖中,初始標(biāo)記和重新標(biāo)記時(shí),需要stop the world。整個(gè)過(guò)程中耗時(shí)最長(zhǎng)的是并發(fā)標(biāo)記和并發(fā)清除,這兩個(gè)過(guò)程都可以和用戶(hù)線(xiàn)程一起工作。


*既然標(biāo)記-清除算法會(huì)造成內(nèi)存空間的碎片化,CMS收集器為什么使用標(biāo)記清除算法而不是使用標(biāo)記整理算法:*
答案:
  CMS收集器更加關(guān)注停頓,它在做GC的時(shí)候是和用戶(hù)線(xiàn)程一起工作的(并發(fā)執(zhí)行),如果使用標(biāo)記整理算法的話(huà),那么在清理的時(shí)候就會(huì)去移動(dòng)可用對(duì)象的內(nèi)存空間,那么應(yīng)用程序的線(xiàn)程就很有可能找不到應(yīng)用對(duì)象在哪里。

七、Java堆內(nèi)存劃分:

根據(jù)對(duì)象的存活率(年齡),Java對(duì)內(nèi)存劃分為3種:新生代、老年代、永久代:
1、新生代:
比如我們?cè)诜椒ㄖ腥ew一個(gè)對(duì)象,那這方法調(diào)用完畢后,對(duì)象就會(huì)被回收,這就是一個(gè)典型的新生代對(duì)象。
現(xiàn)在的商業(yè)虛擬機(jī)都采用這種收集算法來(lái)回收新生代,新生代中的對(duì)象98%都是“朝生夕死”的,所以并不需要按照1:1的比例來(lái)劃分內(nèi)存空間,而是將內(nèi)存分為一塊比較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當(dāng)回收時(shí),將Eden和Survivor中還存活著的對(duì)象一次性地復(fù)制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過(guò)的Survivor空間。HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1,也就是說(shuō),每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%(80%+10%),只有10%的空間會(huì)被浪費(fèi)。
當(dāng)然,98%的對(duì)象可回收只是一般場(chǎng)景下的數(shù)據(jù),我們沒(méi)有辦法保證每次回收都只有不多于10%的對(duì)象存活,當(dāng)Survivor空間不夠用時(shí),需要依賴(lài)于老年代進(jìn)行分配擔(dān)保,所以大對(duì)象直接進(jìn)入老年代。同時(shí),長(zhǎng)期存活的對(duì)象將進(jìn)入老年代(虛擬機(jī)給每個(gè)對(duì)象定義一個(gè)年齡計(jì)數(shù)器)。
來(lái)看下面這張圖:

Minor GC和Full GC:
GC分為兩種:Minor GC和Full GC
Minor GC:
  Minor GC是發(fā)生在新生代中的垃圾收集動(dòng)作,采用的是復(fù)制算法。
對(duì)象在Eden和From區(qū)出生后,在經(jīng)過(guò)一次Minor GC后,如果對(duì)象還存活,并且能夠被to區(qū)所容納,那么在使用復(fù)制算法時(shí)這些存活對(duì)象就會(huì)被復(fù)制到to區(qū)域,然后清理掉Eden區(qū)和from區(qū),并將這些對(duì)象的年齡設(shè)置為1,以后對(duì)象在Survivor區(qū)每熬過(guò)一次Minor GC,就將對(duì)象的年齡+1,當(dāng)對(duì)象的年齡達(dá)到某個(gè)值時(shí)(默認(rèn)是15歲,可以通過(guò)參數(shù) –XX:MaxTenuringThreshold設(shè)置),這些對(duì)象就會(huì)成為老年代。
但這也是不一定的,對(duì)于一些較大的對(duì)象(即需要分配一塊較大的連續(xù)內(nèi)存空間)則是直接進(jìn)入老年代
Full GC:
  Full GC是發(fā)生在老年代的垃圾收集動(dòng)作,采用的是標(biāo)記-清除/整理算法。
老年代里的對(duì)象幾乎都是在Survivor區(qū)熬過(guò)來(lái)的,不會(huì)那么容易死掉。因此Full GC發(fā)生的次數(shù)不會(huì)有Minor GC那么頻繁,并且做一次Full GC要比做一次Minor GC的時(shí)間要長(zhǎng)。
另外,如果采用的是標(biāo)記-清除算法的話(huà)會(huì)產(chǎn)生許多碎片,此后如果需要為較大的對(duì)象分配內(nèi)存空間時(shí),若無(wú)法找到足夠的連續(xù)的內(nèi)存空間,就會(huì)提前觸發(fā)一次GC。

2、老年代:
在新生代中經(jīng)歷了N次垃圾回收后仍然存活的對(duì)象就會(huì)被放到老年代中。而且大對(duì)象直接進(jìn)入老年代。

3、永久代:
即方法區(qū)。

八、類(lèi)加載機(jī)制:

虛擬機(jī)把描述類(lèi)的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類(lèi)型,這就是虛擬機(jī)的類(lèi)加載機(jī)制。
類(lèi)加載的過(guò)程:
包括加載、鏈接(含驗(yàn)證、準(zhǔn)備、解析)、初始化
如下圖所示:

1、加載:
  類(lèi)加載指的是將類(lèi)的class文件讀入內(nèi)存,并為之創(chuàng)建一個(gè)java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類(lèi)的數(shù)據(jù)訪(fǎng)問(wèn)的入口。
也就是說(shuō),當(dāng)程序中使用任何類(lèi)時(shí),系統(tǒng)都會(huì)為之建立一個(gè)java.lang.Class對(duì)象。具體包括以下三個(gè)部分:
(1)通過(guò)類(lèi)的全名產(chǎn)生對(duì)應(yīng)類(lèi)的二進(jìn)制數(shù)據(jù)流。(根據(jù)early load原理,如果沒(méi)找到對(duì)應(yīng)的類(lèi)文件,只有在類(lèi)實(shí)際使用時(shí)才會(huì)拋出錯(cuò)誤)
(2)分析并將這些二進(jìn)制數(shù)據(jù)流轉(zhuǎn)換為方法區(qū)方法區(qū)特定的數(shù)據(jù)結(jié)構(gòu)
(3)創(chuàng)建對(duì)應(yīng)類(lèi)的java.lang.Class對(duì)象,作為方法區(qū)的入口(有了對(duì)應(yīng)的Class對(duì)象,并不意味著這個(gè)類(lèi)已經(jīng)完成了加載鏈接)

通過(guò)使用不同的類(lèi)加載器,可以從不同來(lái)源加載類(lèi)的二進(jìn)制數(shù)據(jù),通常有如下幾種來(lái)源:
(1)從本地文件系統(tǒng)加載class文件,這是絕大部分程序的加載方式
(2)從jar包中加載class文件,這種方式也很常見(jiàn),例如jdbc編程時(shí)用到的數(shù)據(jù)庫(kù)驅(qū)動(dòng)類(lèi)就是放在jar包中,jvm可以從jar文件中直接加載該class文件
(3)通過(guò)網(wǎng)絡(luò)加載class文件
(4)把一個(gè)Java源文件動(dòng)態(tài)編譯、并執(zhí)行加載

2、鏈接:
鏈接指的是將Java類(lèi)的二進(jìn)制文件合并到j(luò)vm的運(yùn)行狀態(tài)之中的過(guò)程。在鏈接之前,這個(gè)類(lèi)必須被成功加載。
類(lèi)的鏈接包括驗(yàn)證、準(zhǔn)備、解析這三步。具體描述如下:
2.1 驗(yàn)證:
驗(yàn)證是用來(lái)確保Java類(lèi)的二進(jìn)制表示在結(jié)構(gòu)上是否完全正確(如文件格式、語(yǔ)法語(yǔ)義等)。如果驗(yàn)證過(guò)程出錯(cuò)的話(huà),會(huì)拋出java.lang.VertifyError錯(cuò)誤。
主要驗(yàn)證以下內(nèi)容:
文件格式驗(yàn)證
元數(shù)據(jù)驗(yàn)證:語(yǔ)義驗(yàn)證
字節(jié)碼驗(yàn)證

2.2 準(zhǔn)備:
  準(zhǔn)備過(guò)程則是創(chuàng)建Java類(lèi)中的靜態(tài)域(static修飾的內(nèi)容),并將這些域的值設(shè)置為默認(rèn)值,同時(shí)在方法區(qū)中分配內(nèi)存空間。準(zhǔn)備過(guò)程并不會(huì)執(zhí)行代碼。
注意這里是做默認(rèn)初始化,不是做顯式初始化。例如:
public static int value = 12;

上面的代碼中,在準(zhǔn)備階段,會(huì)給value的值設(shè)置為0(默認(rèn)初始化)。在后面的初始化階段才會(huì)給value的值設(shè)置為12(顯式初始化)。
2.3 解析:
  解析的過(guò)程就是確保這些被引用的類(lèi)能被正確的找到(將符號(hào)引用替換為直接引用)。解析的過(guò)程可能會(huì)導(dǎo)致其它的Java類(lèi)被加載。

3、初始化:
  初始化階段是類(lèi)加載過(guò)程的最

名稱(chēng)欄目:Java虛擬機(jī)詳解——JVM常見(jiàn)問(wèn)題總結(jié)
本文地址:http://www.bm7419.com/news38/105238.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、虛擬主機(jī)、網(wǎng)站營(yíng)銷(xiāo)、企業(yè)建站網(wǎng)站設(shè)計(jì)、網(wǎng)站改版

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站托管運(yùn)營(yíng)