Java高頻面試集-JVM虛擬機(jī)

JVM面試題

Java面試題


什么是Java虛擬機(jī)?為什么Java被稱作是“平臺(tái)無(wú)關(guān)的編程語(yǔ)言”?

Java虛擬機(jī)是一個(gè)可以執(zhí)行Java字節(jié)碼的虛擬機(jī)進(jìn)程乔夯。Java源文件被編譯成能被Java虛擬機(jī)執(zhí)行的字節(jié)碼文件。
Java被設(shè)計(jì)成允許應(yīng)用程序可以運(yùn)行在任意的平臺(tái),而不需要程序員為每一個(gè)平臺(tái)單獨(dú)重寫或者是重新編譯他匪。Java虛擬機(jī)讓這個(gè)變?yōu)榭赡埽驗(yàn)樗赖讓佑布脚_(tái)的指令長(zhǎng)度和其他特性夸研。

Java內(nèi)存結(jié)構(gòu)邦蜜?

方法區(qū)和堆是所有線程共享的內(nèi)存區(qū)域;而java棧亥至、本地方法棧和程序員計(jì)數(shù)器是運(yùn)行是線程私有的內(nèi)存區(qū)域悼沈。

  • Java堆(Heap),是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊贱迟。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建絮供。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例衣吠,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存。
  • 方法區(qū)(Method Area),方法區(qū)(Method Area)與Java堆一樣壤靶,是各個(gè)線程共享的內(nèi)存區(qū)域缚俏,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量萍肆、靜態(tài)變量袍榆、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
  • 程序計(jì)數(shù)器(Program Counter Register),程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間塘揣,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器包雀。
  • JVM棧(JVM Stacks),與程序計(jì)數(shù)器一樣,Java虛擬機(jī)棧(Java Virtual Machine Stacks)也是線程私有的亲铡,它的生命周期與線程相同才写。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法被執(zhí)行的時(shí)候都會(huì)同時(shí)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表、操作棧奖蔓、動(dòng)態(tài)鏈接赞草、方法出口等信息。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過(guò)程吆鹤,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程厨疙。
  • 本地方法棧(Native Method Stacks),本地方法棧(Native Method Stacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)疑务,而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)沾凄。

解釋內(nèi)存中的棧(stack)、堆(heap)和方法區(qū)(method area)的用法

通常我們定義一個(gè)基本數(shù)據(jù)類型的變量知允,一個(gè)對(duì)象的引用撒蟀,還有就是函數(shù)調(diào)用的現(xiàn)場(chǎng)保存都使用JVM中的棧空間温鸽;而通過(guò)new關(guān)鍵字和構(gòu)造器創(chuàng)建的對(duì)象則放在堆空間保屯,堆是垃圾收集器管理的主要區(qū)域,由于現(xiàn)在的垃圾收集器都采用分代收集算法涤垫,所以堆空間還可以細(xì)分為新生代和老生代姑尺,再具體一點(diǎn)可以分為Eden、Survivor(又可分為From Survivor和To Survivor)蝠猬、Tenured股缸;方法區(qū)和堆都是各個(gè)線程共享的內(nèi)存區(qū)域,用于存儲(chǔ)已經(jīng)被JVM加載的類信息吱雏、常量敦姻、靜態(tài)變量瘾境、JIT編譯器編譯后的代碼等數(shù)據(jù);程序中的字面量(literal)如直接書寫的100镰惦、"hello"和常量都是放在常量池中迷守,常量池是方法區(qū)的一部分,旺入。椂以洌空間操作起來(lái)最快但是棧很小,通常大量的對(duì)象都是放在堆空間茵瘾,棧和堆的大小都可以通過(guò)JVM的啟動(dòng)參數(shù)來(lái)進(jìn)行調(diào)整礼华,棧空間用光了會(huì)引發(fā)StackOverflowError拗秘,而堆和常量池空間不足則會(huì)引發(fā)OutOfMemoryError圣絮。

String str = new String("hello");

上面的語(yǔ)句中變量str放在棧上,用new創(chuàng)建出來(lái)的字符串對(duì)象放在堆上雕旨,而"hello"這個(gè)字面量是放在方法區(qū)的扮匠。

補(bǔ)充1:較新版本的Java(從Java 6的某個(gè)更新開(kāi)始)中,由于JIT編譯器的發(fā)展和"逃逸分析"技術(shù)的逐漸成熟凡涩,棧上分配棒搜、標(biāo)量替換等優(yōu)化技術(shù)使得對(duì)象一定分配在堆上這件事情已經(jīng)變得不那么絕對(duì)了。

補(bǔ)充2:運(yùn)行時(shí)常量池相當(dāng)于Class文件常量池具有動(dòng)態(tài)性活箕,Java語(yǔ)言并不要求常量一定只有編譯期間才能產(chǎn)生力麸,運(yùn)行期間也可以將新的常量放入池中,String類的intern()方法就是這樣的育韩。
看看下面代碼的執(zhí)行結(jié)果是什么并且比較一下Java 7以前和以后的運(yùn)行結(jié)果是否一致克蚂。

String s1 = new StringBuilder("go")
    .append("od").toString();
System.out.println(s1.intern() == s1);
String s2 = new StringBuilder("ja")
    .append("va").toString();
System.out.println(s2.intern() == s2);

對(duì)象分配規(guī)則

  • 對(duì)象優(yōu)先分配在Eden區(qū),如果Eden區(qū)沒(méi)有足夠的空間時(shí)座慰,虛擬機(jī)執(zhí)行一次Minor GC。
  • 大對(duì)象直接進(jìn)入老年代(大對(duì)象是指需要大量連續(xù)內(nèi)存空間的對(duì)象)翠拣。這樣做的目的是避免在Eden區(qū)和兩個(gè)Survivor區(qū)之間發(fā)生大量的內(nèi)存拷貝(新生代采用復(fù)制算法收集內(nèi)存)版仔。
  • 長(zhǎng)期存活的對(duì)象進(jìn)入老年代。虛擬機(jī)為每個(gè)對(duì)象定義了一個(gè)年齡計(jì)數(shù)器误墓,如果對(duì)象經(jīng)過(guò)了1次Minor GC那么對(duì)象會(huì)進(jìn)入Survivor區(qū)蛮粮,之后每經(jīng)過(guò)一次Minor GC那么對(duì)象的年齡加1,知道達(dá)到閥值對(duì)象進(jìn)入老年區(qū)谜慌。
  • 動(dòng)態(tài)判斷對(duì)象的年齡然想。如果Survivor區(qū)中相同年齡的所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象可以直接進(jìn)入老年代欣范。
  • 空間分配擔(dān)保变泄。每次進(jìn)行Minor GC時(shí)令哟,JVM會(huì)計(jì)算Survivor區(qū)移至老年區(qū)的對(duì)象的平均大小,如果這個(gè)值大于老年區(qū)的剩余值大小則進(jìn)行一次Full GC妨蛹,如果小于檢查HandlePromotionFailure設(shè)置屏富,如果true則只進(jìn)行Monitor GC,如果false則進(jìn)行Full GC。

什么是類的加載

類的加載指的是將類的.class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中蛙卤,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi)狠半,然后在堆區(qū)創(chuàng)建一個(gè)java.lang.Class對(duì)象,用來(lái)封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)颤难。類的加載的最終產(chǎn)品是位于堆區(qū)中的Class對(duì)象神年,Class對(duì)象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),并且向Java程序員提供了訪問(wèn)方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口行嗤。

類加載器

  • 啟動(dòng)類加載器:Bootstrap ClassLoader已日,負(fù)責(zé)加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下昂验,或被-Xbootclasspath參數(shù)指定的路徑中的捂敌,并且能被虛擬機(jī)識(shí)別的類庫(kù)

  • 擴(kuò)展類加載器:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)既琴,它負(fù)責(zé)加載DK\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ā)者可以直接使用該類加載器

描述一下JVM加載class文件的原理機(jī)制奖慌?

答:JVM中類的裝載是由類加載器(ClassLoader)和它的子類來(lái)實(shí)現(xiàn)的,Java中的類加載器是一個(gè)重要的Java運(yùn)行時(shí)系統(tǒng)組件松靡,它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入類文件中的類简僧。
由于Java的跨平臺(tái)性,經(jīng)過(guò)編譯的Java源程序并不是一個(gè)可執(zhí)行程序雕欺,而是一個(gè)或多個(gè)類文件岛马。當(dāng)Java程序需要使用某個(gè)類時(shí),JVM會(huì)確保這個(gè)類已經(jīng)被加載屠列、連接(驗(yàn)證啦逆、準(zhǔn)備和解析)和初始化。類的加載是指把類的.class文件中的數(shù)據(jù)讀入到內(nèi)存中笛洛,通常是創(chuàng)建一個(gè)字節(jié)數(shù)組讀入.class文件夏志,然后產(chǎn)生與所加載類對(duì)應(yīng)的Class對(duì)象。加載完成后苛让,Class對(duì)象還不完整沟蔑,所以此時(shí)的類還不可用湿诊。當(dāng)類被加載后就進(jìn)入連接階段,這一階段包括驗(yàn)證溉贿、準(zhǔn)備(為靜態(tài)變量分配內(nèi)存并設(shè)置默認(rèn)的初始值)和解析(將符號(hào)引用替換為直接引用)三個(gè)步驟枫吧。最后JVM對(duì)類進(jìn)行初始化,包括:1)如果類存在直接的父類并且這個(gè)類還沒(méi)有被初始化宇色,那么就先初始化父類九杂;2)如果類中存在初始化語(yǔ)句,就依次執(zhí)行這些初始化語(yǔ)句宣蠕。
類的加載是由類加載器完成的例隆,類加載器包括:根加載器(BootStrap)、擴(kuò)展加載器(Extension)抢蚀、系統(tǒng)加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)镀层。從Java 2(JDK 1.2)開(kāi)始,類加載過(guò)程采取了父親委托機(jī)制(PDM)皿曲。PDM更好的保證了Java平臺(tái)的安全性唱逢,在該機(jī)制中,JVM自帶的Bootstrap是根加載器屋休,其他的加載器都有且僅有一個(gè)父類加載器坞古。類的加載首先請(qǐng)求父類加載器加載,父類加載器無(wú)能為力時(shí)才由其子類加載器自行加載劫樟。JVM不會(huì)向Java程序提供對(duì)Bootstrap的引用痪枫。下面是關(guān)于幾個(gè)類加載器的說(shuō)明:

  • Bootstrap:一般用本地代碼實(shí)現(xiàn),負(fù)責(zé)加載JVM基礎(chǔ)核心類庫(kù)(rt.jar)叠艳;
  • Extension:從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫(kù)奶陈,它的父加載器是Bootstrap;
  • System:又叫應(yīng)用類加載器附较,其父類是Extension吃粒。它是應(yīng)用最廣泛的類加載器。它從環(huán)境變量classpath或者系統(tǒng)屬性java.class.path所指定的目錄中記載類拒课,是用戶自定義加載器的默認(rèn)父加載器徐勃。

描述一下JVM加載class文件的原理機(jī)制?

JVM中類的裝載是由類加載器(ClassLoader)和它的子類來(lái)實(shí)現(xiàn)的捕发,Java中的類加載器是一個(gè)重要的Java運(yùn)行時(shí)系統(tǒng)組件疏旨,它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入類文件中的類很魂。

由于Java的跨平臺(tái)性扎酷,經(jīng)過(guò)編譯的Java源程序并不是一個(gè)可執(zhí)行程序,而是一個(gè)或多個(gè)類文件遏匆。當(dāng)Java程序需要使用某個(gè)類時(shí)法挨,JVM會(huì)確保這個(gè)類已經(jīng)被加載谁榜、連接(驗(yàn)證、準(zhǔn)備和解析)和初始化凡纳。類的加載是指把類的.class文件中的數(shù)據(jù)讀入到內(nèi)存中窃植,通常是創(chuàng)建一個(gè)字節(jié)數(shù)組讀入.class文件,然后產(chǎn)生與所加載類對(duì)應(yīng)的Class對(duì)象荐糜。加載完成后巷怜,Class對(duì)象還不完整,所以此時(shí)的類還不可用暴氏。當(dāng)類被加載后就進(jìn)入連接階段延塑,這一階段包括驗(yàn)證、準(zhǔn)備(為靜態(tài)變量分配內(nèi)存并設(shè)置默認(rèn)的初始值)和解析(將符號(hào)引用替換為直接引用)三個(gè)步驟答渔。最后JVM對(duì)類進(jìn)行初始化关带,包括:

  • 1)如果類存在直接的父類并且這個(gè)類還沒(méi)有被初始化,那么就先初始化父類沼撕;
  • 2)如果類中存在初始化語(yǔ)句宋雏,就依次執(zhí)行這些初始化語(yǔ)句。

類的加載是由類加載器完成的务豺,類加載器包括:根加載器(BootStrap)磨总、擴(kuò)展加載器(Extension)、系統(tǒng)加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)冲呢。

從Java 2(JDK 1.2)開(kāi)始舍败,類加載過(guò)程采取了父親委托機(jī)制(PDM)。PDM更好的保證了Java平臺(tái)的安全性敬拓,在該機(jī)制中邻薯,JVM自帶的Bootstrap是根加載器,其他的加載器都有且僅有一個(gè)父類加載器乘凸。類的加載首先請(qǐng)求父類加載器加載厕诡,父類加載器無(wú)能為力時(shí)才由其子類加載器自行加載。JVM不會(huì)向Java程序提供對(duì)Bootstrap的引用营勤。下面是關(guān)于幾個(gè)類加載器的說(shuō)明:

  • Bootstrap:一般用本地代碼實(shí)現(xiàn)灵嫌,負(fù)責(zé)加載JVM基礎(chǔ)核心類庫(kù)(rt.jar);
  • Extension:從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫(kù)葛作,它的父加載器是Bootstrap寿羞;
  • System:又叫應(yīng)用類加載器,其父類是Extension赂蠢。它是應(yīng)用最廣泛的類加載器绪穆。它從環(huán)境變量classpath或者系統(tǒng)屬性java.class.path所指定的目錄中記載類,是用戶自定義加載器的默認(rèn)父加載器。

Java對(duì)象創(chuàng)建過(guò)程

1.JVM遇到一條新建對(duì)象的指令時(shí)首先去檢查這個(gè)指令的參數(shù)是否能在常量池中定義到一個(gè)類的符號(hào)引用玖院。然后加載這個(gè)類(類加載過(guò)程在后邊講)

2.為對(duì)象分配內(nèi)存菠红。一種辦法“指針碰撞”、一種辦法“空閑列表”难菌,最終常用的辦法“本地線程緩沖分配(TLAB)”

3.將除對(duì)象頭外的對(duì)象內(nèi)存空間初始化為0

4.對(duì)對(duì)象頭進(jìn)行必要設(shè)置

類的生命周期

類的生命周期包括這幾個(gè)部分试溯,加載、連接郊酒、初始化遇绞、使用和卸載,其中前三部是類的加載的過(guò)程,如下圖燎窘;


  • 加載试读,查找并加載類的二進(jìn)制數(shù)據(jù),在Java堆中也創(chuàng)建一個(gè)java.lang.Class類的對(duì)象
  • 連接荠耽,連接又包含三塊內(nèi)容:驗(yàn)證钩骇、準(zhǔn)備、初始化铝量。
    1)驗(yàn)證倘屹,文件格式、元數(shù)據(jù)慢叨、字節(jié)碼纽匙、符號(hào)引用驗(yàn)證;
    2)準(zhǔn)備拍谐,為類的靜態(tài)變量分配內(nèi)存烛缔,并將其初始化為默認(rèn)值;
    3)解析轩拨,把類中的符號(hào)引用轉(zhuǎn)換為直接引用
  • 初始化践瓷,為類的靜態(tài)變量賦予正確的初始值
  • 使用,new出對(duì)象程序中使用
  • 卸載亡蓉,執(zhí)行垃圾回收

Java對(duì)象結(jié)構(gòu)

Java對(duì)象由三個(gè)部分組成:對(duì)象頭晕翠、實(shí)例數(shù)據(jù)、對(duì)齊填充砍濒。

對(duì)象頭由兩部分組成淋肾,第一部分存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù):哈希碼、GC分代年齡爸邢、鎖標(biāo)識(shí)狀態(tài)樊卓、線程持有的鎖、偏向線程ID(一般占32/64 bit)杠河。第二部分是指針類型碌尔,指向?qū)ο蟮念愒獢?shù)據(jù)類型(即對(duì)象代表哪個(gè)類)赶掖。如果是數(shù)組對(duì)象,則對(duì)象頭中還有一部分用來(lái)記錄數(shù)組長(zhǎng)度七扰。

實(shí)例數(shù)據(jù)用來(lái)存儲(chǔ)對(duì)象真正的有效信息(包括父類繼承下來(lái)的和自己定義的)

對(duì)齊填充:JVM要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍(8字節(jié)對(duì)齊)

Java對(duì)象的定位方式

句柄池、直接指針陪白。

如何判斷對(duì)象可以被回收颈走?

判斷對(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)題锐膜。
  • 可達(dá)性分析(Reachability Analysis):從GC Roots開(kāi)始向下搜索,搜索所走過(guò)的路徑稱為引用鏈弛房。當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí)道盏,則證明此對(duì)象是不可用的,不可達(dá)對(duì)象文捶。

JVM的永久代中會(huì)發(fā)生垃圾回收么荷逞?

垃圾回收不會(huì)發(fā)生在永久代,如果永久代滿了或者是超過(guò)了臨界值粹排,會(huì)觸發(fā)完全垃圾回收(Full GC)种远。如果你仔細(xì)查看垃圾收集器的輸出信息,就會(huì)發(fā)現(xiàn)永久代也是被回收的顽耳。這就是為什么正確的永久代大小對(duì)避免Full GC是非常重要的原因坠敷。請(qǐng)參考下Java8:從永久代到元數(shù)據(jù)區(qū)
(注:Java8中已經(jīng)移除了永久代,新加了一個(gè)叫做元數(shù)據(jù)區(qū)的native內(nèi)存區(qū))

引用的分類

  • 強(qiáng)引用:GC時(shí)不會(huì)被回收
  • 軟引用:描述有用但不是必須的對(duì)象射富,在發(fā)生內(nèi)存溢出異常之前被回收
  • 弱引用:描述有用但不是必須的對(duì)象膝迎,在下一次GC時(shí)被回收
  • 虛引用(幽靈引用/幻影引用):無(wú)法通過(guò)虛引用獲得對(duì)象,用PhantomReference實(shí)現(xiàn)虛引用胰耗,虛引用用來(lái)在GC時(shí)返回一個(gè)通知弄抬。

GC是什么?為什么要有GC宪郊?

答:GC是垃圾收集的意思掂恕,內(nèi)存處理是編程人員容易出現(xiàn)問(wèn)題的地方,忘記或者錯(cuò)誤的內(nèi)存回收會(huì)導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰弛槐,Java提供的GC功能可以自動(dòng)監(jiān)測(cè)對(duì)象是否超過(guò)作用域從而達(dá)到自動(dòng)回收內(nèi)存的目的懊亡,Java語(yǔ)言沒(méi)有提供釋放已分配內(nèi)存的顯示操作方法。Java程序員不用擔(dān)心內(nèi)存管理乎串,因?yàn)槔占鲿?huì)自動(dòng)進(jìn)行管理店枣。要請(qǐng)求垃圾收集,可以調(diào)用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉顯示的垃圾回收調(diào)用鸯两。
垃圾回收可以有效的防止內(nèi)存泄露闷旧,有效的使用可以使用的內(nèi)存。垃圾回收器通常是作為一個(gè)單獨(dú)的低優(yōu)先級(jí)的線程運(yùn)行钧唐,不可預(yù)知的情況下對(duì)內(nèi)存堆中已經(jīng)死亡的或者長(zhǎng)時(shí)間沒(méi)有使用的對(duì)象進(jìn)行清除和回收忙灼,程序員不能實(shí)時(shí)的調(diào)用垃圾回收器對(duì)某個(gè)對(duì)象或所有對(duì)象進(jìn)行垃圾回收。在Java誕生初期钝侠,垃圾回收是Java最大的亮點(diǎn)之一该园,因?yàn)榉?wù)器端的編程需要有效的防止內(nèi)存泄露問(wèn)題,然而時(shí)過(guò)境遷帅韧,如今Java的垃圾回收機(jī)制已經(jīng)成為被詬病的東西里初。移動(dòng)智能終端用戶通常覺(jué)得iOS的系統(tǒng)比Android系統(tǒng)有更好的用戶體驗(yàn),其中一個(gè)深層次的原因就在于android系統(tǒng)中垃圾回收的不可預(yù)知性忽舟。

補(bǔ)充:垃圾回收機(jī)制有很多種双妨,包括:分代復(fù)制垃圾回收、標(biāo)記垃圾回收叮阅、增量垃圾回收等方式斥难。標(biāo)準(zhǔn)的Java進(jìn)程既有棧又有堆。棧保存了原始型局部變量帘饶,堆保存了要?jiǎng)?chuàng)建的對(duì)象哑诊。Java平臺(tái)對(duì)堆內(nèi)存回收和再利用的基本算法被稱為標(biāo)記和清除,但是Java對(duì)其進(jìn)行了改進(jìn)及刻,采用“分代式垃圾收集”镀裤。這種方法會(huì)跟Java對(duì)象的生命周期將堆內(nèi)存劃分為不同的區(qū)域,在垃圾收集過(guò)程中缴饭,可能會(huì)將對(duì)象移動(dòng)到不同區(qū)域:

  • 伊甸園(Eden):這是對(duì)象最初誕生的區(qū)域暑劝,并且對(duì)大多數(shù)對(duì)象來(lái)說(shuō),這里是它們唯一存在過(guò)的區(qū)域颗搂。
  • 幸存者樂(lè)園(Survivor):從伊甸園幸存下來(lái)的對(duì)象會(huì)被挪到這里担猛。
  • 終身頤養(yǎng)園(Tenured):這是足夠老的幸存對(duì)象的歸宿。年輕代收集(Minor-GC)過(guò)程是不會(huì)觸及這個(gè)地方的丢氢。當(dāng)年輕代收集不能把對(duì)象放進(jìn)終身頤養(yǎng)園時(shí)傅联,就會(huì)觸發(fā)一次完全收集(Major-GC),這里可能還會(huì)牽扯到壓縮疚察,以便為大對(duì)象騰出足夠的空間蒸走。
    與垃圾回收相關(guān)的JVM參數(shù):

-Xms / -Xmx — 堆的初始大小 / 堆的最大大小
-Xmn — 堆中年輕代的大小
-XX:-DisableExplicitGC — 讓System.gc()不產(chǎn)生任何作用
-XX:+PrintGCDetails — 打印GC的細(xì)節(jié)
-XX:+PrintGCDateStamps — 打印GC操作的時(shí)間戳
-XX:NewSize / XX:MaxNewSize — 設(shè)置新生代大小/新生代最大大小
-XX:NewRatio — 可以設(shè)置老生代和新生代的比例
-XX:PrintTenuringDistribution — 設(shè)置每次新生代GC后輸出幸存者樂(lè)園中對(duì)象年齡的分布
-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設(shè)置老年代閥值的初始值和最大值
-XX:TargetSurvivorRatio:設(shè)置幸存區(qū)的目標(biāo)使用率

判斷一個(gè)對(duì)象應(yīng)該被回收

1.該對(duì)象沒(méi)有與GC Roots相連

2.該對(duì)象沒(méi)有重寫finalize()方法或finalize()已經(jīng)被執(zhí)行過(guò)則直接回收(第一次標(biāo)記)、否則將對(duì)象加入到F-Queue隊(duì)列中(優(yōu)先級(jí)很低的隊(duì)列)在這里finalize()方法被執(zhí)行貌嫡,之后進(jìn)行第二次標(biāo)記比驻,如果對(duì)象仍然應(yīng)該被GC則GC该溯,否則移除隊(duì)列。
(在finalize方法中别惦,對(duì)象很可能和其他 GC Roots中的某一個(gè)對(duì)象建立了關(guān)聯(lián)狈茉,finalize方法只會(huì)被調(diào)用一次,且不推薦使用finalize方法)

回收方法區(qū)

方法區(qū)回收價(jià)值很低掸掸,主要回收廢棄的常量和無(wú)用的類氯庆。

如何判斷無(wú)用的類:

1.該類所有實(shí)例都被回收(Java堆中沒(méi)有該類的對(duì)象)

2.加載該類的ClassLoader已經(jīng)被回收

3.該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方利用反射訪問(wèn)該類

垃圾收集算法

GC最基礎(chǔ)的算法有三種:
標(biāo)記 -清除算法猾漫、復(fù)制算法、標(biāo)記-壓縮算法感凤,我們常用的垃圾回收器一般都采用分代收集算法悯周。

  • 標(biāo)記 -清除算法,“標(biāo)記-清除”(Mark-Sweep)算法陪竿,如它的名字一樣禽翼,算法分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對(duì)象族跛。
  • 復(fù)制算法闰挡,“復(fù)制”(Copying)的收集算法,它將可用內(nèi)存按容量劃分為大小相等的兩塊礁哄,每次只使用其中的一塊长酗。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面桐绒,然后再把已使用過(guò)的內(nèi)存空間一次清理掉夺脾。
  • 標(biāo)記-壓縮算法,標(biāo)記過(guò)程仍然與“標(biāo)記-清除”算法一樣茉继,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理咧叭,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存
  • 分代收集算法烁竭,“分代收集”(Generational Collection)算法菲茬,把Java堆分為新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/li>

垃圾回收器

  • Serial收集器派撕,串行收集器是最古老婉弹,最穩(wěn)定以及效率高的收集器,可能會(huì)產(chǎn)生較長(zhǎng)的停頓终吼,只使用一個(gè)線程去回收马胧。
  • ParNew收集器,ParNew收集器其實(shí)就是Serial收集器的多線程版本衔峰。
  • Parallel收集器佩脊,Parallel Scavenge收集器類似ParNew收集器蛙粘,Parallel收集器更關(guān)注系統(tǒng)的吞吐量。
  • Parallel Old 收集器威彰,Parallel Old是Parallel Scavenge收集器的老年代版本出牧,使用多線程和“標(biāo)記-整理”算法
  • CMS收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器歇盼。
  • G1收集器舔痕,G1 (Garbage-First)是一款面向服務(wù)器的垃圾收集器,主要針對(duì)配備多顆處理器及大容量?jī)?nèi)存的機(jī)器. 以極高概率滿足GC停頓時(shí)間要求的同時(shí),還具備高吞吐量性能特征

GC日志分析

摘錄GC日志一部分(前部分為年輕代gc回收;后部分為full gc回收):

2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs] 
2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]

通過(guò)上面日志分析得出豹缀,PSYoungGen伯复、ParOldGen、PSPermGen屬于Parallel收集器邢笙。其中PSYoungGen表示gc回收前后年輕代的內(nèi)存變化啸如;ParOldGen表示gc回收前后老年代的內(nèi)存變化;PSPermGen表示gc回收前后永久區(qū)的內(nèi)存變化氮惯。young gc 主要是針對(duì)年輕代進(jìn)行內(nèi)存回收比較頻繁叮雳,耗時(shí)短;full gc 會(huì)對(duì)整個(gè)堆內(nèi)存進(jìn)行回城妇汗,耗時(shí)長(zhǎng)帘不,因此一般盡量減少full gc的次數(shù)

調(diào)優(yōu)命令

Sun JDK監(jiān)控和故障處理命令有jps jstat jmap jhat jstack jinfo

  • jps,JVM Process Status Tool,顯示指定系統(tǒng)內(nèi)所有的HotSpot虛擬機(jī)進(jìn)程杨箭。
  • jstat寞焙,JVM statistics Monitoring是用于監(jiān)視虛擬機(jī)運(yùn)行時(shí)狀態(tài)信息的命令,它可以顯示出虛擬機(jī)進(jìn)程中的類裝載互婿、內(nèi)存棺弊、垃圾收集、JIT編譯等運(yùn)行數(shù)據(jù)擒悬。
  • jmap模她,JVM Memory Map命令用于生成heap dump文件
  • jhat,JVM Heap Analysis Tool命令是與jmap搭配使用懂牧,用來(lái)分析jmap生成的dump侈净,jhat內(nèi)置了一個(gè)微型的HTTP/HTML服務(wù)器,生成dump的分析結(jié)果后僧凤,可以在瀏覽器中查看
  • jstack畜侦,用于生成java虛擬機(jī)當(dāng)前時(shí)刻的線程快照。
  • jinfo躯保,JVM Configuration info 這個(gè)命令作用是實(shí)時(shí)查看和調(diào)整虛擬機(jī)運(yùn)行參數(shù)旋膳。

調(diào)優(yōu)工具

常用調(diào)優(yōu)工具分為兩類,jdk自帶監(jiān)控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)途事、GChisto验懊。

  • jconsole擅羞,Java Monitoring and Management Console是從java5開(kāi)始,在JDK中自帶的java監(jiān)控和管理控制臺(tái)义图,用于對(duì)JVM中內(nèi)存减俏,線程和類等的監(jiān)控
  • jvisualvm,jdk自帶全能工具碱工,可以分析內(nèi)存快照娃承、線程快照;監(jiān)控內(nèi)存變化怕篷、GC變化等历筝。
  • MAT,Memory Analyzer Tool廊谓,一個(gè)基于Eclipse的內(nèi)存分析工具梳猪,是一個(gè)快速、功能豐富的Java heap分析工具蹂析,它可以幫助我們查找內(nèi)存泄漏和減少內(nèi)存消耗
  • GChisto舔示,一款專業(yè)分析gc日志的工具

Minor GC與Full GC分別在什么時(shí)候發(fā)生碟婆?

新生代內(nèi)存不夠用時(shí)候發(fā)生MGC也叫YGC电抚,JVM內(nèi)存不夠的時(shí)候發(fā)生FGC

你知道哪些JVM性能調(diào)優(yōu)

  • 設(shè)定堆內(nèi)存大小

-Xmx:堆內(nèi)存最大限制。

  • 設(shè)定新生代大小竖共。
    新生代不宜太小蝙叛,否則會(huì)有大量對(duì)象涌入老年代

-XX:NewSize:新生代大小

-XX:NewRatio 新生代和老生代占比

-XX:SurvivorRatio:伊甸園空間和幸存者空間的占比

  • 設(shè)定垃圾回收器
    年輕代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC

結(jié)束

參考:
jvm面試題
JVM知識(shí)點(diǎn)總覽-高級(jí)Java工程師面試必備

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市公给,隨后出現(xiàn)的幾起案子借帘,更是在濱河造成了極大的恐慌,老刑警劉巖淌铐,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肺然,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡腿准,警方通過(guò)查閱死者的電腦和手機(jī)际起,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吐葱,“玉大人街望,你說(shuō)我怎么就攤上這事〉芘埽” “怎么了灾前?”我有些...
    開(kāi)封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)孟辑。 經(jīng)常有香客問(wèn)我哎甲,道長(zhǎng)蔫敲,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任烧给,我火速辦了婚禮燕偶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘础嫡。我一直安慰自己指么,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布榴鼎。 她就那樣靜靜地躺著伯诬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪巫财。 梳的紋絲不亂的頭發(fā)上盗似,一...
    開(kāi)封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音平项,去河邊找鬼赫舒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛闽瓢,可吹牛的內(nèi)容都是我干的接癌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼扣讼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缺猛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起椭符,我...
    開(kāi)封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤荔燎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后销钝,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體有咨,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蒸健,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了座享。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纵装,死狀恐怖征讲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情橡娄,我是刑警寧澤诗箍,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站挽唉,受9級(jí)特大地震影響滤祖,放射性物質(zhì)發(fā)生泄漏筷狼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一匠童、第九天 我趴在偏房一處隱蔽的房頂上張望埂材。 院中可真熱鬧,春花似錦汤求、人聲如沸俏险。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)竖独。三九已至,卻和暖如春挤牛,著一層夾襖步出監(jiān)牢的瞬間莹痢,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工墓赴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留竞膳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓诫硕,卻偏偏與公主長(zhǎng)得像坦辟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痘括,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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

  • 《深入理解Java虛擬機(jī)》筆記_第一遍 先取看完這本書(JVM)后必須掌握的部分长窄。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,093評(píng)論 1 34
  • 一 滔吠、java虛擬機(jī)底層結(jié)構(gòu)詳解 我們知道纲菌,一個(gè)JVM實(shí)例的行為不光是它自己的事,還涉及到它的子系統(tǒng)疮绷、存儲(chǔ)區(qū)域翰舌、...
    葡萄喃喃囈語(yǔ)閱讀 1,484評(píng)論 0 4
  • 第二部分 自動(dòng)內(nèi)存管理機(jī)制 第二章 java內(nèi)存異常與內(nèi)存溢出異常 運(yùn)行數(shù)據(jù)區(qū)域 程序計(jì)數(shù)器:當(dāng)前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,164評(píng)論 0 2
  • 一、運(yùn)行時(shí)數(shù)據(jù)區(qū)域 Java虛擬機(jī)管理的內(nèi)存包括幾個(gè)運(yùn)行時(shí)數(shù)據(jù)內(nèi)存:方法區(qū)冬骚、虛擬機(jī)棧椅贱、本地方法棧、堆只冻、程序計(jì)數(shù)器庇麦,...
    luhanlin閱讀 545評(píng)論 0 0
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方喜德,同時(shí)不同JDK版本的...
    高廣超閱讀 15,603評(píng)論 3 83