2019年一線大廠最全JVM面試100問!你能答對多少?

前言

JVM(Java虛擬機)簡單來講就是運行Java代碼的解釋器,做爲螺絲釘程序員JVM其實瞭解下就差很少啦,不懂JVM內部細節照樣能寫出優質的代碼!可是一到造火箭、飛機的場景(面試)不懂JVM的你,會被面試官虐的體無完膚!

面對這一大波JVM面試題,你真的Hold的住嗎?
16e7d9453b5a1fda?w=659&h=504&f=png&s=456435java

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

在面試java工程師的時候,這道題常常被問到,故需特別注意。

Java中的全部類,都須要由類加載器裝載到JVM中才能運行。類加載器自己也是一個類,而它的工做就是把class文件從硬盤讀取到內存中。在寫程序的時候,咱們幾乎不須要關心類的加載,由於這些都是隱式裝載的,除非咱們有特殊的用法,像是反射,就須要顯式的加載所須要的類。

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

Java的類加載器有三個,對應Java的三種類:Bootstrap Loader // 負責加載系統類 (指的是內置類,像是String,對應於C#中的System類和C/C++標準庫中的類)  
             |
        - - ExtClassLoader // 負責加載擴展類(就是繼承類和實現類) 
                           | 
                      - - AppClassLoader // 負責加載應用類(程序員自定義的類)三個加載器各自完成本身的工做,但它們是如何協調工做呢?哪個類該由哪一個類加載器完成呢?爲了解決這個問題,Java採用了委託模型機制。

委託模型機制的工做原理很簡單:當類加載器須要加載類的時候,先請示其Parent(即上一層加載器)在其搜索路徑載入,若是找不到,纔在本身的搜索路徑搜索該類。這樣的順序其實就是加載器層次上自頂而下的搜索,由於加載器必須保證基礎類的加載。之因此是這種機制,還有一個安全上的考慮:若是某人將一個惡意的基礎類加載到jvm,委託模型機制會搜索其父類加載器,顯然是不可能找到的,天然就不會將該類加載進來。咱們能夠經過這樣的代碼來獲取類加載器:程序員

ClassLoader loader = ClassName.class.getClassLoader(); ClassLoader ParentLoader = loader.getParent();

注意一個很重要的問題,就是Java在邏輯上並不存在BootstrapKLoader的實體!由於它是用C++編寫的,因此打印其內容將會獲得null。

前面是對類加載器的簡單介紹,它的原理機制很是簡單,就是下面幾個步驟:1.裝載:查找和導入class文件;2.鏈接:(1)檢查:檢查載入的class文件數據的正確性;(2)準備:爲類的靜態變量分配存儲空間;(3)解析:將符號引用轉換成直接引用(這一步是可選的)3.初始化:初始化靜態變量,靜態代碼塊。這樣的過程在程序調用類的靜態成員的時候開始執行,因此靜態方法main()纔會成爲通常程序的入口方法。類的構造器也會引起該動做。面試

JVM的內存結構是什麼樣子的?

JVM內存結構能夠大體可劃分爲線程私有區域和共享區域,線程私有區域由虛擬機棧、本地方法棧、程序計數器組成,而共享區域由堆、元數據空間(方法區)組成。
16e7d9453b700e08?w=640&h=392&f=jpeg&s=21618
再有人問你JVM的內存結構就回想下上面的圖,可是知道JVM的內存模型的樣子仍是不行的,還要知道他們分別幹什麼的。算法

虛擬機棧/本地方法棧

當你碰到過StackOverflowException這個異常的時候,有沒有思考下爲何會出現這樣的異常呢?答案就在虛擬機棧中,JVM會爲每一個方法生成棧幀而後將棧幀壓入虛擬機棧中。舉個粟子:假設JVM參數-Xss設置爲1m,若是某個方法裏面建立一個128kb的數組,那這個方法在同一個線程中只能遞歸4次,再遞歸第五次的時候就會報StackOverflowException異常,由於虛擬機棧的大小隻有1m,每次遞歸都須要爲方法在虛擬機棧中分配128kb的空間,很顯示到第五次的時候就空間不足了。
16e7d9453cb7c1f6?w=640&h=323&f=jpeg&s=26685數組

程序計數器

程序計數器是一個記錄着當前線程所執行的字節碼的行號指示器。JVM的多線程是經過CPU時間片輪轉(即線程輪流切換並分配處理器執行時間)算法來實現的。也就是說,某個線程在執行過程當中可能會由於時間片耗盡而被掛起,而另外一個線程獲取到時間片開始執行。簡單的說程序計數器的主要功能就是記錄着當前線程所執行的字節碼的行號指示器。安全

方法區(元數據區)

方法區存儲了類的元數據信息、靜態變量、常量等數據。
16e7d9453cc195bb?w=640&h=352&f=jpeg&s=19741多線程

堆(heap)

日常你們使用new關鍵字建立的對象都會進入堆中,堆也是GC重點照顧的區域,堆會被劃分爲:新生代、老年代,而新生代還會被進一步劃分爲Eden區和Survivor區:16e7d9453b913341?w=640&h=228&f=jpeg&s=12458
新生代中的Eden區和Survivor區,是根據JVM回收算法來的,只是如今大部分都是使用的分代回收算法,因此在介紹堆的時候會直接將新生代概括爲Eden區和Survivor區。app

小結

JVM內存模型小結:jvm

  • JVM內存模型劃分爲線程私有區域和共享區域ide

  • 虛擬機棧/本地方法棧負責存放線程執行方法棧幀

  • 程序計數器用於記錄線程執行指令的位置

  • 方法區(元數據區)存儲類的元數據信息、靜態變量、常量等數據

  • 堆(heap)使用new關鍵字建立的對象都會進入堆中,堆被劃分爲新生代和老年代

何時對象能夠被收回?

JVM判斷對象回收有兩種方式:引用記數、GC Roots,引用記數比較簡單,JVM爲每一個對象維護一個引用計數,假設A對象引用計數爲零說明沒有任務對象引用A對象,那A對象就能夠被回收了,可是引用計數有個缺點就是沒法解決循環引用的問題。GC Roots經過一系列的名爲GC Roots的對象做爲起始點,從這些節點開始向下搜索,搜索過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證實對象是不可用的。在Java中,能夠做爲GC Roots的對象包括下面幾種:

  • 虛擬機棧中引用的對象;

  • 方法區中類靜態屬性引用的對象;

  • 方法區中的常量引用的對象;

  • 本地方法棧中JNI(即通常說的Native方法)的引用的對象;

小結

總的來講就是當一個對象經過GC Roots搜索不到時,說明對象能夠被回收了,但何時回收還要看GC的心情!

常見的垃圾回收器算法有哪些,各有什麼優劣?

標記清除

這種算法分兩步:標記、清除兩個階段, 標記階段是從根集合(GC Root)開始掃描,每到達一個對象就會標記該對象爲存活狀態,清除階段在掃描完成以後將沒有標記的對象給清除掉。
用一張圖說明:
16e7d9453b8b59c7?w=640&h=421&f=jpeg&s=28606
這個算法有個缺陷就是會產生內存碎片,如上圖B被清除掉後會留下一塊內存區域,若是後面須要分配大的對象就會致使沒有連續的內存可供使用。

標記整理

標記整理就沒有內存碎片的問題了,也是從根集合(GC Root)開始掃描進行標記而後清除無用的對象,清除完成後它會整理內存。
16e7d9453dbab8af?w=640&h=558&f=jpeg&s=37461
這樣內存就是連續的了,可是產生的另一個問題是:每次都得移動對象,所以成本很高。

複製算法

複製算法會將JVM推分紅二等分,若是堆設置的是1g,那使用複製算法的時候堆就會有被劃分爲兩塊區域各512m。給對象分配內存的時候老是使用其中的一塊來分配,分配滿了之後,GC就會進行標記,而後將存活的對象移動到另一塊空白的區域,而後清除掉全部沒有存活的對象,這樣重複的處理,始終就會有一塊空白的區域沒有被合理的利用到。

16e7d9453ddf4cb9?w=640&h=1095&f=jpeg&s=44733

兩塊區域交替使用,最大問題就是會致使空間的浪費,如今堆內存的使用率只有50%。

小結

JVM回收算法小結:

  • 標記清除速度快,可是會產生內存碎片;

  • 標記整理解決了標記清除內存碎片的問題,可是每次都得移動對象,所以成本很高;

  • 複製算法沒有內存碎片也不須要移動對象,可是致使空間的浪費;

何時對象會進入老年代?

新建立出來的對象一開始都會停留在新生代中,但隨着JVM的運行,有些存活的長的對象會慢慢的移動到老年代中。

根據對象年齡

JVM會給對象增長一個年齡(age)的計數器,對象每「熬過」一次GC,年齡就要+1,待對象到達設置的閾值(默認爲15歲)就會被移移動到老年代,可經過-XX:MaxTenuringThreshold調整這個閾值。

16e7d9453def28f7?w=640&h=939&f=jpeg&s=52553

一次Minor GC後,對象年齡就會+1,達到閾值的對象就移動到老年代,其餘存活下來的對象會繼續保留在新生代中。

動態年齡判斷

根據對象年齡有另一個策略也會讓對象進入老年代,不用等待15次GC以後進入老年代,他的大體規則就是,假如當前放對象的Survivor,一批對象的總大小大於這塊Survivor內存的50%,那麼大於這批對象年齡的對象,就能夠直接進入老年代了。

16e7d94540fb8f42?w=640&h=541&f=jpeg&s=28186

如圖上的A、B、D、E這四個對象,假如Survivor 2是100m,若是A + B + D的內存大小超過50m,如今D的年齡是10,那E都會被移動到老年代。實際上這個計算邏輯是這樣的:年齡1 + 年齡2 + 年齡n的多個對象總和超過Survivor區的50%,那就會把年齡n以上的對象都放入老年代。

大對象直接進入老年代

若是設置了-XX:PretenureSizeThreshold這個參數,那麼若是你要建立的對象大於這個參數的值,好比分配一個超大的字節數組,此時就直接把這個大對象放入到老年代,不會通過新生代。這麼作就能夠避免大對象在新生代,多次躲過GC,還得把他們來複制來複制去的,最後才進入老年代,這麼大的對象來回複製,是很耗費時間的。

什麼是空間分配擔保策略?

JVM在發生Minor GC以前,虛擬機會檢查老年代最大可用的連續空間是否大於新生代全部對象的總空間,若是大於,則這次Minor GC是安全的若是小於,則虛擬機會查看HandlePromotionFailure設置項的值是否容許擔保失敗。若是HandlePromotionFailure=true,那麼會繼續檢查老年代最大可用連續空間是否大於歷次晉升到老年代的對象的平均大小,若是大於則嘗試進行一次Minor GC,但此次Minor GC依然是有風險的;若是小於或者HandlePromotionFailure=false,則改成進行一次Full GC。

16e7d9453fbea42e?w=640&h=546&f=jpeg&s=36921

如何優化減小Full GC?

將前面的一些問題總結下來,而後應用到線上,那JVM應該如何優化減小Full GC呢?以標準的4核8G機器爲例說明,首先系統預留4G,其餘4G按以下規則分配 :

  • 堆內存:3g

  • 新生代:1.5g

  • 新生代Eden區:1228m

  • 新生代Survivor區:153m

  • 方法區:256m

  • 虛擬機棧:1m/thread

設置參數以下:

-Xms3072m -Xmx3072m -Xmn1536m -Xss=1m -XX:PermSize=256m -XX:MaxPermSize=256m -XX:HandlePromotionFailure -XX:SurvivorRatio=8

16e7d9454104f384?w=640&h=361&f=jpeg&s=20868

估算系統每秒佔用內存數量

在優化JVM以前,要先估算要系統每秒佔用的內存數量,若有個日活百萬的商場系統,每日下單量在20w左右,按照一天8個小時算,那訂單服務的每秒大概會有500個請求,而後粗略的估算下每一個請求佔用多少內存,計算出每秒要花費多少內存。假設是每秒500個請求,每一個請求須要分配100k的空間,那1秒須要分配大約50m的內存。

計算下多長時間觸發一次Minor GC

按照以前的估算1秒須要分配大約50m的內存的話,Eden區的空間是1228m那平均每25秒就要執行一次Minor GC。

檢查下Survivor區是否足夠

按照上面的模型,每25秒就要執行一次Minor GC,GC執行期間並不能回收掉全部的新生代中的對象,那每秒50m那每次GC執行期間還會剩下大約100m沒法回收的對象會進入Survivor區,可是別忘記JVM有動態年齡判斷機制,這樣設置下來Survivor的空間明顯小了一點,因此將新生代設置2048m,才能避免觸發動態年齡判斷:

-Xms3072m -Xmx3072m -Xmn2048m ...

大對象直接進入老年代

大對象通常是長期存活和使用的對象,通常來講設置1M的對象直接進入老年代,這樣避免大對象一直處於新生代中來回複製,因此加上PretenureSizeThreshold=1m參數。

... -XX:PretenureSizeThreshold=1m ...

合理設置對象年齡閾值

Minor GC後默認躲過15次垃圾回收後自動升入老年代,按照咱們的評估25秒觸發一次Minor GC,若是按照MaxTenuringThreshold參數的默認值,躲過15次GC後,應該是6分鐘以後的事了,結合當前業務場景這裏能夠下降一點,讓那些本應該進入老年代的對象,儘快的進入老年代,避免複製成本和浪費新生代空間,從而致使新生代Survivor空間不足,引起Full GC。

... -XX:MaxTenuringThreshold=6 ...

16e7d9455dafd807?w=657&h=350&f=png&s=179874

內存模型以及分區,須要詳細到每一個區放什麼?

JVM 分爲堆區和棧區,還有方法區,初始化的對象放在堆裏面,引用放在棧裏面,class 類信息常量池(static 常量和 static 變量)等放在方法區new:方法區:主要是存儲類信息,常量池(static 常量和 static 變量),編譯後的代碼(字節碼)等數堆:初始化的對象,成員變量 (那種非 static 的變量),全部的對象實例和數組都要在堆上分配棧:棧的結構是棧幀組成的,調用一個方法就壓入一幀,幀上面存儲局部變量表,操做數棧,方法出口等信息,局部變量表存放的是 8 大基礎類型加上一個應用類型,因此仍是一個指向地址的指針本地方法棧:主要爲 Native 方法服務程序計數器:記錄當前線程執行的行號

總結

全部的面試題目都不是一成不變的,特別是像一線大廠,上面的面試真題只是給你們一個借鑑做用,最主要的是給本身增長知識的儲備,有備無患。
給你們分享整理的2019年大廠JVM面試題資料(20多頁pdf文檔)以及多家公司java面試題資料100多頁pdf文檔和各知識點學習路線思惟腦圖(xmind)還有JVM講解視頻。歡迎你們關注個人公種浩【程序員追風】,文章都會在裏面更新,整理的資料也會放在裏面。但願這些資料對你們有所幫助!

16e7d94560649957?w=640&h=592&f=jpeg&s=48061
16e7d9456184a1ec?w=640&h=448&f=jpeg&s=45980

最後

歡迎你們一塊兒交流,喜歡文章記得關注我點個贊喲,感謝支持!

相關文章
相關標籤/搜索