面試京東Java崗,三問JVM被刷,我回家後仔細總結了這份JVM面試題

###前言

JVM作爲一個重要的知識點,在面試中也是尤爲重要的,面試之前就有朋友提醒過,要多去了解學習,從當前的一個就業形勢來說,不僅對於基本操作內容要有良好的見地,面試官更多的也在考察你對他人不熟悉的技術點的認知情況。

其實在寫出本篇文章之前,我也找過很多相關的知識點內容,並進行了瞭解,奈何沒有上心,導致面試的時候紕漏百出,結果自然是遺憾退場! 所以今天在這裏給大家把這次的知識點總結上! 文中內容也多有借鑑他人經驗,中間也有我自己的一個想法在裏面進行了再加工,希望能對大家下次的面試有所幫助。

那麼,廢話不多說,直接上乾貨…

一、java內存區域

1、jvm包含了哪幾個模塊,以及各個模塊的作用?

這張圖我們要印象深刻,JVM包含兩個子系統和兩個組件,兩個子系統爲Class loader(類裝載)、Execution engine(執行引擎);兩個組件爲Runtime data area(運行時數據區)、Native Interface(本地接口)。

每個模塊的作用:

(1)首先通過編譯器把 Java 代碼轉換成字節碼,

(2)類加載器(ClassLoader)再把字節碼加載到內存中,將其放在運行時數據區(Runtime data area)的方法區內,

(3)而字節碼文件只是 JVM 的一套指令集規範,並不能直接交給底層操作系統去執行,因此需要特定的命令解析器執行引擎(Execution Engine),將字節碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程中需要調用其他語言的本地庫接口(Native Interface)來實現整個程序的功能。

2、說一下 JVM 運行時數據區

Java 虛擬機在執行 Java 程序的過程中會把它所管理的內存區域劃分爲若干個不同的數據區域。注意是在執行java程序的時候劃分的,Java 虛擬機所管理的內存被劃分爲如下幾個區域:

(1)程序計數器(Program Counter Register):當前線程所執行的字節碼的行號指示器,字節碼解析器的工作是通過改變這個計數器的值,來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能,都需要依賴這個計數器來完成;

(2)Java 虛擬機棧(Java Virtual Machine Stacks):用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息;

(3)本地方法棧(Native Method Stack):與虛擬機棧的作用是一樣的,只不過虛擬機棧是服務 Java 方法的,而本地方法棧是爲虛擬機調用 Native 方法服務的;

(4)Java 堆(Java Heap):Java 虛擬機中內存最大的一塊,是被所有線程共享的,幾乎所有的對象實例都在這裏分配內存;

(5)方法區(Methed Area):用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯後的代碼等數據。

3、說一下堆和棧的區別?

(1)物理內存

堆的物理地址分配對對象是不連續的。因此性能慢些。在GC的時候也要考慮到不連續的分配,所以有各種算法。比如,標記-消除,複製,標記-壓縮,分代(即新生代使用複製算法,老年代使用標記——壓縮)

棧使用的是數據結構中的棧,先進後出的原則,物理地址分配是連續的。所以性能快。

(2)存放的內容

堆存放的是對象的實例和數組。因此該區更關注的是數據的存儲

棧存放:局部變量,操作數棧,返回結果。該區更關注的是程序方法的執行。

(3)程序的可見度

堆對於整個應用程序都是共享、可見的。

棧只對於線程是可見的。所以也是線程私有。他的生命週期和線程相同。

4、隊列和棧是什麼?有什麼區別?

操作的名稱不同(入隊出隊、入棧出棧),操作的位置不同(隊頭),方式不同。

二、java虛擬機中的對象

1、一個java對象是如何創建的?

(1)虛擬機遇到一條new指令時,先檢查常量池是否已經加載相應的類,

(2)如果沒有,必須先執行相應的類加載。

(3)類加載通過後,接下來分配內存。若Java堆中內存是絕對規整的,使用“指針碰撞“方式分配內存;如果不是規整的,就從空閒列表中分配,叫做”空閒列表“方式。

(4)劃分內存時還需要考慮一個問題-併發,也有兩種方式: CAS同步處理,或者本地線程分配緩衝(Thread Local Allocation Buffer, TLAB)。

(5)然後內存空間初始化操作,

(6)接着是做一些必要的對象設置(元信息、哈希碼…),最後執行方法。

2、對象創建有哪幾種方式?

Header 解釋
使用new關鍵字 調用了構造函數
使用Class的newInstance方法 調用了構造函數
使用Constructor類的newInstance方法 調用了構造函數
使用clone方法 沒有調用構造函數
使用反序列化 沒有調用構造函數

3、如何爲對象分配內存?

類加載完成後,接着會在Java堆中劃分一塊內存分配給對象。內存分配根據Java堆是否規整,有兩種方式:

(1)指針碰撞:如果Java堆的內存是規整,即所有用過的內存放在一邊,而空閒的的放在另一邊。分配內存時將位於中間的指針指示器向空閒的內存移動一段與對象大小相等的距離,這樣便完成分配內存工作。

(2)空閒列表:如果Java堆的內存不是規整的,則需要由虛擬機維護一個列表來記錄那些內存是可用的,這樣在分配的時候可以從列表中查詢到足夠大的內存分配給對象,並在分配後更新列表記錄。

選擇哪種分配方式是由 Java 堆是否規整來決定的,而 Java 堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。

4、爲對象分配內存時,如何處理併發安全問題?

對象的創建在虛擬機中是一個非常頻繁的行爲,哪怕只是修改一個指針所指向的位置,在併發情況下也是不安全的,可能出現正在給對象 A 分配內存,指針還沒來得及修改,對象 B 又同時使用了原來的指針來分配內存的情況。解決這個問題有兩種方案:

5、如何定位一個堆上的對象?

Java程序需要通過 JVM 棧上的引用訪問堆中的具體對象。對象的訪問方式取決於 JVM 虛擬機的實現。目前主流的訪問方式有 句柄 和 直接指針 兩種方式。

(1)指針:如果使用直接指針訪問,引用 中存儲的直接就是對象地址,那麼Java堆對象內部的佈局中就必須考慮如何放置訪問類型數據的相關信息。

(2)句柄:句柄不直接指向對象,而是指向對象的指針,再由對象的指針指向對象的真實內存地址,句柄中包含了對象實例數據對象類型數據各自的具體地址信息。

6、Java會存在內存泄漏嗎?請簡單描述

內存泄漏是指不再被使用的對象或者變量一直被佔據在內存中。理論上來說,Java是有GC垃圾回收機制的,也就是說,不再被使用的對象,會被GC自動回收掉,自動從內存中清除。

但是,即使這樣,Java也還是存在着內存泄漏的情況,java導致內存泄露的原因很明確:長生命週期的對象持有短生命週期對象的引用就很可能發生內存泄露,儘管短生命週期對象已經不再需要,但是因爲長生命週期對象持有它的引用而導致不能被回收,這就是java中內存泄露的發生場景。

三、垃圾回收

1、簡述Java垃圾回收機制

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

回收會導致程序或系統的不穩定甚至崩潰,Java 提供的 GC 功能可以自動監測對象是否超過作用域從而達到自動

回收內存的目的,Java 語言沒有提供釋放已分配內存的顯示操作方法

2、垃圾回收器的基本原理是什麼?垃圾回收器可以馬上回收內存嗎?有什麼辦法主動通知虛擬機進行垃圾回收?

(1)對於GC來說,當程序員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。通常,GC採用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是"可達的",哪些對象是"不可達的"。當GC確定一些對象爲"不可達"時,GC就有責任回收這些內存空間。

(2)垃圾回收器可以馬上回收內存。

(3)程序員可以手動執行System.gc(),通知GC運行,但是Java語言規範並不保證GC一定會執行。

3、Java 中都有哪些引用類型?

(1)強引用:發生 gc 的時候不會被回收。

(2)軟引用:有用但不是必須的對象,在發生內存溢出之前會被回收。

(3)弱引用:有用但不是必須的對象,在下一次GC時會被回收。

(4)虛引用(幽靈引用/幻影引用):無法通過虛引用獲得對象,用 PhantomReference 實現虛引用,虛引用的用途是在 gc 時返回一個通知。

4、怎麼判斷對象是否可以被回收?

垃圾收集器在做垃圾回收的時候,首先需要判定的就是哪些內存是需要被回收的,哪些對象是「存活」的,是不可以被回收的;哪些對象已經「死掉」了,需要被回收。

一般有兩種方法來判斷:

(1)引用計數器法:爲每個對象創建一個引用計數,有對象引用時計數器 +1,引用被釋放時計數 -1,當計數器爲 0 時就可以被回收。它有一個缺點不能解決循環引用的問題;

(2)可達性分析算法:從 GC Roots 開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連時,則證明此對象是可以被回收的。

5、在Java中,對象什麼時候可以被垃圾回收?

當對象對當前使用這個對象的應用程序變得不可觸及的時候,這個對象就可以被回收了。
垃圾回收會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。

6、說一下 JVM 有哪些垃圾回收算法?

(1)標記-清除算法:標記無用對象,然後進行清除回收。缺點:效率不高,無法清除垃圾碎片。

(2)複製算法:按照容量劃分二個大小相等的內存區域,當一塊用完的時候將活着的對象複製到另一塊上,然後再把已使用的內存空間一次清理掉。缺點:內存使用率不高,只有原來的一半。

(3)標記-整理算法:標記無用對象,讓所有存活的對象都向一端移動,然後直接清除掉端邊界以外的內存。缺點:仍需要進行局部對象移動,一定程度上降低了效率。

(4)分代算法:根據對象存活週期的不同將內存劃分爲幾塊,一般是新生代和老年代,新生代基本採用複製算法,老年代採用標記整理算法。

7、說一下 JVM 有哪些垃圾回收器?

其中用於回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,還有用於回收整個Java堆的G1收集器。不同收集器之間的連線表示它們可以搭配使用。

(1)Serial收集器(複製算法): 新生代單線程收集器,標記和清理都是單線程,優點是簡單高效;

(2)ParNew收集器 (複製算法): 新生代收並行集器,實際上是Serial收集器的多線程版本,在多核CPU環境下有着比Serial更好的表現;

(3)Parallel Scavenge收集器 (複製算法): 新生代並行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用戶線程時間/(用戶線程時間+GC線程時間),高吞吐量可以高效率的利用CPU時間,儘快完成程序的運算任務,適合後臺應用等對交互相應要求不高的場景;

(4)Serial Old收集器 (標記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;

(5)Parallel Old收集器 (標記-整理算法):老年代並行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;

(6)CMS(Concurrent Mark Sweep)收集器(標記-清除算法):老年代並行收集器,以獲取最短回收停頓時間爲目標的收集器,具有高併發、低停頓的特點,追求最短GC回收停頓時間。

(7)G1(Garbage First)收集器 (標記-整理算法):Java堆並行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基於“標記-整理”算法實現,也就是說不會產生內存碎片。此外,G1收集器不同於之前的收集器的一個重要特點是:G1回收的範圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的範圍僅限於新生代或老年代。

8、介紹一下 CMS 垃圾回收器?

CMS 是英文 Concurrent Mark-Sweep 的簡稱,是以犧牲吞吐量爲代價來獲得最短回收停頓時間的垃圾回收器。對於要求服務器響應速度的應用上,這種垃圾回收器非常適合。在啓動 JVM 的參數加上“-XX:+UseConcMarkSweepGC”來指定使用 CMS 垃圾回收器。

CMS 使用的是標記-清除的算法實現的,所以在 gc 的時候回產生大量的內存碎片,當剩餘內存不能滿足程序運行要求時,系統將會出現 Concurrent Mode Failure,臨時 CMS 會採用 Serial Old 回收器進行垃圾清除,此時的性能將會被降低。

9、簡述分代垃圾回收器是怎麼工作的?

分代回收器有兩個分區:老生代和新生代,新生代默認的空間佔比總空間的 1/3,老生代的默認佔比是 2/3。

新生代使用的是複製算法,新生代裏有 3 個分區:Eden、To Survivor、From Survivor,它們的默認佔比是 8:1:1,它的執行流程如下:

(1)把 Eden + From Survivor 存活的對象放入 To Survivor 區;

(2)清空 Eden 和 From Survivor 分區;

(3)From Survivor 和 To Survivor 分區交換,From Survivor 變 To Survivor,To Survivor 變 From Survivor。

每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當年齡到達 15(默認配置是 15)時,升級爲老生代。大對象也會直接進入老生代。

老生代當空間佔用到達某個值之後就會觸發全局垃圾收回,一般使用標記整理的執行算法。以上這些循環往復就構成了整個分代垃圾回收的整體執行流程。

四、內存分配策略

1、簡述java內存分配與回收策率?

所謂自動內存管理,最終要解決的也就是內存分配和內存回收兩個問題。前面我們介紹了內存回收,這裏我們再來聊聊內存分配。

對象的內存分配通常是在 Java 堆上分配(隨着虛擬機優化技術的誕生,某些場景下也會在棧上分配,後面會詳細介紹),**對象主要分配在新生代的 Eden 區,如果啓動了本地線程緩衝,將按照線程優先在 TLAB 上分配。少數情況下也會直接在老年代上分配。**總的來說分配規則不是百分百固定的,其細節取決於哪一種垃圾收集器組合以及虛擬機相關參數有關。

2、簡述Minor GC和Major GC?

多數情況,對象都在新生代 Eden 區分配。當 Eden 區分配沒有足夠的空間進行分配時,虛擬機將會發起一次 Minor GC。如果本次 GC 後還是沒有足夠的空間,則將啓用分配擔保機制在老年代中分配內存。

這裏我們提到 Minor GC,如果你仔細觀察過 GC 日常,通常我們還能從日誌中發現 Major GC/Full GC。

(1)Minor GC 是指發生在新生代的 GC,因爲 Java 對象大多都是朝生夕死,所有 Minor GC 非常頻繁,一般回收速度也非常快;

(2)Major GC/Full GC 是指發生在老年代的 GC,出現了 Major GC 通常會伴隨至少一次 Minor GC。Major GC 的速度通常會比 Minor GC 慢 10 倍以上。

3、對象何時進入老年代

所謂大對象是指需要大量連續內存空間的對象,頻繁出現大對象是致命的,會導致在內存還有不少空間的情況下提前觸發 GC 以獲取足夠的連續空間來安置新對象。

(1)如果大對象直接在新生代分配就會導致 Eden 區和兩個 Survivor 區之間發生大量的內存複製。因此對於大對象都會直接在老年代進行分配。

(2)虛擬機給每個對象定義了一個對象年齡的計數器,如果對象在 Eden 區出生,並且能夠被 Survivor 容納,將被移動到 Survivor 空間中,這時設置對象年齡爲 1。對象在 Survivor 區中每「熬過」一次 Minor GC 年齡就加 1,當年齡達到一定程度(默認 15) 就會被晉升到老年代。

五、類加載機制

1、描述一下JVM加載Class文件的原理機制

Java中的所有類,都需要由類加載器裝載到JVM中才能運行。類加載器本身也是一個類,而它的工作就是把class文件從硬盤讀取到內存中。類裝載方式,有兩種 :

(1)隱式裝載, 程序在運行過程中當碰到通過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中,

(2)顯式裝載, 通過class.forname()等方法,顯式加載需要的類

Java類的加載是動態的,它並不會一次性將所有類全部加載後再運行,而是保證程序運行的基礎類(像是基類)完全加載到jvm中,至於其他類,則在需要的時候才加載。這當然就是爲了節省內存開銷。

2、什麼是類加載器,類加載器有哪些?

主要有一下四種類加載器:

(1)啓動類加載器(Bootstrap ClassLoader)用來加載java核心類庫,無法被java程序直接引用。

(2)擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。

(3)系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來獲取它。

(4)用戶自定義類加載器,通過繼承 java.lang.ClassLoader類的方式實現。

3、說一下類裝載的執行過程?

類裝載分爲以下 5 個步驟:

(1)加載:根據查找路徑找到相應的 class 文件然後導入;

(2)驗證:檢查加載的 class 文件的正確性;

(3)準備:給類中的靜態變量分配內存空間;

(4)解析:虛擬機將常量池中的符號引用替換成直接引用的過程。符號引用就理解爲一個標示,而在直接引用直接指向內存中的地址;

(5)初始化:對靜態變量和靜態代碼塊執行初始化工作。

4、什麼是雙親委派模型?

雙親委派模型:如果一個類加載器收到了類加載的請求,它首先不會自己去加載這個類,而是把這個請求委派給父類加載器去完成,每一層的類加載器都是如此,這樣所有的加載請求都會被傳送到頂層的啓動類加載器中,只有當父加載無法完成加載請求(它的搜索範圍中沒找到所需的類)時,子加載器纔會嘗試去加載類。

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

5、類的生命週期?

類的生命週期包括這幾個部分,加載、連接、初始化、使用和卸載,其中前三部是類的加載的過程,如下圖;

(1)加載,查找並加載類的二進制數據,在堆中也創建一個java.lang.Class類的對象

(2)連接,連接又包含三塊內容:驗證、準備、初始化。1)驗證,文件格式、元數據、字節碼、符號引用驗證;2)準備,爲類的靜態變量分配內存,並將其初始化爲默認值;3)解析,把類中的符號引用轉換爲直接引用

(3)初始化,爲類的靜態變量賦予正確的初始值

(4)使用,new出對象程序中使用

(5)卸載,執行垃圾回收

六、JVM調優

1、說一下 JVM 調優的工具?

JDK 自帶了很多監控工具,都位於 JDK 的 bin 目錄下,其中最常用的是 jconsole 和 jvisualvm 這兩款視圖監控工具。

(1)jconsole:用於對 JVM 中的內存、線程和類等進行監控;

(2)jvisualvm:JDK 自帶的全能分析工具,可以分析:內存快照、線程快照、程序死鎖、監控內存的變化、gc 變化等。

2、常用的 JVM 調優的參數都有哪些?

-Xms2g:初始化推大小爲 2g;

-Xmx2g:堆最大內存爲 2g;

-XX:NewRatio=4:設置年輕的和老年代的內存比例爲 1:4;

-XX:SurvivorRatio=8:設置新生代 Eden 和 Survivor 比例爲 8:2;

–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;

-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;

-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;

-XX:+PrintGC:開啓打印 gc 信息;

-XX:+PrintGCDetails:打印 gc 詳細信息。

OK,jvm系列的面試題,我找了找其他的,基本上都一樣,裏面我補充了一部分,也去掉了一部分。

最後,今天的分享到這裏就結束了,以上的內容大家可以多在面試之前之前去突擊一下,相信會有不錯效果出現, 如需獲取更多面試資料請在後臺私信: 面試 獲取即可!

#####全網最全的大廠面試真題都在這裏了

##感謝閱讀,關注、轉發、評論表明一下你對小編的支持,也是很不錯的!後續將分享更多幹貨給大家!>_<

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章