2019年JVM面試都問了什么?(附答案解析)
一. Java 類加載過程?
Java 類加載需要經(jīng)歷一下 7 個過程:
1. 加載
加載是類加載的第一個過程,在這個階段,將完成一下三件事情:
• 通過一個類的全限定名獲取該類的二進制流。
• 將該二進制流中的靜態(tài)存儲結構轉化為方法去運行時數(shù)據(jù)結構。
• 在內存中生成該類的 Class 對象,作為該類的數(shù)據(jù)訪問入口。
2. 驗證
驗證的目的是為了確保 Class 文件的字節(jié)流中的信息不回危害到虛擬機.在該階段主要完成以下四鐘驗證:
• 文件格式驗證:驗證字節(jié)流是否符合 Class 文件的規(guī)范,如主次版本號是否在當前虛擬機范圍內,常量池中的常量是否有不被支持的類型.
• 元數(shù)據(jù)驗證:對字節(jié)碼描述的信息進行語義分析,如這個類是否有父類,是否集成了不被繼承的類等。
• 字節(jié)碼驗證:是整個驗證過程中最復雜的一個階段,通過驗證數(shù)據(jù)流和控制流的分析,確定程序語義是否正確,主要針對方法體的驗證。如:方法中的類型轉換是否正確,跳轉指令是否正確等。
• 符號引用驗證:這個動作在后面的解析過程中發(fā)生,主要是為了確保解析動作能正確執(zhí)行。
3. 準備
準備階段是為類的靜態(tài)變量分配內存并將其初始化為默認值,這些內存都將在方法區(qū)中進行分配。準備階段不分配類中的實例變量的內存,實例變量將會在對象實例化時隨著對象一起分配在 Java 堆中。
public static int value=123;//在準備階段 value 初始值為 0 。在初始化階段才會變?yōu)?123
4. 解析
該階段主要完成符號引用到直接引用的轉換動作。解析動作并不一定在初始化動作完成之前,也有可能在初始化之后。
5. 初始化
初始化時類加載的最后一步,前面的類加載過程,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執(zhí)行類中定義的Java 程序代碼。
6. 使用
7. 卸載
二.描述一下 JVM 加載 Class 文件的原理機制?
Java 語言是一種具有動態(tài)性的解釋型語言,類(Class)只有被加載到 JVM 后才能運行。當運行指定程序時,JVM 會將編譯生成的 .class 文件按照需求和一定的規(guī)則加載到內存中,并組織成為一個完整的 Java 應用程序。這個加載過程是由類加載器完成,具體來說,就是由 ClassLoader 和它的子類來實現(xiàn)的。類加載器本身也是一個類,其實質是把類文件從硬盤讀取到內存中。
類的加載方式分為隱式加載和顯示加載。隱式加載指的是程序在使用 new 等方式創(chuàng)建對象時,會隱式地調用類的加載器把對應的類加載到 JVM 中。顯示加載指的是通過直接調用 class.forName()方法來把所需的類加載到 JVM 中。
任何一個工程項目都是由許多類組成的,當程序啟動時,只把需要的類加載到 JVM 中,其他類只有被使用到的時候才會被加載,采用這種方法一方面可以加快加載速度,另一方面可以節(jié)約程序運行時對內存的開銷。此外,在 Java 語言中,每個類或接口都對應一個 .class 文件,這些文件可以被看成是一個個可以被動態(tài)加載的單元,因此當只有部分類被修改時,只需要重新編譯變化的類即可,而不需要重新編譯所有文件,因此加快了編譯速度。
在 Java 語言中,類的加載是動態(tài)的,它并不會一次性將所有類全部加載后再運行,而是保證程序運行的基礎類(例如基類)完全加載到 JVM 中,至于其他類,則在需要的時候才加載。
類加載的主要步驟:
• 裝載,根據(jù)查找路徑找到相應的 class 文件,然后導入。
• 鏈接,鏈接又可分為 3 個小步:
• 檢查,檢查待加載的 class 文件的正確性。
• 準備,給類中的靜態(tài)變量分配存儲空間。
• 解析,將符號引用轉換為直接引用(這一步可選)
• 初始化。對靜態(tài)變量和靜態(tài)代碼塊執(zhí)行初始化工作。
三 Java 內存分配。
• 寄存器:我們無法控制。
• 靜態(tài)域:static 定義的靜態(tài)成員。
• 常量池:編譯時被確定并保存在 .class 文件中的(final)常量值和一些文本修飾的符號引用(類和接口的全限定名,字段的名稱和描述符,方法和名稱和描述符)。
• 非 RAM 存儲:硬盤等永久存儲空間。
• 堆內存:new 創(chuàng)建的對象和數(shù)組,由 Java 虛擬機自動垃圾回收器管理,存取速度慢。
• 棧內存:基本類型的變量和對象的引用變量(堆內存空間的訪問地址),速度快,可以共享,但是大小與生存期必須確定,缺乏靈活性。
Java 堆的結構是什么樣子的?什么是堆中的永久代(Perm Genspace)?
JVM 的堆是運行時數(shù)據(jù)區(qū),所有類的實例和數(shù)組都是在堆上分配內存。它在 JVM 啟動的時候被創(chuàng)建。對象所占的堆內存是由自動內存管理系統(tǒng)也就是垃圾收集器回收。
堆內存是由存活和死亡的對象組成的。存活的對象是應用可以訪問的,不會被垃圾回收。死亡的對象是應用不可訪問尚且還沒有被垃圾收集器回收掉的對象。一直到垃圾收集器把這些 對象回收掉之前,他們會一直占據(jù)堆內存空間。
四.GC 是什么? 為什么要有 GC?
GC 是垃圾收集的意思(GabageCollection),內存處理是編程人員容易出現(xiàn)問題的地方,忘記或者錯誤的內存回收會導致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java 提供的 GC 功能可以自動監(jiān)測對象是否超過作用域從而達到自動回收內存的目的,Java 語言沒有提供釋放已分配內存的顯示操作方法。
五. 簡述 Java 垃圾回收機制。
在 Java 中,程序員是不需要顯示的去釋放一個對象的內存的,而是由虛擬機自行執(zhí)行。在 JVM 中,有一個垃圾回收線程,它是低優(yōu)先級的,在正常情況下是不會執(zhí)行的,只有在虛擬機空閑或者當前堆內存不足時,才會觸發(fā)執(zhí)行,掃面那些沒有被任何引用的對象,并將它們添加到要回收的集合中,進行回收。
六. 如何判斷一個對象是否存活?(或者 GC 對象的判定方法)
判斷一個對象是否存活有兩種方法:
1. 引用計數(shù)法
所謂引用計數(shù)法就是給每一個對象設置一個引用計數(shù)器,每當有一個地方引用這個對象時,就將計數(shù)器加一,引用失效時,計數(shù)器就減一。當一個對象的引用計數(shù)器為零時,說明此對象沒有被引用,也就是“死對象”,將會被垃圾回收。
引用計數(shù)法有一個缺陷就是無法解決循環(huán)引用問題,也就是說當對象 A 引用對象 B,對象 B 又引用者對象 A,那么此時 A、B 對象的引用計數(shù)器都不為零,也就造成無法完成垃圾回收,所以主流的虛擬機都沒有采用這種算法。
2. 可達性算法(引用鏈法)
該算法的思想是:從一個被稱為 GC Roots 的對象開始向下搜索,如果一個對象到 GC Roots 沒有任何引用鏈相連時,則說明此對象不可用。
在 Java 中可以作為 GC Roots 的對象有以下幾種:
• 虛擬機棧中引用的對象
• 方法區(qū)類靜態(tài)屬性引用的對象
• 方法區(qū)常量池引用的對象
• 本地方法棧 JNI 引用的對象
雖然這些算法可以判定一個對象是否能被回收,但是當滿足上述條件時,一個對象比不一定會被回收。當一個對象不可達 GC Root時,這個對象并不會立馬被回收,而是出于一個死緩的階段,若要被真正的回收需要經(jīng)歷兩次標記
如果對象在可達性分析中沒有與 GC Root 的引用鏈,那么此時就會被第一次標記并且進行一次篩選,篩選的條件是是否有必要執(zhí)行finalize() 方法。當對象沒有覆蓋 finalize() 方法或者已被虛擬機調用過,那么就認為是沒必要的。 如果該對象有必要執(zhí)行finalize() 方法,那么這個對象將會放在一個稱為 F-Queue 的對隊列中,虛擬機會觸發(fā)一個 Finalize() 線程去執(zhí)行,此線程是低優(yōu)先級的,并且虛擬機不會承諾一直等待它運行完,這是因為如果finalize() 執(zhí)行緩慢或者發(fā)生了死鎖,那么就會造成 F-Queue 對列一直等待,造成了內存回收系統(tǒng)的崩潰。GC 對處于 F-Queue中的對象進行第二次被標記,這時,該對象將被移除” 即將回收”集合,等待回收。
七. 垃圾回收的優(yōu)點和原理。并考慮 2 種回收機制。
Java 語言中一個顯著的特點就是引入了垃圾回收機制,使 C++程序員最頭疼的內存管理的問題迎刃而解,它使得 Java 程序員在編寫程序的時候不再需要考慮內存管理。由于有個垃圾回收機制,Java 中的對象不再有“作用域”的概念,只有對象的引用才有"作用域"。垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作為一個單獨的低級別的線程運行,不可預知的情況下對內存堆中已經(jīng)死亡的或者長時間沒有使用的對象進行清楚和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收。
回收機制有分代復制垃圾回收和標記垃圾回收,增量垃圾回收。
八. 垃圾回收器的基本原理是什么?垃圾回收器可以馬上回收內存嗎?有什么辦法主動通知虛擬機進行垃圾回收?
對于 GC 來說,當程序員創(chuàng)建對象時,GC 就開始監(jiān)控這個對象的地址、大小以及使用情況。通常,GC 采用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是”可達的”,哪些對象是”不可達的”。當 GC 確定一些對象為“不可達”時,GC 就有責任回收這些內存空間。可以。程序員可以手動執(zhí)行 System.gc(),通知 GC 運行,但是 Java 語言規(guī)范并不保證 GC 一定會執(zhí)行。
九. Java 中會存在內存泄漏嗎,請簡單描述。
所謂內存泄露就是指一個不再被程序使用的對象或變量一直被占據(jù)在內存中。Java 中有垃圾回收機制,它可以保證一對象不再被引用的時候,即對象變成了孤兒的時候,對象將自動被垃圾回收器從內存中清除掉。由于 Java 使用有向圖的方式進行垃圾回收管理,可以消除引用循環(huán)的問題,例如有兩個對象,相互引用,只要它們和根進程不可達的,那么 GC 也是可以回收它們的。
Java 中的內存泄露的情況:長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內存泄露,盡管短生命周期對象已經(jīng)不再需要,但是因為長生命周期對象持有它的引用而導致不能被回收,這就是 Java 中內存泄露的發(fā)生場景,通俗地說,就是程序員可能創(chuàng)建了一個對象,以后一直不再使用這個對象,這個對象卻一直被引用,即這個對象無用但是卻無法被垃圾回收器回收的,這就是 java中可能出現(xiàn)內存泄露的情況,例如,緩存系統(tǒng),我們加載了一個對象放在緩存中 (例如放在一個全局 map 對象中),然后一直不再使用它,這個對象一直被緩存引用,但卻不再被使用。
檢查 Java 中的內存泄露,一定要讓程序將各種分支情況都完整執(zhí)行到程序結束,然后看某個對象是否被使用過,如果沒有,則才能判定這個對象屬于內存泄露。
如果一個外部類的實例對象的方法返回了一個內部類的實例對象,這個內部類對象被長期引用了,即使那個外部類實例對象不再被使用,但由于內部類持久外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會造成內存泄露。
內存泄露的另外一種情況:當一個對象被存儲進 HashSet 集合中以后,就不能修改這個對象中的那些參與計算哈希值的字段了,否則,對象修改后的哈希值與最初存儲進 HashSet 集合中時的哈希值就不同了,在這種情況下,即使在 contains 方法使用該對象的當前引用作為的參數(shù)去 HashSet 集合中檢索對象,也將返回找不到對象的結果,這也會導致無法從 HashSet 集合中單獨刪除當前對象,造成內存泄露。
十. 深拷貝和淺拷貝。
簡單來講就是復制、克隆。
Person p=new Person(“張三”);
淺拷貝就是對對象中的數(shù)據(jù)成員進行簡單賦值,如果存在動態(tài)成員或者指針就會報錯。
深拷貝就是對對象中存在的動態(tài)成員或指針重新開辟內存空間。
十一. System.gc() 和 Runtime.gc() 會做什么事情?
這兩個方法用來提示 JVM 要進行垃圾回收。但是,立即開始還是延遲進行垃圾回收是取決于 JVM 的。
十二. finalize() 方法什么時候被調用?析構函數(shù) (finalization) 的目的是什么?
垃圾回收器(garbage colector)決定回收某對象時,就會運行該對象的 finalize() 方法 但是在 Java 中很不幸,如果內存總是充足的,那么垃圾回收可能永遠不會進行,也就是說 filalize() 可能永遠不被執(zhí)行,顯然指望它做收尾工作是靠不住的。 那么finalize() 究竟是做什么的呢? 它最主要的用途是回收特殊渠道申請的內存。Java 程序有垃圾回收器,所以一般情況下內存問題不用程序員操心。但有一種 JNI(Java Native Interface)調用non-Java 程序(C 或 C++), finalize() 的工作就是回收這部分的內存。
十三. 如果對象的引用被置為 null,垃圾收集器是否會立即釋放對象占用的內存?
不會,在下一個垃圾回收周期中,這個對象將是可被回收的。
十四. 什么是分布式垃圾回收(DGC)?它是如何工作的?
DGC 叫做分布式垃圾回收。RMI 使用 DGC 來做自動垃圾回收。因為 RMI 包含了跨虛擬機的遠程對象的引用,垃圾回收是很困難的。DGC 使用引用計數(shù)算法來給遠程對象提供自動內存管理。
十五. 串行(serial)收集器和吞吐量(throughput)收集器的區(qū)別是什么?
吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等規(guī)模和大規(guī)模數(shù)據(jù)的應用程序。 而串行收集器對大多數(shù)的小應用(在現(xiàn)代處理器上需要大概 100M 左右的內存)就足夠了。
十六. 在 Java 中,對象什么時候可以被垃圾回收?
當對象對當前使用這個對象的應用程序變得不可觸及的時候,這個對象就可以被回收了。
十七. 簡述 Java 內存分配與回收策率以及 Minor GC 和 MajorGC。
• 對象優(yōu)先在堆的 Eden 區(qū)分配
• 大對象直接進入老年代
• 長期存活的對象將直接進入老年代
當 Eden 區(qū)沒有足夠的空間進行分配時,虛擬機會執(zhí)行一次Minor GC。Minor GC 通常發(fā)生在新生代的 Eden 區(qū),在這個區(qū)的對象生存期短,往往發(fā)生 Gc 的頻率較高,回收速度比較快;
Full GC/Major GC 發(fā)生在老年代,一般情況下,觸發(fā)老年代 GC的時候不會觸發(fā) Minor GC,但是通過配置,可以在 Full GC 之前進行一次 Minor GC 這樣可以加快老年代的回收速度。
十八. JVM 的永久代中會發(fā)生垃圾回收么?
垃圾回收不會發(fā)生在永久代,如果永久代滿了或者是超過了臨界值,會觸發(fā)完全垃圾回收(Full GC)。
注:Java 8 中已經(jīng)移除了永久代,新加了一個叫做元數(shù)據(jù)區(qū)的native 內存區(qū)。
十九. Java 中垃圾收集的方法有哪些?
標記 - 清除:
這是垃圾收集算法中最基礎的,根據(jù)名字就可以知道,它的思想就是標記哪些要被回收的對象,然后統(tǒng)一回收。這種方法很簡單,但是會有兩個主要問題:
1. 效率不高,標記和清除的效率都很低;
2. 會產(chǎn)生大量不連續(xù)的內存碎片,導致以后程序在分配較大的對象時,由于沒有充足的連續(xù)內存而提前觸發(fā)一次 GC 動作。
復制算法:
為了解決效率問題,復制算法將可用內存按容量劃分為相等的兩部分,然后每次只使用其中的一塊,當一塊內存用完時,就將還存活的對象復制到第二塊內存上,然后一次性清楚完第一塊內存,再將第二塊上的對象復制到第一塊。但是這種方式,內存的代價太高,每次基本上都要浪費一般的內存。
于是將該算法進行了改進,內存區(qū)域不再是按照 1:1 去劃分,而是將內存劃分為 8:1:1 三部分,較大那份內存交 Eden 區(qū),其余是兩塊較小的內存區(qū)叫 Survior 區(qū)。每次都會優(yōu)先使用 Eden 區(qū),若 Eden 區(qū)滿,就將對象復制到第二塊內存區(qū)上,然后清除 Eden區(qū),如果此時存活的對象太多,以至于 Survivor 不夠時,會將這些對象通過分配擔保機制復制到老年代中。(java 堆又分為新生代和老年代)
標記 - 整理:
該算法主要是為了解決標記 - 清除,產(chǎn)生大量內存碎片的問題;當對象存活率較高時,也解決了復制算法的效率問題。它的不同之處就是在清除對象的時候現(xiàn)將可回收對象移動到一端,然后清除掉端邊界以外的對象,這樣就不會產(chǎn)生內存碎片了。
分代收集:
現(xiàn)在的虛擬機垃圾收集大多采用這種方式,它根據(jù)對象的生存周期,將堆分為新生代和老年代。在新生代中,由于對象生存期短,每次回收都會有大量對象死去,那么這時就采用復制算法。
老年代里的對象存活率較高,沒有額外的空間進行分配擔保。
二十. 什么是類加載器,類加載器有哪些?
實現(xiàn)通過類的權限定名獲取該類的二進制字節(jié)流的代碼塊叫做類加載器。
主要有一下四種類加載器:
• 啟動類加載器(Bootstrap ClassLoader)用來加載 Java 核心類庫,無法被 Java 程序直接引用。
• 擴展類加載器(extensions class loader):它用來加載 Java的擴展庫。Java 虛擬機的實現(xiàn)會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。
• 系統(tǒng)類加載器(system class loader):它根據(jù) Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java應用的類都是由它來完成加載的??梢酝ㄟ^ClassLoader.getSystemClassLoader() 來獲取它。
• 用戶自定義類加載器,通過繼承 java.lang.ClassLoader 類的方式實現(xiàn)。
二十一. 類加載器雙親委派模型機制?
當一個類收到了類加載請求時,不會自己先去加載這個類,而是將其委派給父類,由父類去加載,如果此時父類不能加載,反饋給子類,由子類去完成類的加載。
總結
所有的面試題目都不是一成不變的,特別是像一線大廠,上面的面試真題只是給大家一個借鑒作用,最主要的是給自己增加知識的儲備,有備無患。
相關文章
這篇文章主要介紹了10道JVM常見面試題解析(附答案),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學2020-09-04
這篇文章主要介紹了JVM相關面試題及答案(小結),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2020-09-022020面試阿里字節(jié)跳動90%被問到的JVM面試題附答案(史上最全)
這篇文章主要介紹了2020面試阿里字節(jié)跳動90%被問到的JVM面試題附答案,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2020-06-15- 這篇文章主要介紹了2020年JVM高頻率面試題整理,真是小編下了血本給大家整理出來的,值得大家收藏,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-09
這篇文章主要介紹了JVM面試題小結(2020最新版),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2020-02-21Java研發(fā)面試99題(含答案):JVM+Spring+MySQL+線程池+鎖
這篇文章主要介紹了Java研發(fā)面試99題,主要包括了JVM,Spring,MySQL,線程池,鎖等,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-07-16
JVM(Java 虛擬機)算是面試必問的問題的了,而但凡問 JVM 一定會問的第一個問題就是:講一講 JVM 的組成?那本文就注重講一下 JVM 的組成,感興趣的可以了解一下2019-04-10
這篇文章主要介紹了30道有趣的JVM面試題(小結),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2020-11-26



