2019年JVM面試都問了什麼?快看看這22道面試題!(附答案解析)

一. Java 類加載過程?

Java 類加載須要經歷一下 7 個過程:

1. 加載

加載是類加載的第一個過程,在這個階段,將完成一下三件事情:
• 經過一個類的全限定名獲取該類的二進制流。
• 將該二進制流中的靜態存儲結構轉化爲方法去運行時數據結構。
• 在內存中生成該類的 Class 對象,做爲該類的數據訪問入口。

2. 驗證

驗證的目的是爲了確保 Class 文件的字節流中的信息不回危害到虛擬機.在該階段主要完成如下四鍾驗證:
• 文件格式驗證:驗證字節流是否符合 Class 文件的規範,如主次版本號是否在當前虛擬機範圍內,常量池中的常量是否有不被支持的類型.
• 元數據驗證:對字節碼描述的信息進行語義分析,如這個類是否有父類,是否集成了不被繼承的類等。
• 字節碼驗證:是整個驗證過程當中最複雜的一個階段,經過驗證數據流和控制流的分析,肯定程序語義是否正確,主要針對方法體的驗證。如:方法中的類型轉換是否正確,跳轉指令是否正確等。
• 符號引用驗證:這個動做在後面的解析過程當中發生,主要是爲了確保解析動做能正確執行。

3. 準備

準備階段是爲類的靜態變量分配內存並將其初始化爲默認值,這些內存都將在方法區中進行分配。準備階段不分配類中的實例變量的內存,實例變量將會在對象實例化時隨着對象一塊兒分配在 Java 堆中。
public static int value=123;//在準備階段 value 初始值爲 0 。在初始化階段纔會變爲 123 。

4. 解析

該階段主要完成符號引用到直接引用的轉換動做。解析動做並不必定在初始化動做完成以前,也有可能在初始化以後。

5. 初始化

初始化時類加載的最後一步,前面的類加載過程,除了在加載階段用戶應用程序能夠經過自定義類加載器參與以外,其他動做徹底由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的Java 程序代碼。

6. 使用

7. 卸載

二.描述一下 JVM 加載 Class 文件的原理機制?

Java 語言是一種具備動態性的解釋型語言,類(Class)只有被加載到 JVM 後才能運行。當運行指定程序時,JVM 會將編譯生成的 .class 文件按照需求和必定的規則加載到內存中,並組織成爲一個完整的 Java 應用程序。這個加載過程是由類加載器完成,具體來講,就是由 ClassLoader 和它的子類來實現的。類加載器自己也是一個類,其實質是把類文件從硬盤讀取到內存中。

類的加載方式分爲隱式加載和顯示加載。隱式加載指的是程序在使用 new 等方式建立對象時,會隱式地調用類的加載器把對應的類加載到 JVM 中。顯示加載指的是經過直接調用 class.forName()方法來把所需的類加載到 JVM 中。

任何一個工程項目都是由許多類組成的,當程序啓動時,只把須要的類加載到 JVM 中,其餘類只有被使用到的時候纔會被加載,採用這種方法一方面能夠加快加載速度,另外一方面能夠節約程序運行時對內存的開銷。此外,在 Java 語言中,每一個類或接口都對應一個 .class 文件,這些文件能夠被當作是一個個能夠被動態加載的單元,所以當只有部分類被修改時,只須要從新編譯變化的類便可,而不須要從新編譯全部文件,所以加快了編譯速度。

在 Java 語言中,類的加載是動態的,它並不會一次性將全部類所有加載後再運行,而是保證程序運行的基礎類(例如基類)徹底加載到 JVM 中,至於其餘類,則在須要的時候才加載。

類加載的主要步驟:
• 裝載,根據查找路徑找到相應的 class 文件,而後導入。
• 連接,連接又可分爲 3 個小步:
• 檢查,檢查待加載的 class 文件的正確性。
• 準備,給類中的靜態變量分配存儲空間。
• 解析,將符號引用轉換爲直接引用(這一步可選)
• 初始化。對靜態變量和靜態代碼塊執行初始化工做。

三 Java 內存分配。

• 寄存器:咱們沒法控制。
• 靜態域:static 定義的靜態成員。
• 常量池:編譯時被肯定並保存在 .class 文件中的(final)常量值和一些文本修飾的符號引用(類和接口的全限定名,字段的名稱和描述符,方法和名稱和描述符)。
• 非 RAM 存儲:硬盤等永久存儲空間。
• 堆內存:new 建立的對象和數組,由 Java 虛擬機自動垃圾回收器管理,存取速度慢。
• 棧內存:基本類型的變量和對象的引用變量(堆內存空間的訪問地址),速度快,能夠共享,可是大小與生存期必須肯定,缺少靈活性。

Java 堆的結構是什麼樣子的?什麼是堆中的永久代(Perm Genspace)?
JVM 的堆是運行時數據區,全部類的實例和數組都是在堆上分配內存。它在 JVM 啓動的時候被建立。對象所佔的堆內存是由自動內存管理系統也就是垃圾收集器回收。
堆內存是由存活和死亡的對象組成的。存活的對象是應用能夠訪問的,不會被垃圾回收。死亡的對象是應用不可訪問尚且尚未被垃圾收集器回收掉的對象。一直到垃圾收集器把這些 對象回收掉以前,他們會一直佔據堆內存空間。

四.GC 是什麼? 爲何要有 GC?

GC 是垃圾收集的意思(GabageCollection),內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會致使程序或系統的不穩定甚至崩潰,Java 提供的 GC 功能能夠自動監測對象是否超過做用域從而達到自動回收內存的目的,Java 語言沒有提供釋放已分配內存的顯示操做方法。

五. 簡述 Java 垃圾回收機制。

在 Java 中,程序員是不須要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在 JVM 中,有一個垃圾回收線程,它是低優先級的,在正常狀況下是不會執行的,只有在虛擬機空閒或者當前堆內存不足時,纔會觸發執行,掃面那些沒有被任何引用的對象,並將它們添加到要回收的集合中,進行回收。

六. 如何判斷一個對象是否存活?(或者 GC 對象的斷定方法)

判斷一個對象是否存活有兩種方法:

1. 引用計數法

所謂引用計數法就是給每個對象設置一個引用計數器,每當有一個地方引用這個對象時,就將計數器加一,引用失效時,計數器就減一。當一個對象的引用計數器爲零時,說明此對象沒有被引用,也就是「死對象」,將會被垃圾回收。
引用計數法有一個缺陷就是沒法解決循環引用問題,也就是說當對象 A 引用對象 B,對象 B 又引用者對象 A,那麼此時 A、B 對象的引用計數器都不爲零,也就形成沒法完成垃圾回收,因此主流的虛擬機都沒有采用這種算法。

2. 可達性算法(引用鏈法)

該算法的思想是:從一個被稱爲 GC Roots 的對象開始向下搜索,若是一個對象到 GC Roots 沒有任何引用鏈相連時,則說明此對象不可用。
在 Java 中能夠做爲 GC Roots 的對象有如下幾種:
• 虛擬機棧中引用的對象
• 方法區類靜態屬性引用的對象
• 方法區常量池引用的對象
• 本地方法棧 JNI 引用的對象
雖然這些算法能夠斷定一個對象是否能被回收,可是當知足上述條件時,一個對象比不必定會被回收。當一個對象不可達 GC Root時,這個對象並不會立馬被回收,而是出於一個死緩的階段,若要被真正的回收須要經歷兩次標記。

若是對象在可達性分析中沒有與 GC Root 的引用鏈,那麼此時就會被第一次標記而且進行一次篩選,篩選的條件是是否有必要執行finalize() 方法。當對象沒有覆蓋 finalize() 方法或者已被虛擬機調用過,那麼就認爲是不必的。 若是該對象有必要執行finalize() 方法,那麼這個對象將會放在一個稱爲 F-Queue 的對隊列中,虛擬機會觸發一個 Finalize() 線程去執行,此線程是低優先級的,而且虛擬機不會承諾一直等待它運行完,這是由於若是finalize() 執行緩慢或者發生了死鎖,那麼就會形成 F-Queue 對列一直等待,形成了內存回收系統的崩潰。GC 對處於 F-Queue中的對象進行第二次被標記,這時,該對象將被移除」 即將回收」集合,等待回收。

七. 垃圾回收的優勢和原理。並考慮 2 種回收機制。

Java 語言中一個顯著的特色就是引入了垃圾回收機制,使 C++程序員最頭疼的內存管理的問題迎刃而解,它使得 Java 程序員在編寫程序的時候再也不須要考慮內存管理。因爲有個垃圾回收機制,Java 中的對象再也不有「做用域」的概念,只有對象的引用纔有"做用域"。垃圾回收能夠有效的防止內存泄露,有效的使用可使用的內存。垃圾回收器一般是做爲一個單獨的低級別的線程運行,不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清楚和回收,程序員不能實時的調用垃圾回收器對某個對象或全部對象進行垃圾回收。
回收機制有分代複製垃圾回收和標記垃圾回收,增量垃圾回收。

八. 垃圾回收器的基本原理是什麼?垃圾回收器能夠立刻回收內存嗎?有什麼辦法主動通知虛擬機進行垃圾回收?

對於 GC 來講,當程序員建立對象時,GC 就開始監控這個對象的地址、大小以及使用狀況。一般,GC 採用有向圖的方式記錄和管理堆(heap)中的全部對象。經過這種方式肯定哪些對象是」可達的」,哪些對象是」不可達的」。當 GC 肯定一些對象爲「不可達」時,GC 就有責任回收這些內存空間。能夠。程序員能夠手動執行 System.gc(),通知 GC 運行,可是 Java 語言規範並不保證 GC 必定會執行。

九. Java 中會存在內存泄漏嗎,請簡單描述。

所謂內存泄露就是指一個再也不被程序使用的對象或變量一直被佔據在內存中。Java 中有垃圾回收機制,它能夠保證一對象再也不被引用的時候,即對象變成了孤兒的時候,對象將自動被垃圾回收器從內存中清除掉。因爲 Java 使用有向圖的方式進行垃圾回收管理,能夠消除引用循環的問題,例若有兩個對象,相互引用,只要它們和根進程不可達的,那麼 GC 也是能夠回收它們的。

Java 中的內存泄露的狀況:長生命週期的對象持有短生命週期對象的引用就極可能發生內存泄露,儘管短生命週期對象已經再也不須要,可是由於長生命週期對象持有它的引用而致使不能被回收,這就是 Java 中內存泄露的發生場景,通俗地說,就是程序員可能建立了一個對象,之後一直再也不使用這個對象,這個對象卻一直被引用,即這個對象無用可是卻沒法被垃圾回收器回收的,這就是 java中可能出現內存泄露的狀況,例如,緩存系統,咱們加載了一個對象放在緩存中 (例如放在一個全局 map 對象中),而後一直再也不使用它,這個對象一直被緩存引用,但卻再也不被使用。

檢查 Java 中的內存泄露,必定要讓程序將各類分支狀況都完整執行到程序結束,而後看某個對象是否被使用過,若是沒有,則才能斷定這個對象屬於內存泄露。

若是一個外部類的實例對象的方法返回了一個內部類的實例對象,這個內部類對象被長期引用了,即便那個外部類實例對象再也不被使用,但因爲內部類持久外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會形成內存泄露。

內存泄露的另一種狀況:當一個對象被存儲進 HashSet 集合中之後,就不能修改這個對象中的那些參與計算哈希值的字段了,不然,對象修改後的哈希值與最初存儲進 HashSet 集合中時的哈希值就不一樣了,在這種狀況下,即便在 contains 方法使用該對象的當前引用做爲的參數去 HashSet 集合中檢索對象,也將返回找不到對象的結果,這也會致使沒法從 HashSet 集合中單獨刪除當前對象,形成內存泄露。

十. 深拷貝和淺拷貝。

簡單來說就是複製、克隆。
Person p=new Person(「張三」);
淺拷貝就是對對象中的數據成員進行簡單賦值,若是存在動態成員或者指針就會報錯。
深拷貝就是對對象中存在的動態成員或指針從新開闢內存空間。

十一. System.gc() 和 Runtime.gc() 會作什麼事情?

這兩個方法用來提示 JVM 要進行垃圾回收。可是,當即開始仍是延遲進行垃圾回收是取決於 JVM 的。

十二. finalize() 方法何時被調用?析構函數 (finalization) 的目的是什麼?

垃圾回收器(garbage colector)決定回收某對象時,就會運行該對象的 finalize() 方法 可是在 Java 中很不幸,若是內存老是充足的,那麼垃圾回收可能永遠不會進行,也就是說 filalize() 可能永遠不被執行,顯然期望它作收尾工做是靠不住的。 那麼finalize() 到底是作什麼的呢? 它最主要的用途是回收特殊渠道申請的內存。Java 程序有垃圾回收器,因此通常狀況下內存問題不用程序員操心。但有一種 JNI(Java Native Interface)調用non-Java 程序(C 或 C++), finalize() 的工做就是回收這部分的內存。

十三. 若是對象的引用被置爲 null,垃圾收集器是否會當即釋放對象佔用的內存?

不會,在下一個垃圾回收週期中,這個對象將是可被回收的。

十四. 什麼是分佈式垃圾回收(DGC)?它是如何工做的?

DGC 叫作分佈式垃圾回收。RMI 使用 DGC 來作自動垃圾回收。由於 RMI 包含了跨虛擬機的遠程對象的引用,垃圾回收是很困難的。DGC 使用引用計數算法來給遠程對象提供自動內存管理。

十五. 串行(serial)收集器和吞吐量(throughput)收集器的區別是什麼?

吞吐量收集器使用並行版本的新生代垃圾收集器,它用於中等規模和大規模數據的應用程序。 而串行收集器對大多數的小應用(在現代處理器上須要大概 100M 左右的內存)就足夠了。

十六. 在 Java 中,對象何時能夠被垃圾回收?

當對象對當前使用這個對象的應用程序變得不可觸及的時候,這個對象就能夠被回收了。

十七. 簡述 Java 內存分配與回收策率以及 Minor GC 和 MajorGC。

• 對象優先在堆的 Eden 區分配
• 大對象直接進入老年代
• 長期存活的對象將直接進入老年代
當 Eden 區沒有足夠的空間進行分配時,虛擬機會執行一次Minor GC。Minor GC 一般發生在新生代的 Eden 區,在這個區的對象生存期短,每每發生 Gc 的頻率較高,回收速度比較快;
Full GC/Major GC 發生在老年代,通常狀況下,觸發老年代 GC的時候不會觸發 Minor GC,可是經過配置,能夠在 Full GC 以前進行一次 Minor GC 這樣能夠加快老年代的回收速度。

十八. JVM 的永久代中會發生垃圾回收麼?

垃圾回收不會發生在永久代,若是永久代滿了或者是超過了臨界值,會觸發徹底垃圾回收(Full GC)。
注:Java 8 中已經移除了永久代,新加了一個叫作元數據區的native 內存區。

十九. Java 中垃圾收集的方法有哪些?

標記 - 清除:

這是垃圾收集算法中最基礎的,根據名字就能夠知道,它的思想就是標記哪些要被回收的對象,而後統一回收。這種方法很簡單,可是會有兩個主要問題:java

1. 效率不高,標記和清除的效率都很低;
2. 會產生大量不連續的內存碎片,致使之後程序在分配較大的對象時,因爲沒有充足的連續內存而提早觸發一次 GC 動做。

複製算法:

爲了解決效率問題,複製算法將可用內存按容量劃分爲相等的兩部分,而後每次只使用其中的一塊,當一塊內存用完時,就將還存活的對象複製到第二塊內存上,而後一次性清楚完第一塊內存,再將第二塊上的對象複製到第一塊。可是這種方式,內存的代價過高,每次基本上都要浪費通常的內存。程序員

因而將該算法進行了改進,內存區域再也不是按照 1:1 去劃分,而是將內存劃分爲 8:1:1 三部分,較大那分內存交 Eden 區,其他是兩塊較小的內存區叫 Survior 區。每次都會優先使用 Eden 區,若 Eden 區滿,就將對象複製到第二塊內存區上,而後清除 Eden區,若是此時存活的對象太多,以致於 Survivor 不夠時,會將這些對象經過分配擔保機制複製到老年代中。(java 堆又分爲新生代和老年代)

標記 - 整理:

該算法主要是爲了解決標記 - 清除,產生大量內存碎片的問題;當對象存活率較高時,也解決了複製算法的效率問題。它的不一樣之處就是在清除對象的時候現將可回收對象移動到一端,而後清除掉端邊界之外的對象,這樣就不會產生內存碎片了。面試

分代收集:

如今的虛擬機垃圾收集大多采用這種方式,它根據對象的生存週期,將堆分爲新生代和老年代。在新生代中,因爲對象生存期短,每次回收都會有大量對象死去,那麼這時就採用複製算法。算法

老年代裏的對象存活率較高,沒有額外的空間進行分配擔保。

二十. 什麼是類加載器,類加載器有哪些?

實現經過類的權限定名獲取該類的二進制字節流的代碼塊叫作類加載器。
主要有一下四種類加載器:
• 啓動類加載器(Bootstrap ClassLoader)用來加載 Java 核心類庫,沒法被 Java 程序直接引用。
• 擴展類加載器(extensions class loader):它用來加載 Java的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。
• 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。通常來講,Java應用的類都是由它來完成加載的。能夠經過ClassLoader.getSystemClassLoader() 來獲取它。
• 用戶自定義類加載器,經過繼承 java.lang.ClassLoader 類的方式實現。

二十一. 類加載器雙親委派模型機制?

當一個類收到了類加載請求時,不會本身先去加載這個類,而是將其委派給父類,由父類去加載,若是此時父類不能加載,反饋給子類,由子類去完成類的加載。

總結

全部的面試題目都不是一成不變的,特別是像一線大廠,上面的面試真題只是給你們一個借鑑做用,最主要的是給本身增長知識的儲備,有備無患。


給你們分享整理的2019年大廠JVM面試題資料pdf文檔以及學習筆記和各知識點學習路線思惟腦圖還有JVM講解視頻。歡迎你們關注個人 公種浩【程序員追風】,文章都會在裏面更新,整理的資料也會放在裏面。


最後

歡迎你們一塊兒交流,喜歡文章記得關注我點贊轉發喲,感謝支持!
相關文章
相關標籤/搜索