溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java面試題之JVM的示例分析

發(fā)布時間:2021-08-09 13:54:32 來源:億速云 閱讀:115 作者:小新 欄目:開發(fā)技術(shù)

這篇文章給大家分享的是有關(guān)Java面試題之JVM的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

    面試題1:你遇到過哪些OOM情況,什么原因造成的?怎么解決的?

    該問題主要針對你遇到的實際問題出發(fā),可以根據(jù)你實際遇到過的情況和場景,結(jié)合下面每種情況的具體原因和解決方式,整理后回答。

    Java面試題之JVM的示例分析

    Java heap space

    當堆內(nèi)存(Heap Space)沒有足夠空間存放新創(chuàng)建的對象時,就會拋出 java.lang.OutOfMemoryError:Javaheap space錯誤(根據(jù)實際生產(chǎn)經(jīng)驗,可以對程序日志中的 OutOfMemoryError 配置關(guān)鍵字告警,一經(jīng)發(fā)現(xiàn),立即處理)。

    原因分析

    Javaheap space 錯誤產(chǎn)生的常見原因可以分為以下幾類:

    • 請求創(chuàng)建一個超大對象,通常是一個大數(shù)組。

    • 超出預(yù)期的訪問量/數(shù)據(jù)量,通常是上游系統(tǒng)請求流量飆升,常見于各類促銷/秒殺活動,可以結(jié)合業(yè)務(wù)流量指標排查是否有尖狀峰值。

    • 過度使用終結(jié)器(Finalizer),該對象沒有立即被 GC。

    • 內(nèi)存泄漏(Memory Leak),大量對象引用沒有釋放,JVM 無法對其自動回收,常見于使用了 File 等資源沒有回收。

    解決方案

    針對大部分情況,通常只需要通過-Xmx 參數(shù)調(diào)高 JVM 堆內(nèi)存空間即可。如果仍然沒有解決,可以參考以下情況做進一步處理:

    • 如果是超大對象,可以檢查其合理性,比如是否一次性查詢了數(shù)據(jù)庫全部結(jié)果,而沒有做結(jié)果數(shù)限制。

    • 如果是業(yè)務(wù)峰值壓力,可以考慮添加機器資源,或者做限流降級。

    • 如果是內(nèi)存泄漏,需要找到持有的對象,修改代碼設(shè)計,比如關(guān)閉沒有釋放的連接。

    GC overhead limit exceeded

    當 Java 進程花費 98% 以上的時間執(zhí)行 GC,但只恢復(fù)了不到 2% 的內(nèi)存,且該動作連續(xù)重復(fù)了 5 次,就會拋出 java.lang.OutOfMemoryError:GC overhead limit exceeded 錯誤。簡單地說,就是應(yīng)用程序已經(jīng)基本耗盡了所有可用內(nèi)存, GC 也無法回收。

    此類問題的原因與解決方案跟 Javaheap space 非常類似,可以參考上條。

     Permgen space

    該錯誤表示永久代(Permanent Generation)已用滿,通常是因為加載的 class 數(shù)目太多或體積太大。

    原因分析

    永久代存儲對象主要包括以下幾類:

    • 加載/緩存到內(nèi)存中的 class 定義,包括類的名稱,字段,方法和字節(jié)碼;

    • 常量池;

    • 對象數(shù)組/類型數(shù)組所關(guān)聯(lián)的 class;

    • JIT 編譯器優(yōu)化后的 class 信息。

    PermGen 的使用量與加載到內(nèi)存的 class 的數(shù)量/大小正相關(guān)。

    解決方案

    根據(jù) Permgen space 報錯的時機,可以采用不同的解決方案,如下所示:

    • 程序啟動報錯,修改 -XX:MaxPermSize 啟動參數(shù),調(diào)大永久代空間。

    • 應(yīng)用重新部署時報錯,很可能是沒有應(yīng)用沒有重啟,導(dǎo)致加載了多份 class 信息,只需重啟 JVM 即可解決。

    • 運行時報錯,應(yīng)用程序可能會動態(tài)創(chuàng)建大量 class,而這些 class 的生命周期很短暫,但是 JVM 默認不會卸載 class,可以設(shè)置 -XX:+CMSClassUnloadingEnabled 和 -XX:+UseConcMarkSweepGC這兩個參數(shù)允許 JVM 卸載 class。

    如果上述方法無法解決,可以通過 jmap 命令 dump 內(nèi)存對象 jmap-dump:format=b,file=dump.hprof ,然后利用 Eclipse MAT https://www.eclipse.org/mat 功能逐一分析開銷最大的 classloader 和重復(fù) class。

    Metaspace

    JDK 1.8 使用 Metaspace 替換了永久代(Permanent Generation),該錯誤表示 Metaspace 已被用滿,通常是因為加載的 class 數(shù)目太多或體積太大。

    此類問題的原因與解決方法跟 Permgenspace 非常類似,可以參考上文。需要特別注意的是調(diào)整 Metaspace 空間大小的啟動參數(shù)為-XX:MaxMetaspaceSize。

    Unable to create new native thread

    每個 Java 線程都需要占用一定的內(nèi)存空間,當JVM 向底層操作系統(tǒng)請求創(chuàng)建一個新的 native 線程時,如果沒有足夠的資源分配就會報此類錯誤。

    原因分析

    JVM 向 OS 請求創(chuàng)建 native 線程失敗,就會拋出 Unableto createnewnativethread,常見的原因包括以下幾類:

    • 線程數(shù)超過操作系統(tǒng)最大線程數(shù) ulimit 限制;

    • 線程數(shù)超過 kernel.pid_max(只能重啟);

    • native 內(nèi)存不足;

    該問題發(fā)生的常見過程主要包括以下幾步:

    • JVM 內(nèi)部的應(yīng)用程序請求創(chuàng)建一個新的 Java 線程;

    • JVM native 方法代理了該次請求,并向操作系統(tǒng)請求創(chuàng)建一個 native 線程;

    • 操作系統(tǒng)嘗試創(chuàng)建一個新的 native 線程,并為其分配內(nèi)存;

    • 如果操作系統(tǒng)的虛擬內(nèi)存已耗盡,或是受到 32 位進程的地址空間限制,操作系統(tǒng)就會拒絕本次 native 內(nèi)存分配;

    • JVM 將拋出 java.lang.OutOfMemoryError:Unableto createnewnativethread 錯誤。

    解決方案

    • 升級配置,為機器提供更多的內(nèi)存;

    • 降低 Java Heap Space 大小;

    • 修復(fù)應(yīng)用程序的線程泄漏問題;

    • 限制線程池大?。?/p>

    • 使用 -Xss 參數(shù)減少線程棧的大?。?/p>

    • 調(diào)高 OS 層面的線程最大數(shù):執(zhí)行 ulimia-a 查看最大線程數(shù)限制,使用 ulimit-u xxx 調(diào)整最大線程數(shù)限制。

    Out of swap space?

    該錯誤表示所有可用的虛擬內(nèi)存已被耗盡。虛擬內(nèi)存(Virtual Memory)由物理內(nèi)存(Physical Memory)和交換空間(Swap Space)兩部分組成。當運行時程序請求的虛擬內(nèi)存溢出時就會報 Outof swap space 錯誤。

    原因分析

    該錯誤出現(xiàn)的常見原因包括以下幾類:

    • 地址空間不足;

    • 物理內(nèi)存已耗光;

    • 應(yīng)用程序的本地內(nèi)存泄漏(native leak),例如不斷申請本地內(nèi)存,卻不釋放。

    • 執(zhí)行 jmap-histo:live 命令,強制執(zhí)行 Full GC;如果幾次執(zhí)行后內(nèi)存明顯下降,則基本確認為 Direct ByteBuffer 問題。

    解決方案

    根據(jù)錯誤原因可以采取如下解決方案:

    • 升級地址空間為 64 bit;

    • 使用 Arthas 檢查是否為 Inflater/Deflater 解壓縮問題,如果是,則顯式調(diào)用 end 方法。

    • Direct ByteBuffer 問題可以通過啟動參數(shù) -XX:MaxDirectMemorySize 調(diào)低閾值。

    • 升級服務(wù)器配置/隔離部署,避免爭用。

    Kill process or sacrifice child

    有一種內(nèi)核作業(yè)(Kernel Job)名為 Out of Memory Killer,它會在可用內(nèi)存極低的情況下“殺死”(kill)某些進程。OOM Killer 會對所有進程進行打分,然后將評分較低的進程“殺死”,具體的評分規(guī)則可以參考 Surviving the Linux OOM Killer。

    不同于其他的 OOM 錯誤, Killprocessorsacrifice child 錯誤不是由 JVM 層面觸發(fā)的,而是由操作系統(tǒng)層面觸發(fā)的。

    原因分析

    默認情況下,Linux 內(nèi)核允許進程申請的內(nèi)存總量大于系統(tǒng)可用內(nèi)存,通過這種“錯峰復(fù)用”的方式可以更有效的利用系統(tǒng)資源。

    然而,這種方式也會無可避免地帶來一定的“超賣”風險。例如某些進程持續(xù)占用系統(tǒng)內(nèi)存,然后導(dǎo)致其他進程沒有可用內(nèi)存。此時,系統(tǒng)將自動激活 OOM Killer,尋找評分低的進程,并將其“殺死”,釋放內(nèi)存資源。

    解決方案

    • 升級服務(wù)器配置/隔離部署,避免爭用。

    • OOM Killer 調(diào)優(yōu)。

    Requested array size exceeds VM limit

    JVM 限制了數(shù)組的最大長度,該錯誤表示程序請求創(chuàng)建的數(shù)組超過最大長度限制。

    JVM 在為數(shù)組分配內(nèi)存前,會檢查要分配的數(shù)據(jù)結(jié)構(gòu)在系統(tǒng)中是否可尋址,通常為 Integer.MAX_VALUE-2。

    此類問題比較罕見,通常需要檢查代碼,確認業(yè)務(wù)是否需要創(chuàng)建如此大的數(shù)組,是否可以拆分為多個塊,分批執(zhí)行。

    Direct buffer memory

    Java 允許應(yīng)用程序通過 Direct ByteBuffer 直接訪問堆外內(nèi)存,許多高性能程序通過 Direct ByteBuffer 結(jié)合內(nèi)存映射文件(Memory Mapped File)實現(xiàn)高速 IO。

    原因分析

    Direct ByteBuffer 的默認大小為 64 MB,一旦使用超出限制,就會拋出 Directbuffer memory 錯誤。

    解決方案

    • Java 只能通過 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此,可以通過 Arthas 等在線診斷工具攔截該方法進行排查。

    • 檢查是否直接或間接使用了 NIO,如 netty,jetty 等。

    • 通過啟動參數(shù) -XX:MaxDirectMemorySize 調(diào)整 Direct ByteBuffer 的上限值。

    • 檢查 JVM 參數(shù)是否有 -XX:+DisableExplicitGC 選項,如果有就去掉,因為該參數(shù)會使 System.gc() 失效。

    • 檢查堆外內(nèi)存使用代碼,確認是否存在內(nèi)存泄漏;或者通過反射調(diào)用 sun.misc.Cleaner 的 clean() 方法來主動釋放被 Direct ByteBuffer 持有的內(nèi)存空間。

    • 內(nèi)存容量確實不足,升級配置。

    面試題2:說說JVM的內(nèi)存結(jié)構(gòu)?

    Java 虛擬機在執(zhí)行 Java 程序的過程中會把它管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域。每個區(qū)域都有各自的作用,其中有一些會隨著虛擬機啟動而創(chuàng)建,隨著虛擬機退出而銷毀。另外一些則是與線程一一對應(yīng)的,這些與線程一一對應(yīng)的數(shù)據(jù)區(qū)域會隨著線程開始和結(jié)束而創(chuàng)建和銷毀。

    • 線程私有:程序計數(shù)器、JVM棧、本地棧

    • 線程共享:堆、方法區(qū)(永久代或元空間、代碼緩存)

    Java面試題之JVM的示例分析

    • 堆內(nèi)存(Java Heap)

    對于大多數(shù)應(yīng)用來說,Java堆是Java虛擬機所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存。

    Java面試題之JVM的示例分析

    Java堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱做“GC堆”。如果從內(nèi)存回收的角度看,由于現(xiàn)在收集器基本都是采用的分代收集算法,所以Java堆中還可以細分為:新生代和老年代;再細致一點的有Eden空間、From Survivor空間、To Survivor空間,默認情況下年輕代按照8:1:1的比例來分配。

    • 方法區(qū)(Method Area)

    與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。雖然Java虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來。

    對于習慣在HotSpot虛擬機上開發(fā)和部署程序的開發(fā)者來說,很多人愿意把方法區(qū)稱為“永久代”(Permanent Generation),本質(zhì)上兩者并不等價,僅僅是因為HotSpot虛擬機的設(shè)計團隊選擇把GC分代收集擴展至方法區(qū),或者說使用永久代來實現(xiàn)方法區(qū)而已。

    • 程序計數(shù)器(Program Counter Register)

    程序計數(shù)器是一塊較小的內(nèi)存空間,它的作用可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機的概念模型里(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現(xiàn)),字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。

    此內(nèi)存區(qū)域是唯一一個在Java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。

    • JVM棧(JVM Stacks)

    與程序計數(shù)器一樣,Java虛擬機棧也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。每一個方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程。

    Java面試題之JVM的示例分析

    局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型、對象引用和returnAddress類型(指向了一條字節(jié)碼指令的地址)。

    在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:

    • 如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常;

    • 如果虛擬機棧可以動態(tài)擴展,當擴展時無法申請到足夠的內(nèi)存時會拋出OutOfMemoryError異常。

    • 本地方法棧(Native Method Stacks)

    本地方法棧與虛擬機棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機使用到的Native方法服務(wù)。

    虛擬機規(guī)范中對本地方法棧中的方法使用的語言、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有強制規(guī)定,因此具體的虛擬機可以自由實現(xiàn)它。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一。與虛擬機棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常。

    面試題3:說一下new一個對象的過程是什么樣的?

    當虛擬機遇見new關(guān)鍵字時候,實現(xiàn)判斷當前類是否已經(jīng)加載,如果類沒有加載,首先執(zhí)行類的加載機制,加載完成后再為對象分配空間、初始化等。

    Java面試題之JVM的示例分析

    首先校驗當前類是否被加載,如果沒有加載,執(zhí)行類加載機制

    • 加載:就是從字節(jié)碼加載成二進制流的過程

    • 驗證:當然加載完成之后,當然需要校驗Class文件是否符合虛擬機規(guī)范,跟我們接口請求一樣,第一件事情當然是先做個參數(shù)校驗了

    • 準備:為靜態(tài)變量、常量賦默認值

    • 解析:把常量池中符號引用(以符號描述引用的目標)替換為直接引用(指向目標的指針或者句柄等)的過程

    • 初始化:執(zhí)行static代碼塊(cinit)進行初始化,如果存在父類,先對父類進行初始化

    tips:靜態(tài)代碼塊是絕對線程安全的,只能隱式被java虛擬機在類加載過程中初始化調(diào)用!

    當類加載完成之后,緊接著就是對象分配內(nèi)存空間和初始化的過程

    • 首先為對象分配合適大小的內(nèi)存空間

    • 接著為實例變量賦默認值

    • 設(shè)置對象的頭信息,對象hash碼、GC分代年齡、元數(shù)據(jù)信息等

    • 執(zhí)行構(gòu)造函數(shù)(init)初始化 每日小結(jié)

    感謝各位的閱讀!關(guān)于“Java面試題之JVM的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI