面試系列重啟:JVM 篇
時(shí)間:2021-11-01 14:43:06
手機(jī)看文章
掃描二維碼
隨時(shí)隨地手機(jī)看文章
[導(dǎo)讀]今天跟大家聊聊JVM的面試。但是其實(shí)我不知道這種直接問題答案的形式大家喜歡點(diǎn),還是喜歡我最開始俏皮的面試系列那種風(fēng)格?6000字,發(fā)車!什么是跨平臺(tái)性,已經(jīng)在Java中是如何實(shí)現(xiàn)的?平臺(tái)可以指OS硬件,所謂跨平臺(tái)性,是指語言編寫的程序,可以在多個(gè)系統(tǒng)平臺(tái)上運(yùn)行。字節(jié)碼是通過Jav...
今天跟大家聊聊 JVM 的面試。但是其實(shí)我不知道這種直接問題答案的形式大家喜歡點(diǎn),還是喜歡我最開始俏皮的面試系列那種風(fēng)格?6000 字,發(fā)車!
什么是跨平臺(tái)性,已經(jīng)在Java中是如何實(shí)現(xiàn)的?
平臺(tái)可以指OS 硬件,所謂跨平臺(tái)性,是指語言編寫的程序,可以在多個(gè)系統(tǒng)平臺(tái)上運(yùn)行。字節(jié)碼是通過Java虛擬機(jī)在系統(tǒng)平臺(tái)上運(yùn)行的,只要該系統(tǒng)可以安裝相應(yīng)的java虛擬機(jī),該系統(tǒng)就可以運(yùn)行java程序編譯后的字節(jié)碼文件了,即一次編譯,到處運(yùn)行。JVM是跨平臺(tái)的嗎?
不是的,Java 平臺(tái)的核心是執(zhí)行字節(jié)碼的"虛擬機(jī)器"的概念。無論程序運(yùn)行在哪硬件或操作系統(tǒng)下,此字節(jié)碼都是一樣的。雖然 Java 程序是獨(dú)立于平臺(tái)的,但執(zhí)行這些程序的 Java 虛擬機(jī)代碼并非如此。每個(gè)操作系統(tǒng)或者硬件上都有不同的虛擬機(jī)。JVM是如何工作的?
用戶創(chuàng)建 aobing.java 文件Java 編譯器(javac)把文件編譯到 aobing.class文件,這個(gè)地方會(huì)有一些編譯期優(yōu)化。Java 虛擬機(jī)器加載類,并由解釋器逐條翻譯或即時(shí)編譯器將其編譯為機(jī)器代碼。轉(zhuǎn)換后的機(jī)器代碼是由 CPU 直接執(zhí)行,主流的虛擬機(jī)都是架設(shè)在操作系統(tǒng)之上的。什么時(shí)即時(shí)編譯器?
即時(shí)編譯器是 JRE 的一部分,全稱 Just-In-Time Compiler ,一般稱之為 JIT ,它可顯著的提高 Java 應(yīng)用程序在運(yùn)行時(shí)間的性能。Java 編譯成字節(jié)碼后,這些代碼可以通過 JVM 在許多不同的計(jì)算機(jī)架構(gòu)上進(jìn)行運(yùn)行。在開始運(yùn)行時(shí),JVM 中的解釋器首先開始工作,逐行的將字節(jié)碼解釋為本地機(jī)器碼,意味著 Java 應(yīng)用程序的執(zhí)行速度比本地語言的應(yīng)用程序慢,這是背景。為了提高效率,虛擬機(jī)引入了 JIT 技術(shù),通過將熱點(diǎn)代碼編譯成本機(jī)代碼,以提高 Java 程序的性能。JIT 編譯后的代碼存放在方法區(qū)中。虛擬機(jī)是怎么識(shí)別出熱點(diǎn)代碼的?
目前熱點(diǎn)代碼的探測(cè)有兩種方式:采樣計(jì)數(shù)器HotSpot使用的是計(jì)數(shù)器的方式,它為每個(gè)方法準(zhǔn)備了兩類計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器(Invocation Counter)回邊計(jì)數(shù)器(Back EdgeCounter)。這兩個(gè)計(jì)數(shù)器都有一個(gè)確定的閾值,當(dāng)計(jì)數(shù)器超過閾值溢出了,就會(huì)觸發(fā)JIT編譯。JDK、JRE和JVM
JDK:JDK 是 (SDK) 軟件開發(fā)套件的擴(kuò)展子集,包括用于開發(fā)、調(diào)試和監(jiān)控 Java 應(yīng)用程序的工具。JRE:JRE 稱為 Java 運(yùn)行時(shí)間環(huán)境是 JDK 的一部分,是開發(fā) Java 應(yīng)用程序的一組編程工具。Java 運(yùn)行時(shí)間環(huán)境為執(zhí)行 Java 應(yīng)用程序提供了最低要求,并且它由 Java 虛擬機(jī)器 (JVM) 核心類和核心類庫組成。JVM:JVM是一種可以執(zhí)行字節(jié)碼的虛擬機(jī)器。它是 Java 平臺(tái)的代碼執(zhí)行組件。什么是虛引用?
虛引用是虛擬機(jī)中定義的"非強(qiáng)"引用的級(jí)別之一。4種引用的級(jí)別由高到低依次為強(qiáng)引用、軟引用、弱引用和虛引用。如果一個(gè)對(duì)象具有強(qiáng)引用,那垃圾回收器絕不會(huì)回收它。如果一個(gè)對(duì)象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。弱引用的對(duì)象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。虛引用的對(duì)象,它是如此脆弱以至于我們通過虛引用甚至無法獲取到被引用的對(duì)象,它存在的唯一作用就是當(dāng)它指向的對(duì)象被回收后,虛引用本身會(huì)被加入到引用隊(duì)列中,用作記錄它指向的對(duì)象已被回收。什么是Java的內(nèi)存結(jié)構(gòu)、內(nèi)存模型和對(duì)象模型內(nèi)存結(jié)構(gòu)是和運(yùn)行時(shí)數(shù)據(jù)區(qū)有關(guān)。內(nèi)存模型是指用于屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的并發(fā)效果,是虛擬機(jī)的規(guī)范之一。對(duì)象模型是指java對(duì)象在內(nèi)存中真正的存儲(chǔ)(表示)形式有關(guān)。運(yùn)行時(shí)數(shù)據(jù)區(qū)包括哪幾部分?
存放實(shí)例對(duì)象的堆。用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器生成代碼的方法區(qū)。為了支持多線程的程序計(jì)數(shù)器。用于支持方法的運(yùn)行的虛擬機(jī)棧和本地方法棧。面向?qū)ο蟮膬?yōu)點(diǎn)
模型和真實(shí)世界中的對(duì)象類似,理解起來更容易,又因?yàn)槊嫦驅(qū)ο笥蟹庋b、繼承、多態(tài)的特性,可以設(shè)計(jì)出低耦合高內(nèi)聚的系統(tǒng),易維護(hù)、易復(fù)用、易擴(kuò)展。什么是多態(tài)?
面向?qū)ο蟮牡谌筇匦灾弧?/p>是指同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力。Java作為面向?qū)ο蟮恼Z言,同樣可以描述一個(gè)事物的多種形態(tài)。如Student類繼承了Person類,一個(gè)Student的對(duì)象便既是Student,又是Person。final finally finalize區(qū)別
final可以修飾類、變量、方法,修飾類表示該類不能被繼承、修飾方法表示該方法不能被重寫、修飾變量表示該變量是一個(gè)常量不能被重新賦值。finally一般作用在try-catch代碼塊中,在處理異常的時(shí)候,通常我們將一定要執(zhí)行的代碼方法finally代碼塊中,表示不管是否出現(xiàn)異常,該代碼塊都會(huì)執(zhí)行,一般用來存放一些關(guān)閉資源的代碼。finalize是一個(gè)方法,屬于Object類的一個(gè)方法,而Object類是所有類的父類,該方法一般由垃圾回收器來調(diào)用。說一下JVM加載一個(gè)類的過程
JVM 中類的裝載是由類加載器,也就是ClassLoader,和它的子類來實(shí)現(xiàn)的,Java 中的類加載器是一個(gè)重要的 Java 運(yùn)行時(shí)系統(tǒng)組件,它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入類文件中的類。由于 Java 的跨平臺(tái)性, 經(jīng)過編譯的 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è)類還沒有被初始化,那么就先初始化父類;2)如果類中存在初始化語句, 就依次執(zhí)行這些初始化語句。類加載器有哪些,各有什么作用?
從JDK 1.2開始, 類加載過程采取了雙親委派機(jī)制。更好的保證了 Java 平臺(tái)的安全性,在該機(jī)制中,JVM 自帶的 Bootstrap 是根加載器, 其他的加載器都有且僅有一個(gè)父類加載器。類的加載首先請(qǐng)求父類加載器加載,父類加載器無能為力時(shí)才由其子類加載。JVM 不會(huì)向 Java 程序提供對(duì) Bootstrap 的引用。- 根加載器(BootStrap)一般用本地代碼實(shí)現(xiàn),負(fù)責(zé)加載 JVM 基礎(chǔ)核心類庫(rt.jar)。
- 擴(kuò)展加載器( ExtClassLoader)從 java.ext.dirs 系統(tǒng)屬性所指定的目錄中加載類庫,它的父加載器是 Bootstrap。
- 系統(tǒng)加載器( AppClassLoader) 又叫應(yīng)用類加載器,其父類是 Extension。它是應(yīng)用最廣泛的類加載器。它從環(huán)境變量 classpath 或者系統(tǒng)屬性 java.class.path 所指定的目錄中記載類,是用戶自定義加載器的默認(rèn)父加載器。
- 用戶自定義類加載器 ( java.lang.ClassLoader 的子類)父類是AppClassLoader。
error和exception有什么區(qū)別?
error 表示恢復(fù)不是不可能但很困難的情況下的一種嚴(yán)重問題。比如說內(nèi)存溢出。不可能指望程序能處理這樣的情況。exception表示一種設(shè)計(jì)或?qū)崿F(xiàn)問題。也就是說,它表示如果程序運(yùn)行正常,從不會(huì)發(fā)生的情況。System.out.println(),System是什么,out是什么,println又是什么?
System是捆綁在java.lang包中的最終類。out是打印流類的參考,它是系統(tǒng)類的靜態(tài)成員。println是一種打印流類的方法,它捆綁在 java.io 包中打印輸出。方法區(qū)、永久區(qū)和元數(shù)據(jù)區(qū)它們之間是什么關(guān)系?
方法區(qū)是jvm規(guī)范里要求的,永久區(qū)是Hotspot虛擬機(jī)對(duì)方法區(qū)的具體實(shí)現(xiàn),前者是規(guī)范,后者是實(shí)現(xiàn)方式。jdk1.8作了改變。說說GC是什么,以及為什么要有GC?
GC 是垃圾收集的意思,內(nèi)存處理是編程人員容易出現(xiàn)問題的地方,忘記或者錯(cuò)誤的內(nèi)存回收會(huì)導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰。Java 虛擬機(jī)提供的 GC 功能可以自動(dòng)監(jiān)測(cè)對(duì)象是否超過作用域從而達(dá)到自動(dòng)回收內(nèi)存的目的,Java 語言沒有提供釋放已分配內(nèi)存的顯示操作方法。Java 程序員不用擔(dān)心內(nèi)存管理, 因?yàn)槔占鲿?huì)自動(dòng)進(jìn)行管理。GC中STW是什么?
Java中Stop-The-World機(jī)制簡(jiǎn)稱STW,是在執(zhí)行垃圾收集算法時(shí),Java應(yīng)用程序的其他所有線程(除了垃圾收集)都被掛起。Java中一種全局暫停現(xiàn)象,全局停頓,所有Java代碼停止,native代碼可以執(zhí)行,但不能與JVM交互。如何識(shí)別出垃圾?
常用有兩種方式:引用計(jì)數(shù)法,這種難以解決對(duì)象之間的循環(huán)引用的問題。可達(dá)性分析算法,主流的JVM采用的是這種方式。簡(jiǎn)單聊聊垃圾回收算法
- 標(biāo)記-清除算法,如它的名字一樣,算法分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對(duì)象。
- 復(fù)制算法,它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。缺點(diǎn)是浪費(fèi)空間,優(yōu)點(diǎn)是回收速度快,沒碎片。
- 標(biāo)記-壓縮算法,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),完成碎片整理。
- 分代收集算法,把Java堆分為新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴?,我覺得它更像是一種思想,而不是算法。
你知道有哪些垃圾回收器?
- 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í),還具備高吞吐量性能特征
基于棧和寄存器的指令集架構(gòu)是什么?
前者的指令運(yùn)行過程,需要借助棧這個(gè)數(shù)據(jù)結(jié)構(gòu)來完成,主要的優(yōu)點(diǎn)就是可移植,缺點(diǎn)是執(zhí)行速度慢。后者的寄存器指令由硬件直接提供,速度很快,但是缺點(diǎn)是和硬件強(qiáng)綁定。我們主流的java虛擬機(jī)采用的都是基于棧的指令集架構(gòu)。虛擬機(jī)棧是什么?
JVM規(guī)范讓每個(gè)Java線程擁有自己的獨(dú)立的JVM棧,也就是Java方法的調(diào)用棧。當(dāng)方法調(diào)用的時(shí)候,會(huì)生成一個(gè)棧幀。方法的調(diào)用返回過程,其實(shí)就是棧幀的入棧出棧。棧幀是保存在虛擬機(jī)棧中的,棧幀存儲(chǔ)了方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和方法返回地址等信息。線程運(yùn)行過程中,只有一個(gè)棧幀是處于活躍狀態(tài),稱為“當(dāng)前活躍棧幀”,當(dāng)前活動(dòng)棧幀始終是虛擬機(jī)棧的棧頂元素。程序計(jì)數(shù)器為什么是私有的?
虛擬機(jī)是支持多線程并發(fā)的,程序計(jì)數(shù)器私有主要是為了線程切換后能恢復(fù)到正確的執(zhí)行位置。在多線程的情況下,程序計(jì)數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置,從而當(dāng)線程被切換回來的時(shí)候能夠知道該線程上次運(yùn)行到哪兒了。需要注意的是,如果執(zhí)行的是 native 修飾的本地方法,那么程序計(jì)數(shù)器記錄的是 undefined 地址,只有執(zhí)行的是 Java 代碼時(shí)程序計(jì)數(shù)器記錄的才是下一條指令的地址。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.拋出異常的位置Java對(duì)象的創(chuàng)建過程清楚嗎?
- JVM 遇到一條新建對(duì)象的指令時(shí)首先去檢查這個(gè)指令的參數(shù)是否能在常量池中定義到一個(gè)類的符號(hào)引用。然后加載這個(gè)類(類加載過程在后邊講)
- 為對(duì)象分配內(nèi)存。一種辦法“指針碰撞”、一種辦法“空閑列表”,最終常用的辦法“本地線程緩沖分配(TLAB)”
- 將除對(duì)象頭外的對(duì)象內(nèi)存空間初始化為 0
- 對(duì)對(duì)象頭進(jìn)行必要設(shè)置
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ì)象頭中還有一部分用來記錄數(shù)組長(zhǎng)度。
- 實(shí)例數(shù)據(jù)用來存儲(chǔ)對(duì)象真正的有效信息(包括父類繼承下來的和自己定義的)
- 對(duì)齊填充:JVM 要求對(duì)象起始地址必須是 8 字節(jié)的整數(shù)倍(8 字節(jié)對(duì)齊)
Java 對(duì)象的定位方式你清楚嗎?
句柄池、直接指針。方法區(qū)和永久代有什么區(qū)別
永久代又叫 Perm 區(qū),只存在于 HotSpot JVM 中,并且只存在于 JDK 1.7 和之前的版本中,JDK 1.8 中已經(jīng)徹底移除了永久代,JDK 1.8 中引入了一個(gè)新的內(nèi)存區(qū)域叫 metaspace。- 并不是所有的 JVM 中都有永久代,IBM 的 9,Oracle 的 JRocket 都沒有永久代。
- 永久代是實(shí)現(xiàn)層面的東西。
- 永久代里面存的東西基本上就是方法區(qū)規(guī)定的那些東西。
為什么要用 metaspace 替換 permspace 呢
主要有如下幾點(diǎn):- 字符串存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出。
- 類及方法的信息等比較難確定其大小,因此對(duì)于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出。
- 永久代會(huì)為 GC 帶來不必要的復(fù)雜度,并且回收效率偏低。
- 移除永久代是為融合 HotSpot JVM 與 JRockit VM 而做出的努力,因?yàn)?JRockit 沒有永久代,不需要配置永久代。