當(dāng)前位置:首頁 > 公眾號(hào)精選 > 小林coding
[導(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。
加載一個(gè)類兩次

可以加載兩次,但是第二次會(huì)把第一次覆蓋。

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)建過程清楚嗎?

  1. JVM 遇到一條新建對(duì)象的指令時(shí)首先去檢查這個(gè)指令的參數(shù)是否能在常量池中定義到一個(gè)類的符號(hào)引用。然后加載這個(gè)類(類加載過程在后邊講)
  2. 為對(duì)象分配內(nèi)存。一種辦法“指針碰撞”、一種辦法“空閑列表”,最終常用的辦法“本地線程緩沖分配(TLAB)”
  3. 將除對(duì)象頭外的對(duì)象內(nèi)存空間初始化為 0
  4. 對(duì)對(duì)象頭進(jìn)行必要設(shè)置

Java 對(duì)象結(jié)構(gòu)了解過嗎?

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

  1. 對(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)度。
  2. 實(shí)例數(shù)據(jù)用來存儲(chǔ)對(duì)象真正的有效信息(包括父類繼承下來的和自己定義的)
  3. 對(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ī)定的那些東西。
因此,我們可以說,在 JDK 1.7 中永久代是方法區(qū)的一種實(shí)現(xiàn),當(dāng)然,在 HotSpot JDK 1.8 中 metaspace 可以看成是方法區(qū)的一種實(shí)現(xiàn)。

為什么要用 metaspace 替換 permspace 呢

主要有如下幾點(diǎn):

  • 字符串存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出。
  • 類及方法的信息等比較難確定其大小,因此對(duì)于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出。
  • 永久代會(huì)為 GC 帶來不必要的復(fù)雜度,并且回收效率偏低。
  • 移除永久代是為融合 HotSpot JVM 與 JRockit VM 而做出的努力,因?yàn)?JRockit 沒有永久代,不需要配置永久代。

END

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉