?1.什么是 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)度和其他特性缕粹。
?2.Java 內(nèi)存結(jié)構(gòu)稚茅?
方法區(qū)和對(duì)是所有線程共享的內(nèi)存區(qū)域;而 java 棧平斩、本地方法棧和程序員計(jì)數(shù)器是運(yùn)行是線程私有的內(nèi)存區(qū)域亚享。
3.內(nèi)存模型以及分區(qū),需要詳細(xì)到每個(gè)區(qū)放什么绘面?
JVM 分為堆區(qū)和棧區(qū)欺税,還有方法區(qū)侈沪,初始化的對(duì)象放在堆里面,引用放在棧里面晚凿,class 類信息常量池(static 常量和 static 變量)等放在方法區(qū)亭罪。 new:
方法區(qū):主要是存儲(chǔ)類信息,常量池(static 常量和 static 變量)歼秽,編譯后的代碼(字節(jié)碼)等數(shù)據(jù)
堆:初始化的對(duì)象应役,成員變量 (那種非 static 的變量),所有的對(duì)象實(shí)例和數(shù)組都要在堆上分配
棧:棧的結(jié)構(gòu)是棧幀組成的燥筷,調(diào)用一個(gè)方法就壓入一幀箩祥,幀上面存儲(chǔ)局部變量表,操作數(shù)棧肆氓,方法出口等信息袍祖,局部變量表存放的是 8 大基礎(chǔ)類型加上一個(gè)應(yīng)用類型,所以還是一個(gè)指向地址的指針
本地方法棧:主要為 Native 方法服務(wù)
程序計(jì)數(shù)器:記錄當(dāng)前線程執(zhí)行的行號(hào)
4.堆里面的分區(qū):Eden谢揪,survival (from+ to)蕉陋,老年代,各自的特點(diǎn)拨扶?
堆里面分為新生代和老生代(java8 取消了永久代凳鬓,采用了 Metaspace),新生代包含 Eden+Survivor 區(qū)屈雄,survivor 區(qū)里面分為 from 和 to 區(qū)村视,內(nèi)存回收時(shí)官套,如果用的是復(fù)制算法酒奶,從 from 復(fù)制到 to,當(dāng)經(jīng)過(guò)一次或者多次 GC 之后奶赔,存活下來(lái)的對(duì)象會(huì)被移動(dòng)到老年區(qū)惋嚎,當(dāng) JVM 內(nèi)存不夠用的時(shí)候,會(huì)觸發(fā) Full GC站刑,清理 JVM 老年區(qū)當(dāng)新生區(qū)滿了之后會(huì)觸發(fā) YGC,先把存活的對(duì)象放到其中一個(gè) Survice 區(qū)另伍,然后進(jìn)行垃圾清理。
因?yàn)槿绻麅H僅清理需要?jiǎng)h除的對(duì)象绞旅,這樣會(huì)導(dǎo)致內(nèi)存碎片摆尝,因此一般會(huì)把 Eden 進(jìn)行完全的清理,然后整理內(nèi)存因悲。
那么下次 GC 的時(shí)候堕汞,就會(huì)使用下一個(gè) Survive,這樣循環(huán)使用晃琳。
如果有特別大的對(duì)象讯检,新生代放不下琐鲁,就會(huì)使用老年代的擔(dān)保,直接放到老年代里面人灼。 因?yàn)?JVM 認(rèn)為围段,一般大對(duì)象的存活時(shí)間一般比較久遠(yuǎn)。
5 .解釋內(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凿跳。
Stringstr =newString("hello");
上面的語(yǔ)句中變量 str 放在棧上件豌,用 new 創(chuàng)建出來(lái)的字符串對(duì)象放在堆上,而”hello”這個(gè)字面量是放在方法區(qū)的控嗜。
補(bǔ)充 1:較新版本的 Java(從 Java 6 的某個(gè)更新開始)中茧彤,由于 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 =newStringBuilder("go")? ? .append("od").toString();System.out.println(s1.intern() == s1);String s2 =newStringBuilder("ja")? ? .append("va").toString();System.out.println(s2.intern() == s2);
6.GC 的兩種判定方法?
引用計(jì)數(shù)法:指的是如果某個(gè)地方引用了這個(gè)對(duì)象就+1痹愚,如果失效了就-1,當(dāng)為 0 就 會(huì)回收但是 JVM 沒(méi)有用這種方式蛔糯,因?yàn)闊o(wú)法判定相互循環(huán)引用(A 引用 B,B 引用 A) 的情況拯腮。 引用鏈法:通過(guò)一種 GC ROOT 的對(duì)象(方法區(qū)中靜態(tài)變量引用的對(duì)象等-static 變 量)來(lái)判斷,如果有一條鏈能夠到達(dá) GC ROOT 就說(shuō)明蚁飒,不能到達(dá) GC ROOT 就說(shuō)明 可以回收
7.SafePoint 是什么?
比如 GC 的時(shí)候必須要等到 Java 線程都進(jìn)入到 safepoint 的時(shí)候 VMThread 才能開始執(zhí)行 GC 1.循環(huán)的末尾 (防止大循環(huán)的時(shí)候一直不進(jìn)入 safepoint动壤,而其他線程在等待它進(jìn)入 safepoint) 2.方法返回前 3.調(diào)用方法的 call 之后 4.拋出異常的位置
8.GC 的三種收集方法:標(biāo)記清除、標(biāo)記整理淮逻、復(fù)制算法的原理與特點(diǎn)琼懊,分別用在什么地方,如果讓你優(yōu)化收集方法爬早,有什么思路哼丈?
先標(biāo)記,標(biāo)記完畢之后再清除筛严,效率不高醉旦,會(huì)產(chǎn)生碎片 復(fù)制算法:分為 8:1 的 Eden 區(qū)和 survivor 區(qū),就是上面談到的 YGC 標(biāo)記整理:標(biāo)記完畢之后桨啃,讓所有存活的對(duì)象向一端移動(dòng)
9.GC 收集器有哪些车胡?CMS 收集器與 G1 收集器的特點(diǎn)?
并行收集器:串行收集器使用一個(gè)單獨(dú)的線程進(jìn)行收集优幸,GC 時(shí)服務(wù)有停頓時(shí)間 串行收集器:次要回收中使用多線程來(lái)執(zhí)行 CMS 收集器是基于“標(biāo)記—清除”算法實(shí)現(xiàn)的吨拍,經(jīng)過(guò)多次標(biāo)記才會(huì)被清除 G1 從整體來(lái)看是基于“標(biāo)記—整理”算法實(shí)現(xiàn)的收集器褪猛,從局部(兩個(gè) Region 之間)上來(lái)看是基于“復(fù)制”算法實(shí)現(xiàn)的
10.Minor GC 與 Full GC 分別在什么時(shí)候發(fā)生网杆?
新生代內(nèi)存不夠用時(shí)候發(fā)生 MGC 也叫 YGC,JVM 內(nèi)存不夠的時(shí)候發(fā)生 FGC
11. 幾種常用的內(nèi)存調(diào)試工具:jmap伊滋、jstack碳却、jconsole、jhat笑旺?
jstack 可以看當(dāng)前棧的情況昼浦,jmap 查看內(nèi)存,jhat 進(jìn)行 dump 堆的信息 mat(eclipse 的也要了解一下)
12.什么是類的加載
類的加載指的是將類的.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)的接口。
13.類加載器
啟動(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.*開頭的類)槐壳,開發(fā)者可以直接使用擴(kuò)展類加載器。
應(yīng)用程序類加載器:Application ClassLoader喜每,該類加載器由 sun.misc.Launcher$AppClassLoader 來(lái)實(shí)現(xiàn)宏粤,它負(fù)責(zé)加載用戶類路徑(ClassPath)所指定的類,開發(fā)者可以直接使用該類加載器
14.描述一下 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)開始蹋绽,類加載過(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)父加載器胀葱。
15.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è)置
16.類的生命周期
類的生命周期包括這幾個(gè)部分,加載轧葛、連接搂抒、初始化、使用和卸載朝群,其中前三部是類的加載的過(guò)程,如下圖燕耿;
java 類加載需要經(jīng)歷以下 幾個(gè)過(guò)程:
加載
加載時(shí)類加載的第一個(gè)過(guò)程,在這個(gè)階段姜胖,將完成以下三件事情:
1.通過(guò)一個(gè)類的全限定名獲取該類的二進(jìn)制流。
2.將該二進(jìn)制流中的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法去運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)淀散。
3.在內(nèi)存中生成該類的 Class 對(duì)象右莱,作為該類的數(shù)據(jù)訪問(wèn)入口蚜锨。
驗(yàn)證
驗(yàn)證的目的是為了確保 Class 文件的字節(jié)流中的信息不回危害到虛擬機(jī).在該階段主要完成以下四鐘驗(yàn)證:
文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合 Class 文件的規(guī)范,如主次版本號(hào)是否在當(dāng)前虛擬機(jī)范圍內(nèi)慢蜓,常量池中的常量是否有不被支持的類型.
元數(shù)據(jù)驗(yàn)證:對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析亚再,如這個(gè)類是否有父類,是否集成了不被繼承的類等晨抡。
字節(jié)碼驗(yàn)證:是整個(gè)驗(yàn)證過(guò)程中最復(fù)雜的一個(gè)階段氛悬,通過(guò)驗(yàn)證數(shù)據(jù)流和控制流的分析,確定程序語(yǔ)義是否正確耘柱,主要針對(duì)方法體的驗(yàn)證如捅。 如: 方法中的類型轉(zhuǎn)換是否正確,跳轉(zhuǎn)指令是否正確等调煎。
符號(hào)引用驗(yàn)證: 這個(gè)動(dòng)作在后面的解析過(guò)程中發(fā)生镜遣,主要是為了確保解析動(dòng)作能正確執(zhí)行。
準(zhǔn)備
準(zhǔn)備階段是為類的靜態(tài)變量分配內(nèi)存并將其初始化為默認(rèn)值士袄,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配悲关。準(zhǔn)備階段不分配類中的實(shí)例變量的內(nèi)存,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在 Java 堆中娄柳。
publicstaticintvalue=123;//在準(zhǔn)備階段 value 初始值為 0 寓辱。在初始化階段才會(huì)變?yōu)?23。
解析
該階段主要完成符號(hào)引用到直接引用的轉(zhuǎn)換動(dòng)作赤拒。解析動(dòng)作并不一定在初始化動(dòng)作完成之前讶舰,也有可能在初始化之后。
初始化
初始化時(shí)類加載的最后一步需了,前面的類加載過(guò)程跳昼,除了在加載階段用戶應(yīng)用程序可以通過(guò)自定義類加載器參與之外,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制肋乍。 到了初始化階段鹅颊,才真正開始執(zhí)行類中定義的 Java 程序。
17.簡(jiǎn)述 java 類加載機(jī)制?
虛擬機(jī)把描述類的數(shù)據(jù)從 Class 文件加載到內(nèi)存墓造,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)堪伍,解析和初始化,最終形成可以被虛擬機(jī)直接使用的 java 類型觅闽。
18.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ì)齊)
19.Java 對(duì)象的定位方式
句柄池抡砂、直接指針。
20.如和判斷一個(gè)對(duì)象是否存活?(或者 GC 對(duì)象的判定方法)
判斷一個(gè)對(duì)象是否存活有兩種方法:
1.引用計(jì)數(shù)法
所謂引用計(jì)數(shù)法就是給每一個(gè)對(duì)象設(shè)置一個(gè)引用計(jì)數(shù)器恬涧,每當(dāng)有一個(gè)地方引用這個(gè)對(duì)象時(shí)注益,就將計(jì)數(shù)器加一,引用失效時(shí)气破,計(jì)數(shù)器就減一聊浅。當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)器為零時(shí),說(shuō)明此對(duì)象沒(méi)有被引用现使,也就是“死對(duì)象”,將會(huì)被垃圾回收低匙。 引用計(jì)數(shù)法有一個(gè)缺陷就是無(wú)法解決循環(huán)引用問(wèn)題,也就是說(shuō)當(dāng)對(duì)象 A 引用對(duì)象 B碳锈,對(duì)象 B 又引用者對(duì)象 A顽冶,那么此時(shí) A,B 對(duì)象的引用計(jì)數(shù)器都不為零,也就造成無(wú)法完成垃圾回收售碳,所以主流的虛擬機(jī)都沒(méi)有采用這種算法强重。
2.可達(dá)性算法(引用鏈法)
該算法的思想是:從一個(gè)被稱為 GC Roots 的對(duì)象開始向下搜索,如果一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連時(shí)贸人,則說(shuō)明此對(duì)象不可用间景。 在 java 中可以作為 GC Roots 的對(duì)象有以下幾種: 虛擬機(jī)棧中引用的對(duì)象
方法區(qū)類靜態(tài)屬性引用的對(duì)象
方法區(qū)常量池引用的對(duì)象
本地方法棧 JNI 引用的對(duì)象
雖然這些算法可以判定一個(gè)對(duì)象是否能被回收,但是當(dāng)滿足上述條件時(shí)艺智,一個(gè)對(duì)象比不一定會(huì)被回收倘要。當(dāng)一個(gè)對(duì)象不可達(dá) GC Root 時(shí),這個(gè)對(duì)象并不會(huì)立馬被回收十拣,而是出于一個(gè)死緩的階段封拧,若要被真正的回收需要經(jīng)歷兩次標(biāo)記如果對(duì)象在可達(dá)性分析中沒(méi)有與 GC Root 的引用鏈,那么此時(shí)就會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選夭问,篩選的條件是是否有必要執(zhí)行 finalize()方法泽西。當(dāng)對(duì)象沒(méi)有覆蓋 finalize()方法或者已被虛擬機(jī)調(diào)用過(guò),那么就認(rèn)為是沒(méi)必要的缰趋。
如果該對(duì)象有必要執(zhí)行 finalize()方法捧杉,那么這個(gè)對(duì)象將會(huì)放在一個(gè)稱為 F-Queue 的對(duì)隊(duì)列中陕见,虛擬機(jī)會(huì)觸發(fā)一個(gè) Finalize()線程去執(zhí)行,此線程是低優(yōu)先級(jí)的糠溜,并且虛擬機(jī)不會(huì)承諾一直等待它運(yùn)行完淳玩,這是因?yàn)槿绻?finalize()執(zhí)行緩慢或者發(fā)生了死鎖直撤,那么就會(huì)造成 FQueue 隊(duì)列一直等待非竿,造成了內(nèi)存回收系統(tǒng)的崩潰。 GC 對(duì)處于 F-Queue 中的對(duì)象進(jìn)行第二次被標(biāo)記谋竖,這時(shí)红柱,該對(duì)象將被移除”即將回收”集合,等待回收蓖乘。
21.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ū))
22.簡(jiǎn)述 java 內(nèi)存分配與回收策率以及 Minor GC 和 Major GC岗宣?
1.對(duì)象優(yōu)先在堆的 Eden 區(qū)分配蚂会。
2.大對(duì)象直接進(jìn)入老年代.
3.長(zhǎng)期存活的對(duì)象將直接進(jìn)入老年代.,當(dāng) Eden 區(qū)沒(méi)有足夠的空間進(jìn)行分配時(shí)耗式,虛擬機(jī)會(huì)執(zhí)行一次 Minor GC.Minor Gc 通常發(fā)生在新生代的 Eden 區(qū)胁住,在這個(gè)區(qū)的對(duì)象生存期短,往往發(fā)生 Gc 的頻率較高刊咳, 回收速度比較快;Full Gc/Major GC 發(fā)生在老年代彪见,一般情況下,觸發(fā)老年代 GC 的時(shí)候不會(huì)觸發(fā) Minor GC,但是通過(guò)配置娱挨,可以在 Full GC 之前進(jìn)行一次 MinorGC 這樣可以加快老年代的回收速度余指。
23.判斷一個(gè)對(duì)象應(yīng)該被回收
該對(duì)象沒(méi)有與 GC Roots 相連
該對(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 方法)
24.回收方法區(qū)
方法區(qū)回收價(jià)值很低缸濒,主要回收廢棄的常量和無(wú)用的類。
如何判斷無(wú)用的類:
該類所有實(shí)例都被回收(Java 堆中沒(méi)有該類的對(duì)象)
加載該類的 ClassLoader 已經(jīng)被回收
該類對(duì)應(yīng)的 java.lang.Class 對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方利用反射訪問(wèn)該類
25.垃圾收集算法
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ù)氖占惴ā?/p>
26.垃圾回收器
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í),還具備高吞吐量性能特征
27.GC 日志分析
摘錄 GC 日志一部分(前部分為年輕代 gc 回收返奉;后部分為 full gc 回收):
2016-07-05T10:43:18.093+0800:25.395:[GC[PSYoungGen:274931K->10738K(274944K)]371093K->147186K(450048K),0.0668480secs] [Times:user=0.17sys=0.08,real=0.07secs]2016-07-05T10:43:18.160+0800:25.462:[FullGC[PSYoungGen:10738K->0K(274944K)] [ParOldGen:136447K->140379K(302592K)]147186K->140379K(577536K)[PSPermGen:85411K->85376K(171008K)],0.6763541secs] [Times:user=1.75sys=0.02,real=0.68secs]
通過(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ù)
28.調(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ù)吼肥。
29.調(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 開始动猬,在 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 日志的工具
30.Minor GC 與 Full GC 分別在什么時(shí)候發(fā)生?
新生代內(nèi)存不夠用時(shí)候發(fā)生 MGC 也叫 YGC肥卡,JVM 內(nèi)存不夠的時(shí)候發(fā)生 FGC