面試系列重啟:JVM?篇
時間:2021-10-20 16:36:30
手機看文章
掃描二維碼
隨時隨地手機看文章
[導讀]今天跟大家聊聊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)建過程清楚嗎?
- JVM 遇到一條新建對象的指令時首先去檢查這個指令的參數(shù)是否能在常量池中定義到一個類的符號引用。然后加載這個類(類加載過程在后邊講)
- 為對象分配內存。一種辦法“指針碰撞”、一種辦法“空閑列表”,最終常用的辦法“本地線程緩沖分配(TLAB)”
- 將除對象頭外的對象內存空間初始化為 0
- 對對象頭進行必要設置
Java 對象結構了解過嗎?
Java 對象由三個部分組成:對象頭、實例數(shù)據(jù)、對齊填充。- 對象頭由兩部分組成,第一部分存儲對象自身的運行時數(shù)據(jù):哈希碼、GC 分代年齡、鎖標識狀態(tài)、線程持有的鎖、偏向線程 ID(一般占 32/64 bit)。第二部分是指針類型,指向對象的類元數(shù)據(jù)類型(即對象代表哪個類)。如果是數(shù)組對象,則對象頭中還有一部分用來記錄數(shù)組長度。
- 實例數(shù)據(jù)用來存儲對象真正的有效信息(包括父類繼承下來的和自己定義的)
- 對齊填充: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ī)定的那些東西。
為什么要用 metaspace 替換 permspace 呢
主要有如下幾點:- 字符串存在永久代中,容易出現(xiàn)性能問題和內存溢出。
- 類及方法的信息等比較難確定其大小,因此對于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導致老年代溢出。
- 永久代會為 GC 帶來不必要的復雜度,并且回收效率偏低。
- 移除永久代是為融合 HotSpot JVM 與 JRockit VM 而做出的努力,因為 JRockit 沒有永久代,不需要配置永久代。