Java面試看這一篇就夠了:JVM(上)

什么是虛擬機(jī)

Java 虛擬機(jī)(JVM)是運(yùn)行 Java 程序必不可少的機(jī)制灯萍。JVM實(shí)現(xiàn)了Java語(yǔ)言最重要的特征:平臺(tái)無(wú)關(guān)性。原理:編譯后的 Java 程序指令并不直接在硬件系統(tǒng)的 CPU 上執(zhí)行,而是由 JVM 執(zhí)行。JVM屏蔽了與具體平臺(tái)相關(guān)的信息畅形,使Java語(yǔ)言編譯程序只需要生成在JVM上運(yùn)行的目標(biāo)字節(jié)碼(.class),就可以在多種平臺(tái)上不加修改地運(yùn)行诉探。Java 虛擬機(jī)在執(zhí)行字節(jié)碼時(shí)日熬,把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令執(zhí)行。因此實(shí)現(xiàn)java平臺(tái)無(wú)關(guān)性肾胯。JVM = 類加載器classloader + 執(zhí)行引擎 execution engine + 運(yùn)行時(shí)數(shù)據(jù)區(qū)域runtime data area竖席,classloader 把硬盤(pán)上的class 文件加載到JVM中的運(yùn)行時(shí)數(shù)據(jù)區(qū)域, 但是它不負(fù)責(zé)這個(gè)類文件能否執(zhí)行,而是由執(zhí)行引擎負(fù)責(zé)的敬肚。不同平臺(tái)下需要安裝不同版本的 JVM 毕荐。


image

JVM能夠跨計(jì)算機(jī)體系結(jié)構(gòu)來(lái)執(zhí)行 Java 字節(jié)碼,主要是由于 JVM 屏蔽了與各個(gè)計(jì)算機(jī)平臺(tái)相關(guān)的軟件或者硬件之間的差異艳馒,使得與平臺(tái)相關(guān)的耦合統(tǒng)一由 JVM 提供者來(lái)實(shí)現(xiàn)憎亚。

JVM工作原理

任何一個(gè)Java類的main函數(shù)運(yùn)行都會(huì)創(chuàng)建一個(gè)JVM實(shí)例,JVM實(shí)例啟動(dòng)時(shí)默認(rèn)啟動(dòng)幾個(gè)守護(hù)線程弄慰,比如:垃圾回收的線程第美,而main方法的執(zhí)行在一個(gè)單獨(dú)的非守護(hù)線程中執(zhí)行。只要非守護(hù)線程結(jié)束JVM實(shí)例就銷毀了陆爽。那么在Java類main函數(shù)運(yùn)行過(guò)程中什往,JVM的工作原理如下:

  1. 根據(jù)系統(tǒng)環(huán)境變量,創(chuàng)建裝載JVM的環(huán)境與配置慌闭;
  2. 尋找JRE目錄别威,尋找jvm.dll,并裝載jvm.dll驴剔;
  3. 根據(jù)JVM的參數(shù)配置省古,如:內(nèi)存參數(shù),初始化jvm實(shí)例丧失;
  4. JVM實(shí)例產(chǎn)生一個(gè)引導(dǎo)類加載器實(shí)例(Bootstrap Loader)豺妓,加載Java核心庫(kù),然后引導(dǎo)類加載器自動(dòng)加載擴(kuò)展類加載器(Extended Loader)利花,加載Java擴(kuò)展庫(kù)科侈,最后擴(kuò)展類加載器自動(dòng)加載系統(tǒng)類加載器(AppClass Loader),加載當(dāng)前的Java類炒事;
  5. 當(dāng)前Java類加載至內(nèi)存后,會(huì)經(jīng)過(guò) 驗(yàn)證蔫慧、準(zhǔn)備挠乳、解析三步,將Java類中的類型信息、屬性信息睡扬、常量池存放在方法區(qū)內(nèi)存中盟蚣,方法指令直接保存到棧內(nèi)存中,如:main函數(shù)卖怜;
  6. 執(zhí)行引擎開(kāi)始執(zhí)行棧內(nèi)存中指令屎开,由于main函數(shù)是靜態(tài)方法,所以不需要傳入實(shí)例马靠,在類加載完畢之后奄抽,直接執(zhí)行main方法指令;
  7. main函數(shù)執(zhí)行主線程結(jié)束甩鳄,隨之守護(hù)線程銷毀逞度,最后JVM實(shí)例被銷毀;

JVM由哪些部分組成

image
  • 類加載器:在 JVM 啟動(dòng)時(shí)或者類運(yùn)行時(shí)將需要的 class 加載到 JVM 中妙啃。
  • 內(nèi)存區(qū):將內(nèi)存劃分成若干個(gè)區(qū)以模擬實(shí)際機(jī)器上的存儲(chǔ)档泽、記錄和調(diào)度功能模塊,如實(shí)際機(jī)器上的各種功能的寄存器或者 PC 指針的記錄器等揖赴。
  • 執(zhí)行引擎:執(zhí)行class文件中包含的字節(jié)碼指令馆匿,相當(dāng)于實(shí)際機(jī)器上的CPU。
  • 本地方法調(diào)用:調(diào)用 C 或 C++ 實(shí)現(xiàn)的本地方法的代碼返回結(jié)果燥滑。

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

該圖是基于 JDK6 版本的運(yùn)行內(nèi)存的分類甜熔。

image
  • 程序計(jì)數(shù)器:線程私有,不存在內(nèi)存溢出的情況突倍,是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器腔稀,當(dāng)線程在執(zhí)行一個(gè)Java方法時(shí),該計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址羽历,當(dāng)線程在執(zhí)行的是Native方法時(shí)焊虏,該計(jì)數(shù)器的值為空。
  • Java虛擬機(jī)棧(棧內(nèi)存):線程私有秕磷,描述的是java方法執(zhí)行的內(nèi)存模型诵闭,每個(gè)方法執(zhí)行時(shí)會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表澎嚣、操作數(shù)棧疏尿、動(dòng)態(tài)鏈接、方法返回地址等信息易桃,每一個(gè)方法從調(diào)用到執(zhí)行完畢的過(guò)程褥琐,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)中入棧到出棧的過(guò)程。
  • 本地方法棧:和Java虛擬機(jī)棧的作用類似晤郑,區(qū)別是該區(qū)域?yàn)镴VM提供使用 Native方法的服務(wù)敌呈。
  • Java堆:所有線程共享贸宏,幾乎所有的對(duì)象實(shí)例和數(shù)組都在這里分配內(nèi)存。是垃圾收集器管理的主要區(qū)域磕洪。目前主要的垃圾回收算法都是分代收集算法吭练,所以 Java 堆中還可以細(xì)分為:新生代和老年代;再細(xì)致一點(diǎn)的有 Eden 空間析显、From Survivor 空間鲫咽、To Survivor 空間等,默認(rèn)情況下新生代按照 8:1:1 的比例來(lái)分配谷异。
    根據(jù) Java 虛擬機(jī)規(guī)范的規(guī)定分尸,Java 堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可晰绎,就像我們的磁盤(pán)一樣寓落。
  • 方法區(qū):線程共享,用于存儲(chǔ)被虛擬機(jī)加載的類信息荞下、常量伶选、靜態(tài)變量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù)尖昏。Sun HotSpot虛擬機(jī)中方法區(qū)又稱為永久代仰税。運(yùn)行時(shí)常量池是方法區(qū)的一部分,用于存放編譯器生成的各種字面量和符號(hào)引用抽诉。
  • 直接內(nèi)存:不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分陨簇,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,它直接從操作系統(tǒng)中分配迹淌,不受Java堆大小的限制河绽,但是會(huì)受到本機(jī)總內(nèi)存的大小及處理器尋址空間的限制,因此它也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)唉窃。在JDK1.4中新引入了NIO機(jī)制耙饰,使用Native函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作纹份,可以直接從操作系統(tǒng)中分配直接內(nèi)存苟跪,即在堆外分配內(nèi)存,這樣能在一些場(chǎng)景中提高性能蔓涧,因?yàn)楸苊饬嗽贘ava堆和Native堆中來(lái)回復(fù)制數(shù)據(jù)件已。直接內(nèi)存也叫做堆外內(nèi)存。

直接內(nèi)存(堆外內(nèi)存)與堆內(nèi)存比較元暴?

  1. 直接內(nèi)存申請(qǐng)空間耗費(fèi)更高的性能篷扩,當(dāng)頻繁申請(qǐng)到一定量時(shí)尤為明顯。
  2. 直接內(nèi)存 IO 讀寫(xiě)的性能要優(yōu)于普通的堆內(nèi)存昨寞,在多次讀寫(xiě)操作的情況下差異明顯瞻惋。

JDK后續(xù)版本對(duì)方法區(qū)做的調(diào)整
JDK7:的改變:存儲(chǔ)在永久代的部分?jǐn)?shù)據(jù)(常量池和靜態(tài)變量)就已經(jīng)轉(zhuǎn)移到了 Java Heap厦滤。但永久代仍存在于 JDK7 中援岩,但是并沒(méi)完全移除歼狼。
JDK8 的改變:廢棄 PermGen(永久代),新增 Metaspace(元數(shù)據(jù)區(qū))享怀。方法區(qū)在 Metaspace 中羽峰。MetaSpace 大小默認(rèn)沒(méi)有限制,一般根據(jù)系統(tǒng)內(nèi)存的大小添瓷。JVM 會(huì)動(dòng)態(tài)改變此值梅屉。

為什么要廢棄永久代?
由于永久代內(nèi)存經(jīng)常不夠用或發(fā)生內(nèi)存泄露鳞贷,字符串存在永久代中坯汤,容易出現(xiàn)性能問(wèn)題和內(nèi)存溢出。類及方法的信息等比較難確定其大小搀愧,因此對(duì)于永久代的大小指定比較困難惰聂,太小容易出現(xiàn)永久代溢出皆愉,太大則容易導(dǎo)致老年代溢出蚂踊。

Java 內(nèi)存堆和棧區(qū)別眉踱?
棧內(nèi)存用來(lái)存儲(chǔ)基本類型的變量和對(duì)象的引用變量歌粥;堆內(nèi)存用來(lái)存儲(chǔ)Java中的對(duì)象歹苦,無(wú)論是成員變量缠沈,局部變量攻锰,還是類變量揩魂,它們指向的對(duì)象都存儲(chǔ)在堆內(nèi)存中饲趋。棧內(nèi)存歸屬于單個(gè)線程拐揭,每個(gè)線程都會(huì)有一個(gè)棧內(nèi)存,其存儲(chǔ)的變量只能在其所屬線程中可見(jiàn)奕塑,即棧內(nèi)存可以理解成線程的私有內(nèi)存堂污;堆內(nèi)存中的對(duì)象對(duì)所有線程可見(jiàn)。堆內(nèi)存中的對(duì)象可以被所有線程訪問(wèn)爵川。

image

每個(gè)區(qū)域可能造成的內(nèi)存溢出現(xiàn)象

  • 堆內(nèi)存OutOfMemoryError:只要不斷創(chuàng)建對(duì)象并且對(duì)象不被回收敷鸦,那么對(duì)象數(shù)量達(dá)到最大堆容量限制后就會(huì)產(chǎn)生內(nèi)存溢出異常了。
  • 棧溢出(StockOverflowError 和 OutOfMemoryError):
    • 方法調(diào)用的深度太深寝贡,就會(huì)產(chǎn)生棧溢出扒披。我們只要寫(xiě)一個(gè)無(wú)限調(diào)用自己的方法,就會(huì)出現(xiàn)方法調(diào)用的深度太深的場(chǎng)景圃泡。
    • 過(guò)不斷創(chuàng)建線程的方式可以產(chǎn)生OutOfMemoryError碟案,因?yàn)槊總€(gè)線程都有自己的棧空間颇蜡。
  • 方法區(qū)和運(yùn)行時(shí)常量池溢出:運(yùn)行時(shí)常量池也是方法區(qū)的一部分价说。這個(gè)區(qū)域的OutOfMemoryError可以利用String.intern()方法來(lái)產(chǎn)生辆亏。這是一個(gè)Native方法,意思是如果常量池中有一個(gè)String對(duì)象的字符串就返回池中的這個(gè)字符串的String對(duì)象鳖目;否則扮叨,將此String對(duì)象包含的字符串添加到常量池中去,并且返回此String對(duì)象的引用领迈。JDK1.7下是不會(huì)有這個(gè)異常的彻磁,while循環(huán)將一直下去,字符串常量池移動(dòng)到堆中了狸捅。JDK1.8移除了永久代并采用元空間來(lái)實(shí)現(xiàn)方法區(qū)的規(guī)劃了衷蜓。

classloader 類加載器

作用:裝載.class文件到JVM中的運(yùn)行時(shí)數(shù)據(jù)區(qū)。classloader 有兩種裝載class的方式(時(shí)機(jī)):

  • 顯示加載:在代碼中通過(guò)調(diào)用ClassLoader加載class對(duì)象尘喝,如直接使用Class.forName(name)或this.getClass().getClassLoader().loadClass()加載class對(duì)象磁浇。
  • 隱式加載:不直接在代碼中調(diào)用ClassLoader的方法加載class對(duì)象,而是通過(guò)虛擬機(jī)自動(dòng)加載到內(nèi)存中朽褪,如在加載某個(gè)類的class文件時(shí)置吓,該類的class文件中引用了另外一個(gè)類的對(duì)象,此時(shí)額外引用的類將通過(guò)JVM自動(dòng)加載到內(nèi)存中鞍匾。

類加載的時(shí)機(jī)

虛擬機(jī)嚴(yán)格規(guī)定交洗,有且僅有 5 種情況必須對(duì)類進(jìn)行加載(些文章會(huì)稱為對(duì)類進(jìn)行“初始化”。)

  1. 使用new關(guān)鍵字實(shí)例化對(duì)象時(shí)橡淑、讀取或設(shè)置一個(gè)類的靜態(tài)字段(static)時(shí)(被static修飾又被final修飾的构拳,已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)、以及調(diào)用一個(gè)類的靜態(tài)方法時(shí)梁棠。
  2. 使用Java.lang.refect包的方法對(duì)類進(jìn)行反射調(diào)用時(shí)置森。
  3. 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行初始化符糊,則需要先觸發(fā)其父類的初始化凫海。
  4. 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)主類男娄,虛擬機(jī)會(huì)先執(zhí)行該主類行贪。
  5. 當(dāng)使用Jdk1.7的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后解析的結(jié)果REF_getStatic模闲、REF_putStatic建瘫、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒(méi)有進(jìn)行過(guò)初始化尸折,則需要進(jìn)行初始化啰脚。

類的加載過(guò)程

image
  1. 加載:通過(guò)一個(gè)類的全限定名來(lái)獲取其定義的二進(jìn)制字節(jié)流。二進(jìn)制字節(jié)流可以從Class文件中獲取实夹,還可以從Jar橄浓、EAR粒梦、War包中獲取、從網(wǎng)絡(luò)中獲取荸实、由其他文件生成(JSP應(yīng)用)匀们、運(yùn)行時(shí)計(jì)算生成(比如動(dòng)態(tài)代理)等。將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)(類信息泪勒、靜態(tài)變量昼蛀、字節(jié)碼宴猾、常量這些)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)圆存。在Java堆中生成一個(gè)代表這個(gè).class文件的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口仇哆。
  2. 驗(yàn)證:確保Class文件中的字節(jié)流包含的信息符合當(dāng)前虛擬機(jī)的要求沦辙,而且不會(huì)危害虛擬機(jī)自身的安全。
  3. 準(zhǔn)備:為類變量(即static修飾的字段變量)分配內(nèi)存并且設(shè)置該類變量的初始值即0(如static int i=5;這里只將i初始化為0讹剔,至于5的值將在初始化時(shí)賦值)油讯,這里不包含用final修飾的static,因?yàn)閒inal在編譯的時(shí)候就會(huì)分配了延欠。這里也不會(huì)為實(shí)例變量分配內(nèi)存陌兑,實(shí)例變量會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一塊分配在 Java 堆中。
  4. 解析:將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程由捎。Class文件中不會(huì)保存各個(gè)方法和字段的最終內(nèi)存布局信息兔综,因此,這些字段和方法的符號(hào)引用不經(jīng)過(guò)轉(zhuǎn)換是無(wú)法直接被虛擬機(jī)使用的狞玛。當(dāng)虛擬機(jī)運(yùn)行時(shí)软驰,需要從常量池中獲得對(duì)應(yīng)的符號(hào)引用,再在類加載過(guò)程中的解析階段將其替換為直接引用心肪,并翻譯到具體的內(nèi)存地址中锭亏。
  5. 初始化:給static變量賦予用戶指定的值以及執(zhí)行靜態(tài)代碼塊。JVM 初始化步驟:假如這個(gè)類還沒(méi)有被加載和連接硬鞍,則程序先加載并連接該類慧瘤。假如該類的直接父類還沒(méi)有被初始化,則先初始化其直接父類固该。假如類中有初始化語(yǔ)句锅减,則系統(tǒng)依次執(zhí)行這些初始化語(yǔ)句。
  6. 使用
  7. 卸載:卸載是對(duì)象被GC的階段蹬音,JVM中的Class只有滿足下面的三個(gè)條件上煤,才會(huì)被卸載(回收),該類所有的實(shí)例都被GC著淆,不存在該類的任何實(shí)例劫狠;加載該類的ClassLoader已經(jīng)被GC拴疤;該類的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,比如不能再任何地方通過(guò)反射訪問(wèn)該類的方法

類加載器

通過(guò)一個(gè)類的全限定名來(lái)獲取描述此類的二進(jìn)制字節(jié)流的代碼塊稱之為類加載器独泞。
比較兩個(gè)類是否"相等"呐矾,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義,否則即使這兩個(gè)類來(lái)源于同一個(gè).class文件懦砂,被同一個(gè)虛擬機(jī)加載蜒犯,只要加載它們的類加載器不同,這兩個(gè)類必定不相等荞膘。"相等"包括代表類的.class對(duì)象的equals()方法罚随、isAssignableFrom()方法、isInstance()方法的返回結(jié)果羽资,也包括使用instanceof關(guān)鍵字做對(duì)象所屬關(guān)系判定等情況淘菩。類加載器可以大致劃分為以下三類:

  • 啟動(dòng)類加載器:Bootstrap ClassLoader。負(fù)責(zé)加載存放在JDK\jre\lib屠升,或被-Xbootclasspath參數(shù)指定的路徑中的潮改,并且能被虛擬機(jī)識(shí)別的類庫(kù)(如rt.jar,所有的java.* 開(kāi)頭的類均被Bootstrap ClassLoader加載)腹暖。啟動(dòng)類加載器是無(wú)法被Java程序直接引用的汇在。
  • 擴(kuò)展類加載器:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)脏答,它負(fù)責(zé)加載JDK\jre\lib\ext目錄中糕殉,或者由java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(kù)(如javax.* 開(kāi)頭的類),開(kāi)發(fā)者可以直接使用擴(kuò)展類加載器以蕴。
  • 應(yīng)用程序類加載器:Application ClassLoader糙麦,該類加載器由sun.misc.Launcher$AppClassLoader來(lái)實(shí)現(xiàn),它負(fù)責(zé)加載用戶類路徑(ClassPath)所指定的類丛肮,開(kāi)發(fā)者可以直接使用該類加載器赡磅,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器宝与。

應(yīng)用程序都是由這三種類加載器互相配合進(jìn)行加載的焚廊,如果有必要,我們還可以加入自定義的類加載器习劫。因?yàn)镴VM自帶的ClassLoader只是從本地文件系統(tǒng)加載標(biāo)準(zhǔn)的java class文件咆瘟,如果編寫(xiě)了自己的ClassLoader,便可以做到如下幾點(diǎn):

  • 在執(zhí)行非置信代碼之前诽里,自動(dòng)驗(yàn)證數(shù)字簽名袒餐。
  • 動(dòng)態(tài)地創(chuàng)建符合用戶特定需要的定制化構(gòu)建類。
  • 從特定的場(chǎng)所取得Java class,例如數(shù)據(jù)庫(kù)中和網(wǎng)絡(luò)中灸眼。

雙親委派模型

類加載器 ClassLoader 是具有層次結(jié)構(gòu)的卧檐,也就是父子關(guān)系,父子關(guān)系并非通常所說(shuō)的類繼承關(guān)系焰宣,而是采用組合關(guān)系來(lái)復(fù)用父類加載器的相關(guān)代碼霉囚,類加載器間的關(guān)系如下:


image
  1. 雙親委派模型的工作過(guò)程:如果一個(gè)類加載器收到了類加載的請(qǐng)求,當(dāng)前 ClassLoader首先從自己已經(jīng)加載的類中匕积,查詢是否此類已經(jīng)加載盈罐,如果已經(jīng)加載則直接返回原來(lái)已經(jīng)加載的類。每個(gè)類加載器都有自己的加載緩存闪唆,當(dāng)一個(gè)類被加載了以后就會(huì)放入緩存盅粪,等下次加載的時(shí)候就可以直接返回了。如果當(dāng)前 ClassLoader 的緩存中沒(méi)有找到被加載的類的時(shí)候苞氮,會(huì)委托父類加載器去加載湾揽,父類加載器采用同樣的策略,首先查看自己的緩存笼吟,然后委托父類的父類去加載,一直到 bootstrap ClassLoader霸旗。當(dāng)所有的父類加載器都沒(méi)有加載的時(shí)候贷帮,再由當(dāng)前的類加載器加載,并將其放入它自己的緩存中诱告,以便下次有加載請(qǐng)求的時(shí)候直接返回撵枢。
  2. 雙親委派模式的優(yōu)勢(shì):可以避免類的重復(fù)加載,當(dāng)父親已經(jīng)加載了該類時(shí)精居,就沒(méi)有必要子ClassLoader再加載一次锄禽。其次是考慮到安全因素,java核心api中定義類型不會(huì)被隨意替換靴姿,假設(shè)通過(guò)網(wǎng)絡(luò)傳遞一個(gè)名為java.lang.Integer的類沃但,通過(guò)雙親委托模式傳遞到啟動(dòng)類加載器,而啟動(dòng)類加載器在核心Java API發(fā)現(xiàn)這個(gè)名字的類佛吓,發(fā)現(xiàn)該類已被加載宵晚,并不會(huì)重新加載網(wǎng)絡(luò)傳遞的過(guò)來(lái)的java.lang.Integer,而直接返回已加載過(guò)的Integer.class维雇,這樣便可以防止核心API庫(kù)被隨意篡改淤刃。可能你會(huì)想吱型,如果我們?cè)赾lasspath路徑下自定義一個(gè)名為java.lang.SingleInterge類(該類是胡編的)呢逸贾?該類并不存在java.lang中,經(jīng)過(guò)雙親委托模式,傳遞到啟動(dòng)類加載器中铝侵,由于父類加載器路徑下并沒(méi)有該類掂名,所以不會(huì)加載,將反向委托給子類加載器加載哟沫,最終會(huì)通過(guò)系統(tǒng)類加載器加載該類饺蔑。但是這樣做是不允許,因?yàn)閖ava.lang是核心API包嗜诀,需要訪問(wèn)權(quán)限猾警,強(qiáng)制加載將會(huì)報(bào)出異常。相反隆敢,如果沒(méi)有雙親委派模型发皿,由各個(gè)類自己去加載的話,如果用戶自己編寫(xiě)了一個(gè)java.lang.Object拂蝎,并放在CLASSPATH下穴墅,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,Java體系中最基礎(chǔ)的行為也將無(wú)法保證温自,應(yīng)用程序也將會(huì)變得一片混亂玄货。如果一個(gè)對(duì)象每次加載都是由不同的類加載器加載的,就會(huì)出現(xiàn)很多同名但不是同一個(gè)類的類悼泌。
    3 雙親委派模型的缺陷:雙親委派模型解決了各個(gè)類加載器的基礎(chǔ)類的統(tǒng)一問(wèn)題松捉,越基礎(chǔ)的類由越上層的加載器進(jìn)行加載,基礎(chǔ)類的代碼總是作為被用戶代碼調(diào)用的API馆里,如果基礎(chǔ)類要調(diào)用用戶的代碼隘世,這就有問(wèn)題了。比如JNDI服務(wù)(JNDI的目的是對(duì)資源進(jìn)行集中管理和查找鸠踪,要調(diào)用由獨(dú)立廠商實(shí)現(xiàn)并部署在應(yīng)用程序的ClassPath下的JNDI接口提供者(SPI)的代碼)丙者,啟動(dòng)類加載器不認(rèn)可這些代碼,所以引入了線程上下文類加載器(Thread Context ClassLoader)营密,用父類加載器請(qǐng)求子類加載器完成類加載械媒。JNDI,JDBC等都是采用這種方式卵贱。
  3. 破壞雙親委派模型:破壞雙親委托模型滥沫,需要做的是,#loadClass(String name, boolean resolve) 方法中键俱,不調(diào)用父 parent ClassLoader 方法去加載類兰绣,那么就成功了。那么我們要做的僅僅是编振,錯(cuò)誤的覆蓋 ##loadClass(String name, boolean resolve) 方法缀辩,不去使用父 parent ClassLoader 方法去加載類即可臭埋。
  4. OSGI原理:模塊熱部署,打破了雙親委派模型臀玄。OSGI實(shí)現(xiàn)模塊化熱部署的關(guān)鍵是它自定義的類加載器機(jī)制的實(shí)現(xiàn)瓢阴,每一個(gè)程序模塊(Bundle)都有自己的類加載器,當(dāng)需要更換一個(gè)Bundle時(shí)健无,就把Bundle連同類加載器一起換掉實(shí)現(xiàn)模塊熱部署荣恐。OSGI的類加載器不再是雙親委派模型中的樹(shù)狀結(jié)構(gòu),而是復(fù)雜的網(wǎng)狀結(jié)構(gòu)累贤。例如bundleA叠穆、B都依賴于bundleC,當(dāng)他們?cè)L問(wèn)bundleC中的類時(shí)臼膏,就會(huì)委托給bundleC的類加載器硼被,由它來(lái)查找類;如果它發(fā)現(xiàn)還要依賴bundleE中的類渗磅,就會(huì)再委托給bundleE的類加載器嚷硫。

Java對(duì)象創(chuàng)建

在語(yǔ)言層面上,創(chuàng)建對(duì)象(克隆始鱼、反序列化)就是一個(gè)new關(guān)鍵字而已仔掸,在虛擬機(jī)層面上創(chuàng)建對(duì)象的步驟:

  1. 檢測(cè)類是否被加載:檢查這個(gè)指令的參數(shù)能否在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已經(jīng)被加載风响、解析和初始化嘉汰。如果沒(méi)有,那么必須先執(zhí)行類的加載過(guò)程状勤。
  2. 為對(duì)象分配內(nèi)存:類加載檢查通過(guò)后,為新生對(duì)象分配內(nèi)存双泪。對(duì)象所需內(nèi)存大小在類加載完成后便可以確定持搜,為對(duì)象分配空間就是從Java堆中劃分出一塊確定大小的內(nèi)存。
  3. 虛擬機(jī)將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭)焙矛。這一步保證了對(duì)象的實(shí)例字段在Java代碼中可以不用賦初始值就可以直接使用葫盼,程序能訪問(wèn)到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。
  4. 對(duì)對(duì)象進(jìn)行必要的設(shè)置村斟,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例贫导、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼蟆盹、對(duì)象的GC分代年齡等信息孩灯,這些信息存放在對(duì)象的對(duì)象頭中。
  5. 執(zhí)行對(duì)象構(gòu)造器init方法逾滥,進(jìn)行初始化


    image

Java對(duì)象的訪問(wèn)定位

對(duì)內(nèi)存分配情況分析最常見(jiàn)的示例便是對(duì)象實(shí)例化:
Object obj = new Object();
這段代碼的執(zhí)行會(huì)涉及java棧峰档、Java堆、方法區(qū)。假設(shè)該語(yǔ)句出現(xiàn)在方法體中讥巡,obj會(huì)作為引用類型的數(shù)據(jù)保存在Java棧的本地變量表中掀亩,在Java堆中保存該引用的實(shí)例化對(duì)象,Java堆中還必須包含能查找到此對(duì)象類型數(shù)據(jù)的地址信息欢顷,這些類型數(shù)據(jù)則保存在方法區(qū)中槽棍。
另外,由于reference類型在Java虛擬機(jī)規(guī)范里面只規(guī)定了一個(gè)指向?qū)ο蟮囊锰浚煌摂M機(jī)實(shí)現(xiàn)的對(duì)象訪問(wèn)方式會(huì)有所不同炼七,主流的訪問(wèn)方式有兩種:使用句柄池和直接使用指針。

  • 通過(guò)句柄池訪問(wèn)的方式:使用句柄訪問(wèn)方式的最大好處就是reference中存放的是穩(wěn)定的句柄地址怎爵,在對(duì)象被移動(dòng)(垃圾收集時(shí)移動(dòng)對(duì)象)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針特石,而reference本身不需要修改
    [圖片上傳失敗...(image-bb2ae3-1600958929592)]
  • 通過(guò)直接指針訪問(wèn)的方式:
    [圖片上傳失敗...(image-ee5181-1600958929592)]
    使用直接指針訪問(wèn)方式的最大好處是速度快,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷鳖链。目前Java默認(rèn)使用的HotSpot虛擬機(jī)采用的是直接指針進(jìn)行對(duì)象訪問(wèn)的姆蘸。

對(duì)象在堆中的布局

HotSpot虛擬機(jī)中,對(duì)象在堆內(nèi)存中的布局分為三塊區(qū)域:對(duì)象頭芙委、實(shí)例數(shù)據(jù)和對(duì)齊填充逞敷。

  1. 對(duì)象頭:包括兩部分:Mark Word 和 類型指針。
    • Mark Word:存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)灌侣,如哈希碼推捐、GC分代年齡、鎖狀態(tài)標(biāo)志侧啼、線程持有的鎖牛柒、偏向線程ID、偏向時(shí)間戳等等痊乾,占用內(nèi)存大小與虛擬機(jī)位長(zhǎng)一致皮壁。
    • 類型指針:指向方法區(qū)中的對(duì)象類型數(shù)據(jù),虛擬機(jī)通過(guò)這個(gè)指針確定該對(duì)象是哪個(gè)類的實(shí)例哪审。
  2. 實(shí)例數(shù)據(jù):對(duì)象真正存儲(chǔ)的有效信息蛾魄。
  3. 對(duì)齊填充:由于HotSpot虛擬機(jī)要求對(duì)象的大小必須是8字節(jié)的整數(shù)倍。而對(duì)象頭部分正好是8字節(jié)的倍數(shù)湿滓,當(dāng)對(duì)象實(shí)例數(shù)據(jù)部分沒(méi)有對(duì)齊的時(shí)候滴须,就需要通過(guò)對(duì)齊填充來(lái)補(bǔ)全。對(duì)齊填充不是必然存在的叽奥。

如何保證new對(duì)象時(shí)候的線程安全性扔水。

因?yàn)榭赡艹霈F(xiàn)虛擬機(jī)正在給對(duì)象A分配內(nèi)存,指針還沒(méi)有來(lái)得及修改而线,對(duì)象B又同時(shí)使用了原來(lái)的指針來(lái)分配內(nèi)存的情況铭污。虛擬機(jī)采用了CAS配上失敗重試的方式保證更新操作的原子性和TLAB兩種方式來(lái)解決這個(gè)問(wèn)題恋日。TLAB:內(nèi)存分配的動(dòng)作,每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存嘹狞,稱為本地線程分配緩沖(Thread Local Allocation Buffer岂膳,TLAB)。哪個(gè)線程需要分配內(nèi)存磅网,就在哪個(gè)線程的TLAB上分配谈截。虛擬機(jī)是否使用TLAB,可以通過(guò)-XX:+/-UseTLAB參數(shù)來(lái)設(shè)定涧偷。這么做的目的之一簸喂,也是為了并發(fā)創(chuàng)建一個(gè)對(duì)象時(shí),保證創(chuàng)建對(duì)象的線程安全性燎潮。TLAB比較小喻鳄,直接在TLAB上分配內(nèi)存的方式稱為快速分配方式,而TLAB大小不夠确封,導(dǎo)致內(nèi)存被分配在Eden區(qū)的內(nèi)存分配方式稱為慢速分配方式除呵。

垃圾收集器

垃圾對(duì)象的判定

  • 引用計(jì)數(shù)算法:每個(gè)對(duì)象有一個(gè)引用計(jì)數(shù)屬性,新增一個(gè)引用時(shí)計(jì)數(shù)加 1 爪喘,引用釋放時(shí)計(jì)數(shù)減 1 颜曾,計(jì)數(shù)為 0 時(shí)可以回收。此方法簡(jiǎn)單秉剑,無(wú)法解決對(duì)象相互循環(huán)引用的問(wèn)題泛豪。目前在用的有 Python、ActionScript3 等語(yǔ)言侦鹏。

  • 可達(dá)性分析算法:從 GC Roots 開(kāi)始向下搜索诡曙,搜索所走過(guò)的路徑稱為引用鏈。當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連時(shí)略水,則證明此對(duì)象是不可用的岗仑。不可達(dá)對(duì)象。目前在用的有 Java聚请、C# 等語(yǔ)言∥绕洌可作為GC Roots的對(duì)象包括下面幾種:

    1. 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象驶赏。
    2. 方法區(qū)中的類靜態(tài)屬性引用的對(duì)象。
    3. 方法區(qū)中的常量引用的對(duì)象既鞠。
    4. 本地方法棧中JNI(Native方法)的引用對(duì)象煤傍。

    在可達(dá)性分析算法中,要真正宣告一個(gè)對(duì)象死亡嘱蛋,至少要經(jīng)歷兩次標(biāo)記過(guò)程:如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒(méi)有與GC Roots相連接的引用鏈蚯姆,那它會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選五续,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法。當(dāng)對(duì)象沒(méi)有覆蓋finalize()方法龄恋,或finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過(guò)疙驾,則沒(méi)有必要執(zhí)行。

對(duì)象引用

  • 強(qiáng)引用:如“Object obj = new Object()”郭毕,這類引用是Java程序中最普遍的它碎。只要強(qiáng)引用還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象显押。
  • 軟引用:它用來(lái)描述一些可能還有用扳肛,但并非必須的對(duì)象。在系統(tǒng)內(nèi)存不夠用時(shí)乘碑,這類引用關(guān)聯(lián)的對(duì)象將被垃圾收集器回收挖息。JDK1.2之后提供了SoftReference類來(lái)實(shí)現(xiàn)軟引用。
SoftReference<User> softReference = new SoftReference<User>(new User());  
strangeReference = softReference.get(); //通過(guò)get方法獲得強(qiáng)引用
  • 弱引用:被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前兽肤。當(dāng)垃圾收集器工作時(shí)套腹,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象轿衔。在JDK1.2之后沉迹,提供了WeakReference類來(lái)實(shí)現(xiàn)弱引用。jdk中的ThreadLocal就是弱引用的
WeakReference<User> weakReference = new WeakReference<User>(new User());  
  • 虛引用:“虛引用”顧名思義害驹,就是形同虛設(shè)鞭呕,虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用宛官,那么它就和沒(méi)有任何引用一樣葫松,在任何時(shí)候都可能被垃圾回收。虛引用主要用來(lái)跟蹤對(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ì)列中。程序可以通過(guò)判斷引用隊(duì)列中是否已經(jīng)加入了虛引用费变,來(lái)了解被引用的對(duì)象是否將要被垃圾回收摧扇。程序如果發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對(duì)象的內(nèi)存被回收之前采取必要的行動(dòng)挚歧。JDK1.2之后提供了PhantomReference類來(lái)實(shí)現(xiàn)虛引用扛稽。虛引用PhantomReference<T>的聲明的借助強(qiáng)引用或者匿名對(duì)象,結(jié)合泛型ReferenceQueue<T>初始化,具體如下:
PhantomReference<User> phantomReference = new PhantomReference<User>(new User(),new ReferenceQueue<User>()); 

為什么要有不同的引用類型滑负?

不像 C 語(yǔ)言在张,我們可以控制內(nèi)存的申請(qǐng)和釋放用含,在 Java 中有時(shí)候我們需要適當(dāng)?shù)目刂茖?duì)象被回收的時(shí)機(jī),因此就誕生了不同的引用類型帮匾,可以說(shuō)不同的引用類型實(shí)則是對(duì) GC 回收時(shí)機(jī)不可控的妥協(xié)啄骇。有以下幾個(gè)使用場(chǎng)景可以充分的說(shuō)明:利用軟引用和弱引用解決 OOM 問(wèn)題。用一個(gè) HashMap 來(lái)保存圖片的路徑和相應(yīng)圖片對(duì)象關(guān)聯(lián)的軟引用之間的映射關(guān)系辟狈,在內(nèi)存不足時(shí)肠缔,JVM 會(huì)自動(dòng)回收這些緩存圖片對(duì)象所占用的空間,從而有效地避免了 OOM 的問(wèn)題哼转;通過(guò)軟引用實(shí)現(xiàn) Java 對(duì)象的高速緩存明未。比如我們創(chuàng)建了一 Person 的類,如果每次需要查詢一個(gè)人的信息壹蔓,哪怕是幾秒中之前剛剛查詢過(guò)的趟妥,都要重新構(gòu)建一個(gè)實(shí)例,這將引起大量 Person 對(duì)象的消耗佣蓉,并且由于這些對(duì)象的生命周期相對(duì)較短披摄,會(huì)引起多次 GC 影響性能。此時(shí)勇凭,通過(guò)軟引用和 HashMap 的結(jié)合可以構(gòu)建高速緩存疚膊,提供性能。

方法區(qū)回收

虛擬機(jī)規(guī)范中不要求方法區(qū)一定要實(shí)現(xiàn)垃圾回收虾标,而且方法區(qū)中進(jìn)行垃圾回收的效率也確實(shí)比較低寓盗,但是HotSpot對(duì)方法區(qū)也是進(jìn)行回收的,主要回收的是廢棄常量和無(wú)用的類兩部分璧函。

  • 廢棄常量:只要當(dāng)前系統(tǒng)中沒(méi)有任何一處引用該常量就是廢棄常量
  • 無(wú)用的類需要同時(shí)滿足以下三個(gè)條件:
    1. 該類所有實(shí)例都已經(jīng)被回收傀蚌,Java堆中不存在該類的任何實(shí)例
    2. 加載該類的ClassLoader已經(jīng)被回收
    3. 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法

在大量使用反射蘸吓、動(dòng)態(tài)代理善炫、CGLib等ByteCode框架、動(dòng)態(tài)生成JSP以及OSGi這類頻繁自定義ClassLoader的場(chǎng)景都需要虛擬機(jī)具備類卸載功能库继,以保證方法區(qū)不會(huì)溢出箩艺。

垃圾收集算法

  1. 標(biāo)記-清除算法:首先標(biāo)記出所有需要回收的對(duì)象,然后統(tǒng)一回收所有被標(biāo)記的對(duì)象宪萄;缺點(diǎn)是效率不高且容易產(chǎn)生大量不連續(xù)的內(nèi)存碎片舅桩, 當(dāng)程序需要分配較大對(duì)象時(shí)無(wú)法找到連續(xù)內(nèi)存而不得不觸發(fā)另一次垃圾收集動(dòng)作。
    image
  2. 復(fù)制算法:將可用內(nèi)存分為大小相等的兩塊雨膨,每次只使用其中一塊;當(dāng)這一塊用完了读串,就將還活著的對(duì)象復(fù)制到另一塊上聊记,然后把已使用過(guò)的內(nèi)存清理掉撒妈。在HotSpot里,考慮到大部分對(duì)象存活時(shí)間很短排监,將內(nèi)存分為Eden和兩塊Survivor狰右,默認(rèn)比例為8:1:1。代價(jià)是存在部分內(nèi)存空間浪費(fèi)舆床,且可能存在空間不夠需要分配擔(dān)保的情況棋蚌,所以適合在新生代使用;
    image
  3. 標(biāo)記-整理算法:首先標(biāo)記出所有需要回收的對(duì)象挨队,然后讓所有存活的對(duì)象都向一端移動(dòng)谷暮,然后直接清理掉端邊界以外的內(nèi)存。適用于老年代盛垦。
    image
  4. 分代收集算法:一般把Java堆分新生代和老年代湿弦,在新生代用復(fù)制算法,新生代每次垃圾收集時(shí)都會(huì)有大量對(duì)象死去腾夯,只有少量存活颊埃。老年代對(duì)象存活率高、沒(méi)有額外額空間對(duì)他進(jìn)行分配擔(dān)保蝶俱,用標(biāo)記-清理或標(biāo)記-整理算法班利,是現(xiàn)代虛擬機(jī)通常采用的算法。
    image

內(nèi)存分配策略

  1. 對(duì)象優(yōu)先在Eden區(qū)分配:當(dāng)Eden區(qū)沒(méi)有足夠空間進(jìn)行分配時(shí)榨呆,虛擬機(jī)將發(fā)起一次Minor GC
  2. 大對(duì)象直接進(jìn)入老年代:大對(duì)象是指需要大量連續(xù)內(nèi)存空間的Java對(duì)象罗标,比如很長(zhǎng)的字符串以及數(shù)組,老年代發(fā)生Full GC
  3. 長(zhǎng)期存活的對(duì)象將進(jìn)入老年代:如果對(duì)象在Eden區(qū)出生并且經(jīng)過(guò)第一次Minor GC后任然存在愕提,并且能被Survivor容納馒稍,則被移動(dòng)到Survivor空間中,并且對(duì)象年齡+1浅侨,當(dāng)對(duì)象年齡達(dá)到“-XX:MaxTenuringThreshold”設(shè)置的值(默認(rèn)15)的時(shí)候纽谒,對(duì)象就會(huì)被晉升到老年代中

什么是安全點(diǎn)

SafePoint安全點(diǎn),顧名思義是指一些特定的位置如输,當(dāng)線程運(yùn)行到這些位置時(shí)鼓黔,線程的一些狀態(tài)可以被確定,比如記錄OopMap 的狀態(tài)不见,從而確定 GC Root 的信息澳化,使 JVM 可以安全的進(jìn)行一些操作,比如開(kāi)始 GC稳吮。SafePoint 指的特定位置主要有:
循環(huán)的末尾 (防止大循環(huán)的時(shí)候一直不進(jìn)入 Safepoint 缎谷,而其他線程在等待它進(jìn)入 Safepoint )、方法返回前灶似、調(diào)用方法的 Call 之后列林、拋出異常的位置瑞你。

Minor GC和Full GC的區(qū)別

  1. 新生代GC(Minor GC):發(fā)生在新生代的垃圾收集動(dòng)作
  2. 老年代GC(Major GC/Full GC):發(fā)生在老年代的垃圾收集動(dòng)作,出現(xiàn)了Major GC希痴,經(jīng)常會(huì)伴隨至少一次的Minor GC(但并不是絕對(duì)的)者甲。Major GC的速度一般要比Minor GC慢上10倍以上

垃圾收集器

  1. Serial收集器:復(fù)制算法的單線程的收集器,它只會(huì)使用一條線程去完成垃圾收集工作砌创,進(jìn)行垃圾收集時(shí)必須暫停其他線程的所有工作虏缸,直到它收集結(jié)束為止。垃圾收集的過(guò)程中會(huì)Stop The World(服務(wù)暫停)嫩实。參數(shù)控制: -XX:+UseSerialGC 串行收集器

  2. ParNew收集器:Serial收集器的多線程版本刽辙。新生代并行,老年代串行舶赔;新生代復(fù)制算法扫倡、老年代標(biāo)記-壓縮。-XX:+UseParNewGC ParNew收集器竟纳,-XX:ParallelGCThreads 限制線程數(shù)量

  3. Parallel收集器:Parallel Scavenge收集器類似ParNew收集器撵溃,Parallel收集器更關(guān)注系統(tǒng)的吞吐量∽独郏可以通過(guò)參數(shù)來(lái)打開(kāi)自適應(yīng)調(diào)節(jié)策略缘挑,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或最大的吞吐量桶略;也可以通過(guò)參數(shù)控制GC的時(shí)間不大于多少毫秒或者比例语淘;新生代復(fù)制算法、老年代標(biāo)記-壓縮际歼。參數(shù)控制: -XX:+UseParallelGC 使用Parallel收集器+ 老年代串行

  4. Parallel Old收集器:Parallel Scavenge收集器的老年代版本惶翻,使用多線程和“標(biāo)記-整理”算法。這個(gè)收集器是在JDK1.6中才開(kāi)始提供鹅心,參數(shù)控制:-XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行

  5. CMS收集器:Concurrent Mark Sweep吕粗,是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。目前很大一部分的Java應(yīng)用都集中在互聯(lián)網(wǎng)站或B/S系統(tǒng)的服務(wù)端上旭愧,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度颅筋,希望系統(tǒng)停頓時(shí)間最短,以給用戶帶來(lái)較好的體驗(yàn)输枯,缺點(diǎn)是會(huì)產(chǎn)生大量空間碎片议泵、并發(fā)階段會(huì)降低吞吐量。從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的桃熄,它的運(yùn)作過(guò)程相對(duì)于前面幾種收集器來(lái)說(shuō)要更復(fù)雜一些先口,整個(gè)過(guò)程分為4個(gè)步驟,包括:

    1. 初始標(biāo)記:僅標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快池充,單線程桩引,會(huì)發(fā)生Stop The World
    2. 并發(fā)標(biāo)記:與應(yīng)用線程一起運(yùn)行,是CMS最主要的工作階段收夸,通過(guò)直達(dá)對(duì)象,掃描全部的對(duì)象血崭,進(jìn)行標(biāo)記
    3. 重新標(biāo)記:STW卧惜,修正并發(fā)標(biāo)記時(shí)由于應(yīng)用程序還在并發(fā)運(yùn)行產(chǎn)生的對(duì)象的修改,多線程夹纫,速度快咽瓷,需要全局停頓
    4. 并發(fā)清除:與應(yīng)用線程一起運(yùn)行,清理垃圾對(duì)象
  6. G1收集器:G1是目前技術(shù)發(fā)展的最前沿成果之一舰讹,HotSpot開(kāi)發(fā)團(tuán)隊(duì)賦予它的使命是未來(lái)可以替換掉JDK1.5中發(fā)布的CMS收集器茅姜。與CMS收集器相比G1收集器有以下特點(diǎn):

    • 空間整合:G1收集器采用標(biāo)記整理算法,不會(huì)產(chǎn)生內(nèi)存空間碎片月匣。分配大對(duì)象時(shí)不會(huì)因?yàn)闊o(wú)法找到連續(xù)空間而提前觸發(fā)下一次GC钻洒。
    • 可預(yù)測(cè)停頓,這是G1的另一大優(yōu)勢(shì)锄开,降低停頓時(shí)間是G1和CMS的共同關(guān)注點(diǎn)素标,但G1除了追求低停頓外,還能建立可預(yù)測(cè)的停頓時(shí)間模型萍悴,能讓使用者明確指定在一個(gè)長(zhǎng)度為N毫秒的時(shí)間片段內(nèi)头遭,消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒,這幾乎已經(jīng)是實(shí)時(shí)Java(RTSJ)的垃圾收集器的特征了癣诱。

上面提到的垃圾收集器计维,收集的范圍都是整個(gè)新生代或者老年代,而G1不再是這樣撕予。使用G1收集器時(shí)鲫惶,Java堆的內(nèi)存布局與其他收集器有很大差別,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)嗅蔬,雖然還保留有新生代和老年代的概念剑按,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續(xù))Region的集合澜术。收集步驟:

  1. 標(biāo)記階段: 首先初始標(biāo)記(Initial-Mark)艺蝴,這個(gè)階段是停頓的(Stop the World Event),并且會(huì)觸發(fā)一次普通Mintor GC鸟废。對(duì)應(yīng)GC log:GC pause (young) (inital-mark)
  2. Root Region Scanning猜敢,程序運(yùn)行過(guò)程中會(huì)回收survivor區(qū)(存活到老年代),這一過(guò)程必須在young GC之前完成。
  3. Concurrent Marking缩擂,在整個(gè)堆中進(jìn)行并發(fā)標(biāo)記(和應(yīng)用程序并發(fā)執(zhí)行)鼠冕,此過(guò)程可能被young GC中斷。在并發(fā)標(biāo)記階段胯盯,若發(fā)現(xiàn)區(qū)域?qū)ο笾械乃袑?duì)象都是垃圾懈费,那個(gè)這個(gè)區(qū)域會(huì)被立即回收(圖中打X)。同時(shí)博脑,并發(fā)標(biāo)記過(guò)程中憎乙,會(huì)計(jì)算每個(gè)區(qū)域的對(duì)象活性(區(qū)域中存活對(duì)象的比例)。
  4. Remark, 再標(biāo)記叉趣,會(huì)有短暫停頓(STW)泞边。再標(biāo)記階段是用來(lái)收集 并發(fā)標(biāo)記階段 產(chǎn)生新的垃圾(并發(fā)階段和應(yīng)用程序一同運(yùn)行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)疗杉。
  5. Copy/Clean up阵谚,多線程清除失活對(duì)象,會(huì)有STW烟具。G1將回收區(qū)域的存活對(duì)象拷貝到新區(qū)域戴差,清除Remember Sets融痛,并發(fā)清空回收區(qū)域并把它返回到空閑區(qū)域鏈表中酱塔。
  6. 復(fù)制/清除過(guò)程后驮捍。回收區(qū)域的活性對(duì)象已經(jīng)被集中回收到深藍(lán)色和深綠色區(qū)域玖翅。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末翼馆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子金度,更是在濱河造成了極大的恐慌应媚,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猜极,死亡現(xiàn)場(chǎng)離奇詭異中姜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)跟伏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門丢胚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人受扳,你說(shuō)我怎么就攤上這事携龟。” “怎么了勘高?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵峡蟋,是天一觀的道長(zhǎng)坟桅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蕊蝗,這世上最難降的妖魔是什么仅乓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蓬戚,結(jié)果婚禮上夸楣,老公的妹妹穿的比我還像新娘。我一直安慰自己子漩,他們只是感情好裕偿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著痛单,像睡著了一般。 火紅的嫁衣襯著肌膚如雪劲腿。 梳的紋絲不亂的頭發(fā)上旭绒,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音焦人,去河邊找鬼挥吵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛花椭,可吹牛的內(nèi)容都是我干的忽匈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼矿辽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼丹允!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起袋倔,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤雕蔽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后宾娜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體批狐,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年前塔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嚣艇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡华弓,死狀恐怖食零,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情该抒,我是刑警寧澤慌洪,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布顶燕,位于F島的核電站,受9級(jí)特大地震影響冈爹,放射性物質(zhì)發(fā)生泄漏涌攻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一频伤、第九天 我趴在偏房一處隱蔽的房頂上張望恳谎。 院中可真熱鬧,春花似錦憋肖、人聲如沸因痛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鸵膏。三九已至,卻和暖如春怎炊,著一層夾襖步出監(jiān)牢的瞬間谭企,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工评肆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留债查,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓瓜挽,卻偏偏與公主長(zhǎng)得像盹廷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子久橙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354