當前位置:首頁 > 公眾號精選 > 小林coding
[導讀]今天跟大家聊聊JVM的面試。但是其實我不知道這種直接問題答案的形式大家喜歡點,還是喜歡我最開始俏皮的面試系列那種風格?6000字,發(fā)車!什么是跨平臺性,已經(jīng)在Java中是如何實現(xiàn)的?平臺可以指OS硬件,所謂跨平臺性,是指語言編寫的程序,可以在多個系統(tǒng)平臺上運行。字節(jié)碼是通過Jav...

今天跟大家聊聊 JVM 的面試。

但是其實我不知道這種直接問題答案的形式大家喜歡點,還是喜歡我最開始俏皮的面試系列那種風格?

6000 字,發(fā)車!

什么是跨平臺性,已經(jīng)在Java中是如何實現(xiàn)的?

平臺可以指OS 硬件,所謂跨平臺性,是指語言編寫的程序,可以在多個系統(tǒng)平臺上運行。

字節(jié)碼是通過Java虛擬機在系統(tǒng)平臺上運行的,只要該系統(tǒng)可以安裝相應的java虛擬機,該系統(tǒng)就可以運行java程序編譯后的字節(jié)碼文件了,即一次編譯,到處運行。

JVM是跨平臺的嗎?

不是的,Java 平臺的核心是執(zhí)行字節(jié)碼的"虛擬機器"的概念。無論程序運行在哪硬件或操作系統(tǒng)下,此字節(jié)碼都是一樣的。雖然 Java 程序是獨立于平臺的,但執(zhí)行這些程序的 Java 虛擬機代碼并非如此。每個操作系統(tǒng)或者硬件上都有不同的虛擬機。

JVM是如何工作的?

用戶創(chuàng)建 aobing.java 文件

Java 編譯器(javac)把文件編譯到 aobing.class文件,這個地方會有一些編譯期優(yōu)化。

Java 虛擬機器加載類,并由解釋器逐條翻譯或即時編譯器將其編譯為機器代碼。

轉換后的機器代碼是由 CPU 直接執(zhí)行,主流的虛擬機都是架設在操作系統(tǒng)之上的。

什么時即時編譯器?

即時編譯器是 JRE 的一部分,全稱 Just-In-Time Compiler ,一般稱之為 JIT ,它可顯著的提高 Java 應用程序在運行時間的性能。

Java 編譯成字節(jié)碼后,這些代碼可以通過 JVM 在許多不同的計算機架構上進行運行。在開始運行時,JVM 中的解釋器首先開始工作,逐行的將字節(jié)碼解釋為本地機器碼,意味著 Java 應用程序的執(zhí)行速度比本地語言的應用程序慢,這是背景。

為了提高效率,虛擬機引入了 JIT 技術,通過將熱點代碼編譯成本機代碼,以提高 Java 程序的性能。

JIT 編譯后的代碼存放在方法區(qū)中。

虛擬機是怎么識別出熱點代碼的?

目前熱點代碼的探測有兩種方式:

采樣

計數(shù)器

HotSpot使用的是計數(shù)器的方式,它為每個方法準備了兩類計數(shù)器:

方法調用計數(shù)器(Invocation Counter)

回邊計數(shù)器(Back EdgeCounter)。

這兩個計數(shù)器都有一個確定的閾值,當計數(shù)器超過閾值溢出了,就會觸發(fā)JIT編譯。

JDK、JRE和JVM

JDK:JDK 是 (SDK) 軟件開發(fā)套件的擴展子集,包括用于開發(fā)、調試和監(jiān)控 Java 應用程序的工具。

JRE:JRE 稱為 Java 運行時間環(huán)境是 JDK 的一部分,是開發(fā) Java 應用程序的一組編程工具。Java 運行時間環(huán)境為執(zhí)行 Java 應用程序提供了最低要求,并且它由 Java 虛擬機器 (JVM) 核心類和核心類庫組成。

JVM:JVM是一種可以執(zhí)行字節(jié)碼的虛擬機器。它是 Java 平臺的代碼執(zhí)行組件。

什么是虛引用?

虛引用是虛擬機中定義的"非強"引用的級別之一。

4種引用的級別由高到低依次為強引用、軟引用、弱引用和虛引用。

如果一個對象具有強引用,那垃圾回收器絕不會回收它。

如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。

弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。

虛引用的對象,它是如此脆弱以至于我們通過虛引用甚至無法獲取到被引用的對象,它存在的唯一作用就是當它指向的對象被回收后,虛引用本身會被加入到引用隊列中,用作記錄它指向的對象已被回收。

什么是Java的內存結構、內存模型和對象模型

內存結構是和運行時數(shù)據(jù)區(qū)有關。

內存模型是指用于屏蔽掉各種硬件和操作系統(tǒng)的內存訪問差異,以實現(xiàn)讓Java程序在各種平臺下都能達到一致的并發(fā)效果,是虛擬機的規(guī)范之一。

對象模型是指java對象在內存中真正的存儲(表示)形式有關。

運行時數(shù)據(jù)區(qū)包括哪幾部分?

存放實例對象的堆。

用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器生成代碼的方法區(qū)。

為了支持多線程的程序計數(shù)器。

用于支持方法的運行的虛擬機棧和本地方法棧。

面向對象的優(yōu)點

模型和真實世界中的對象類似,理解起來更容易,又因為面向對象有封裝、繼承、多態(tài)的特性,可以設計出低耦合高內聚的系統(tǒng),易維護、易復用、易擴展。

什么是多態(tài)?

面向對象的第三大特性之一。

是指同一個行為具有多個不同表現(xiàn)形式或形態(tài)的能力。

Java作為面向對象的語言,同樣可以描述一個事物的多種形態(tài)。如Student類繼承了Person類,一個Student的對象便既是Student,又是Person。

final finally finalize區(qū)別

final可以修飾類、變量、方法,修飾類表示該類不能被繼承、修飾方法表示該方法不能被重寫、修飾變量表示該變量是一個常量不能被重新賦值。

finally一般作用在try-catch代碼塊中,在處理異常的時候,通常我們將一定要執(zhí)行的代碼方法finally代碼塊

中,表示不管是否出現(xiàn)異常,該代碼塊都會執(zhí)行,一般用來存放一些關閉資源的代碼。

finalize是一個方法,屬于Object類的一個方法,而Object類是所有類的父類,該方法一般由垃圾回收器來調

用。

說一下JVM加載一個類的過程

JVM 中類的裝載是由類加載器,也就是ClassLoader,和它的子類來實現(xiàn)的,Java 中的類加載器是一個重要的 Java 運行時系統(tǒng)組件,它負責在運行時查找和裝入類文件中的類。

由于 Java 的跨平臺性, 經(jīng)過編譯的 Java 源程序并不是一個可執(zhí)行程序, 而是一個或多個類文件。當 Java 程序需要使用某個類時,JVM 會確保這個類已經(jīng)被加載、連接( 驗證、 準備和解析)和初始化。

類的加載是指把類的.class 文件中的數(shù)據(jù)讀入到內存中,通常是創(chuàng)建一個字節(jié)數(shù)組讀入.class 文件,然后產(chǎn)生與所加載類對應的 Class 對象。加載完成后, Class 對象還不完整, 所以此時的類還不可用。當類被加載后就進入連接階段, 這一階段包括驗證、準備( 為靜態(tài)變量分配內存并設置默認的初始值) 和解析( 將符號引用替換為直接引用) 三個步驟。

最后 JVM 對類進行初始化,包括:1)如果類存在直接的父類并且這個類還沒有被初始化,那么就先初始化父類;2)如果類中存在初始化語句, 就依次執(zhí)行這些初始化語句。

類加載器有哪些,各有什么作用?

從JDK 1.2開始, 類加載過程采取了雙親委派機制。更好的保證了 Java 平臺的安全性,在該機制中,JVM 自帶的 Bootstrap 是根加載器, 其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能為力時才由其子類加載。

JVM 不會向 Java 程序提供對 Bootstrap 的引用。

  • 根加載器(BootStrap)一般用本地代碼實現(xiàn),負責加載 JVM 基礎核心類庫(rt.jar)。
  • 擴展加載器( ExtClassLoader)從 java.ext.dirs 系統(tǒng)屬性所指定的目錄中加載類庫,它的父加載器是 Bootstrap。
  • 系統(tǒng)加載器( AppClassLoader) 又叫應用類加載器,其父類是 Extension。它是應用最廣泛的類加載器。它從環(huán)境變量 classpath 或者系統(tǒng)屬性 java.class.path 所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。
  • 用戶自定義類加載器 ( java.lang.ClassLoader 的子類)父類是AppClassLoader。
加載一個類兩次

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

error和exception有什么區(qū)別?

error 表示恢復不是不可能但很困難的情況下的一種嚴重問題。比如說內存溢出。不可能指望程序能處理這樣的情況。exception表示一種設計或實現(xiàn)問題。也就是說,它表示如果程序運行正常,從不會發(fā)生的情況。

System.out.println(),System是什么,out是什么,println又是什么?

System是捆綁在java.lang包中的最終類。

out是打印流類的參考,它是系統(tǒng)類的靜態(tài)成員。

println是一種打印流類的方法,它捆綁在 java.io 包中打印輸出。

方法區(qū)、永久區(qū)和元數(shù)據(jù)區(qū)它們之間是什么關系?

方法區(qū)是jvm規(guī)范里要求的,永久區(qū)是Hotspot虛擬機對方法區(qū)的具體實現(xiàn),前者是規(guī)范,后者是實現(xiàn)方式。jdk1.8作了改變。

說說GC是什么,以及為什么要有GC?

GC 是垃圾收集的意思,內存處理是編程人員容易出現(xiàn)問題的地方,忘記或者錯誤的內存回收會導致程序或系統(tǒng)的不穩(wěn)定甚至崩潰。

Java 虛擬機提供的 GC 功能可以自動監(jiān)測對象是否超過作用域從而達到自動回收內存的目的,Java 語言沒有提供釋放已分配內存的顯示操作方法。Java 程序員不用擔心內存管理, 因為垃圾收集器會自動進行管理。

GC中STW是什么?

Java中Stop-The-World機制簡稱STW,是在執(zhí)行垃圾收集算法時,Java應用程序的其他所有線程(除了垃圾收集)都被掛起。

Java中一種全局暫?,F(xiàn)象,全局停頓,所有Java代碼停止,native代碼可以執(zhí)行,但不能與JVM交互。

如何識別出垃圾?

常用有兩種方式:

引用計數(shù)法,這種難以解決對象之間的循環(huán)引用的問題。

可達性分析算法,主流的JVM采用的是這種方式。

簡單聊聊垃圾回收算法

  • 標記-清除算法,如它的名字一樣,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統(tǒng)一回收掉所有被標記的對象。
  • 復制算法,它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。缺點是浪費空間,優(yōu)點是回收速度快,沒碎片。
  • 標記-壓縮算法,標記過程仍然與“標記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,完成碎片整理。
  • 分代收集算法,把Java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴ǎ矣X得它更像是一種思想,而不是算法。

你知道有哪些垃圾回收器?

  • Serial 收集器,串行收集器是最古老,最穩(wěn)定以及效率高的收集器,可能會產(chǎn)生較長的停頓,只使用一個線程去回收。
  • ParNew 收集器,ParNew 收集器其實就是 Serial 收集器的多線程版本。
  • Parallel 收集器,Parallel Scavenge 收集器類似 ParNew 收集器,Parallel 收集器更關注系統(tǒng)的吞吐量。
  • Parallel Old 收集器,Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多線程和“標記-整理”算法
  • CMS 收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。
  • G1 收集器,G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高概率滿足 GC 停頓時間要求的同時,還具備高吞吐量性能特征

基于棧和寄存器的指令集架構是什么?

前者的指令運行過程,需要借助棧這個數(shù)據(jù)結構來完成,主要的優(yōu)點就是可移植,缺點是執(zhí)行速度慢。

后者的寄存器指令由硬件直接提供,速度很快,但是缺點是和硬件強綁定。

我們主流的java虛擬機采用的都是基于棧的指令集架構。

虛擬機棧是什么?

JVM規(guī)范讓每個Java線程擁有自己的獨立的JVM棧,也就是Java方法的調用棧。

當方法調用的時候,會生成一個棧幀。方法的調用返回過程,其實就是棧幀的入棧出棧。

棧幀是保存在虛擬機棧中的,棧幀存儲了方法的局部變量表、操作數(shù)棧、動態(tài)連接和方法返回地址等信息。

線程運行過程中,只有一個棧幀是處于活躍狀態(tài),稱為“當前活躍棧幀”,當前活動棧幀始終是虛擬機棧的棧頂元素。

程序計數(shù)器為什么是私有的?

虛擬機是支持多線程并發(fā)的,程序計數(shù)器私有主要是為了線程切換后能恢復到正確的執(zhí)行位置。

在多線程的情況下,程序計數(shù)器用于記錄當前線程執(zhí)行的位置,從而當線程被切換回來的時候能夠知道該線程上次運行到哪兒了。

需要注意的是,如果執(zhí)行的是 native 修飾的本地方法,那么程序計數(shù)器記錄的是 undefined 地址,只有執(zhí)行的是 Java 代碼時程序計數(shù)器記錄的才是下一條指令的地址。

SafePoint 是什么?

比如 GC 的時候必須要等到 Java 線程都進入到 safepoint 的時候 VMThread 才能開始執(zhí)行 GC 1.循環(huán)的末尾 (防止大循環(huán)的時候一直不進入 safepoint,而其他線程在等待它進入 safepoint) 2.方法返回前 3.調用方法的 call 之后 4.拋出異常的位置

Java對象的創(chuàng)建過程清楚嗎?

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

Java 對象結構了解過嗎?

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

  1. 對象頭由兩部分組成,第一部分存儲對象自身的運行時數(shù)據(jù):哈希碼、GC 分代年齡、鎖標識狀態(tài)、線程持有的鎖、偏向線程 ID(一般占 32/64 bit)。第二部分是指針類型,指向對象的類元數(shù)據(jù)類型(即對象代表哪個類)。如果是數(shù)組對象,則對象頭中還有一部分用來記錄數(shù)組長度。
  2. 實例數(shù)據(jù)用來存儲對象真正的有效信息(包括父類繼承下來的和自己定義的)
  3. 對齊填充:JVM 要求對象起始地址必須是 8 字節(jié)的整數(shù)倍(8 字節(jié)對齊)

Java 對象的定位方式你清楚嗎?

句柄池、直接指針。

方法區(qū)和永久代有什么區(qū)別

永久代又叫 Perm 區(qū),只存在于 HotSpot JVM 中,并且只存在于 JDK 1.7 和之前的版本中,JDK 1.8 中已經(jīng)徹底移除了永久代,JDK 1.8 中引入了一個新的內存區(qū)域叫 metaspace。

  • 并不是所有的 JVM 中都有永久代,IBM 的 9,Oracle 的 JRocket 都沒有永久代。
  • 永久代是實現(xiàn)層面的東西。
  • 永久代里面存的東西基本上就是方法區(qū)規(guī)定的那些東西。
因此,我們可以說,在 JDK 1.7 中永久代是方法區(qū)的一種實現(xiàn),當然,在 HotSpot JDK 1.8 中 metaspace 可以看成是方法區(qū)的一種實現(xiàn)。

為什么要用 metaspace 替換 permspace 呢

主要有如下幾點:

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

END

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