首頁 > 軟體

大三Java後端暑期實習面經總結——JVM篇

2021-05-28 10:30:42

博主現在大三在讀,從三月開始找暑期實習,暑假準備去tx實習啦!總結下了很多面試真題,希望能幫助正在找工作的大家!相關參考都會標註原文連結,尊重原創!

目錄

1. jvm體系結構2. 類載入器3. 類載入過程4. 什麼是雙親委派機制5. 雙親委派機制怎麼破壞6. jvm如何確定要回收的物件1. 引用計數法2. 可達性分析法(根回溯法)7. jvm確定要回收物件後何時回收8. jvm如何回收1. 標記-清除演算法2. 複製演算法3. 標記-壓縮演算法4. 分代收集演算法9. System.gc()10. jvm效能調優11. jvm調優參數

參考:

https://zhuanlan.zhihu.com/p/64147696https://www.bilibili.com/video/BV1Eb4y1R7zd?p=2&t=4948https://blog.csdn.net/qq_43529421/article/details/108401933

1. jvm體系結構

類裝載子系統運行時資料區位元組碼執行引擎本地方法介面

首先通過類載入器(ClassLoader)會把 Java 程式碼轉換成位元組碼,運行時資料區(Runtime Data Area)再把位元組碼載入到記憶體中,而位元組碼檔案只是 JVM 的一套指令集規範,並不能直接交個底層作業系統去執行,因此需要特定的命令解析器執行引擎(Execution Engine),將位元組碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程中需要呼叫其他語言的本地庫介面(Native Interface)來實現整個程式的功能。

2. 類載入器

其中ExtClassloader還是系統類載入器、執行緒上下文載入器

系統類載入器:由於應用程式類載入器是ClassLoader類中的getSystem-ClassLoader()方法的返回值,所以有些場合中也稱它為「系統類載入器」。它負責載入使用者類路徑(ClassPath)上所有的類庫,開發者同樣可以直接在程式碼中使用這個類載入器。執行緒上下文載入器:貫穿於以上三個載入器,每個載入器都可以對其訪問

如果想要自定義載入器,需要繼承 java.lang.ClassLoader類,且為了滿足雙親委派機制,需要指定父載入器為拓展類載入器

3. 類載入過程

Class Loader 類載入器

載入:載入.Class位元組碼檔案載入到記憶體中,創建代表這個類的java.lang.Class物件連結:對類進行連結,將類的二進位制程式碼合併到Java運行時環境JRE中初始化:然後交給JVM對類進行初始化

詳細過程圖示:

4. 什麼是雙親委派機制

當一個.class檔案要被載入時,不考慮我們自定義類載入器類,首先會在AppClassLoader中檢查是否載入過,如果有那就無需再載入;如果沒有會交到父載入器,然後呼叫父載入器的loadClass方法。父載入器同樣也會先檢查自己是否已經載入過,如果沒有再往上,直到到達BootstrapClassLoader之前,都是在檢查是否載入過,並不會選擇自己去載入。到了根載入器時,才會開始檢查是否能夠載入當前類,能載入就結束,使用當前的載入器;否則就通知子載入器進行載入;子載入器重複該步驟。如果到最底層還不能載入,就拋出異常ClassNotFoundException

總結:所有的載入請求都會傳送到根載入器去載入,只有當父載入器無法載入時,子類載入器才會去載入

作用

避免類的重複載入保證Java核心類庫的安全

5. 雙親委派機制怎麼破壞

如何打破雙親委派機制?

【JVM筆記】如何打破雙親委派機制

破壞雙親委派模型 - 簡書 (jianshu.com)

1 第一次破壞

由於雙親委派模型是在JDK1.2之後才被引入的,而類載入器和抽象類java.lang.ClassLoader則在JDK1.0時代就已經存在,面對已經存在的使用者自定義類載入器的實現程式碼,Java設計者引入雙親委派模型時不得不做出一些妥協。在此之前,使用者去繼承java.lang.ClassLoader的唯一目的就是為了重寫loadClass()方法,因為虛擬機器在進行類載入的時候會呼叫載入器的私有方法loadClassInternal(),而這個方法唯一邏輯就是去呼叫自己的loadClass()。

2 第二次破壞

雙親委派模型的第二次「被破壞」是由這個模型自身的缺陷所導致的,雙親委派很好地解決了各個類載入器的基礎類的同一問題(越基礎的類由越上層的載入器進行載入),基礎類之所以稱為「基礎」,是因為它們總是作為被使用者程式碼呼叫的API,但世事往往沒有絕對的完美。

一個典型的例子就是JNDI服務,JNDI現在已經是Java的標準服務

JNDI(Java Naming and Directory Interface,Java命名和目錄介面)是SUN公司提供的一種標準的Java命名系統介面,JNDI提供統一的客戶端API,通過不同的訪問提供者介面JNDI服務供應介面(SPI)的實現,由管理者將JNDI API對映為特定的命名服務和目錄系統,使得Java應用程式可以和這些命名服務和目錄服務之間進行互動。

它的程式碼由啟動類載入器去載入(在JDK1.3時放進去的rt.jar),但JNDI的目的就是對資源進行集中管理和查詢,它需要呼叫由獨立廠商實現並部署在應用程式的ClassPath下的JNDI介面提供者的程式碼,但啟動類載入器不可能「認識」這些程式碼。

為了解決這個問題,Java設計團隊只好引入了一個不太優雅的設計**:執行緒上下文類載入器**Thread Context ClassLoader。這個類載入器可以通過java.lang.Thread類的setContextClassLoader()方法進行設定,如果創建執行緒時還未設定,他將會從父執行緒中繼承一個,如果在應用程式的全局範圍內都沒有設定過的話,那這個類載入器預設就是應用程式類載入器。 有了執行緒上下文載入器,JNDI服務就可以使用去載入所需要的SPI程式碼,也就是父類載入器請求子類載入器去完成類載入的動作,這種行為實際上就是打通了雙親委派模型層次結構來逆向使用類載入器,實際上已經違背了雙親委派模型的一般性原則,但這也是無可奈何的事情。Java中所有涉及SPI的載入動作基本上都採用這種方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

3 第三次破壞

雙親委派模型的第三次「被破壞」是由於使用者對程式動態性的追求導致的,這裡所說的「動態性」指的是當前一些非常「熱門」的名詞:程式碼熱替換、模組熱部署等,簡答的說就是機器不用重啟,只要部署上就能用。 OSGi實現模組化熱部署的關鍵則是它自定義的類載入器機制的實現。每一個程式模組(Bundle)都有一個自己的類載入器,當需要更換一個Bundle時,就把Bundle連同類載入器一起換掉以實現程式碼的熱替換。在OSGi幻境下,類載入器不再是雙親委派模型中的樹狀結構,而是進一步發展為更加複雜的網狀結構,當受到類載入請求時,OSGi將按照下面的順序進行類搜尋: 1)將java.*開頭的類委派給父類載入器載入。 2)否則,將委派列表名單內的類委派給父類載入器載入。 3)否則,將Import列表中的類委派給Export這個類的Bundle的類載入器載入。 4)否則,查詢當前Bundle的ClassPath,使用自己的類載入器載入。 5)否則,查詢類是否在自己的Fragment Bundle中,如果在,則委派給Fragment Bundle的類載入器載入。 6)否則,查詢Dynamic Import列表的Bundle,委派給對應Bundle的類載入器載入。 7)否則,類載入器失敗。

6. jvm如何確定要回收的物件

1. 引用計數法

每個物件都有一個引用計數的屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收

該演算法當前的jvm中並沒有採用,因為它並不能解決物件之間迴圈引用的問題

假設有A和B兩個物件之間互相引用,則兩個物件的引用計數都不為0,都不能回收publicclassMain{ publicstaticvoidmain(String[] args){ MyObjectA=newMyObject();MyObjectB=newMyObject();A.object =B;B.object =A;A=null;B=null;System.gc();//不能將A、B物件進行回收}}

2. 可達性分析法(根回溯法)

由於引用計數法的缺點引入了可達性分析法,通過判斷物件的引用鏈是否可達來決定物件是否可以被回收。

從GC Roots開始向下搜尋,搜尋過的路徑稱為引用鏈。當一個物件到 GC Roots沒有任何引用鏈相連時,則證明此物件是不可達的,則JVM判斷為可回收物件

GC Roots的物件有:

虛擬機器棧(棧幀中的本地變量表)中引用的物件方法區中類靜態屬性引用的物件方法區中常量引用的物件本地方法棧JNI(NATIVE本地方法)引用的物件

可達性演算法中的不可達物件並不是立即死亡的,物件擁有一次自我拯救的機會。物件被系統宣告死亡至少要經歷兩次標記過程:第一次是經過可達性分析發現沒有與GC Roots相連線的引用鏈,第二次是在由虛擬機器自動建立的Finalize佇列中判斷是否需要執行finalize()方法

當物件變為不可達時,GC會判斷物件是否覆蓋了finalize()方法,如果沒有覆蓋,則直接將其回收。否則,如果物件沒有執行過finalize方法,將其放入F-Queue佇列中,由一低優先順序執行緒執行該佇列中物件的finalize方法。執行finalize方法完畢後,GC會再次判斷物件是否可達,如果不可達,則進行回收;否則物件復活

注意:每個物件只能觸發一次finalize方法,此外,finalize方法的運行代價很大,不確定性也很大,無法保證各個物件的呼叫順序,所以一般都不會進行覆蓋。

7. jvm確定要回收物件後何時回收

會在cpu空閒的時候自動進行回收在堆記憶體儲存滿了之後主動呼叫System.gc()後嘗試進行回收

8. jvm如何回收

GC垃圾回收,主要在年輕代和老年代。首先,物件出生在伊甸園區,該區只能存放一定數量物件,存滿時就會觸發一次輕GC, 將伊甸園區清空;清理後,有的物件可能還存在引用,存活下來進入倖存區;以此往復,如果物件在年輕代經歷了15次(可設定-XX:MaxTenuringThreshold=n參數)GC還沒有死亡,則會進入老年代,老年代滿時會觸發一次重GC,年輕代+老年代的物件都會進行清理,如果新生代+老年代都滿了,則OOMMinor GC:輕GC,伊甸園區滿時觸發;從年輕代回收記憶體Full GC:重FC,老年代滿時觸發;清理整個堆空間,包含年輕代和老年代

如何回收說的也就是垃圾收集的演算法,演算法有四個:

1. 標記-清除演算法

這是最基礎的一種演算法,分為兩個步驟,首先就是標記,也就是標記處所有需要回收的物件,標記完成後就進行統一的回收掉哪些帶有標記的物件。

這種演算法優點是簡單,缺點是效率問題,還有一個最大的缺點是空間問題,標記清除之後會產生大量不連續的記憶體碎片,當程式在以後的運行過程中需要分配較大物件時無法找到足夠的連續記憶體而造成記憶體空間浪費

2. 複製演算法

複製將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的物件複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。這樣使得每次都是對其中的一塊進行記憶體回收,記憶體分配時也就不用考慮記憶體碎片等複雜情況。只是這種演算法的代價是將記憶體縮小為原來的一半

3. 標記-壓縮演算法

標記整理演算法與標記清除演算法很相似,但最顯著的區別是:標記清除演算法僅對不存活的物件進行處理,剩餘存活物件不做任何處理,造成記憶體碎片;而標記整理演算法不僅對不存活物件進行處理清除,還對剩餘的存活物件進行整理,重新整理,因此其不會產生記憶體碎片

4. 分代收集演算法

分代收集演算法是一種比較智慧的演算法,也是現在jvm使用最多的一種演算法,他本身其實不是一個新的演算法,而是他會在具體的場景自動選擇以上三種演算法進行垃圾物件回收

9. System.gc()

System.gc()用於呼叫垃圾收集器,System.gc()函數的作用只是提醒或告訴虛擬機器,希望進行一次垃圾回收,至於什麼時候進行回收還是取決於虛擬機器,而且也不能保證一定進行回收 實際上呼叫了Runtime.getRuntime().gc()方法,Runtime類中有些常用方法:

publicclassTest{ publicstaticvoidmain(String[] args){ //返回jvm試圖使用的最大記憶體long max =Runtime.getRuntime().maxMemory();//返回jvm的初始化記憶體總量long total =Runtime.getRuntime().totalMemory();//預設情況下:分配的總記憶體為電腦記憶體的1/4,初始化記憶體為電腦記憶體的1/64System.out.println("max="+ max /(double)1024/1024/1024+"G");System.out.println("total="+ total /(double)1024/1024/1024+"G");}}

其中gc()是一個本地方法,呼叫了底層C/C++的庫

10. jvm效能調優

參考:JVM調優工具詳解

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

jconsole:用於對 JVM 中的記憶體、執行緒和類等進行監控;jvisualvm:JDK 自帶的全能分析工具,可以分析:記憶體快照、執行緒快照、程式死鎖、監控記憶體的變化、gc 變化等。

1 設定jvm調優參數

-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

-Xmx用來設定 jvm試圖使用的最大記憶體,預設為1/4-Xms用來設定 jvm初始化記憶體,預設為1/64-XX:+HeapDumpOnOutOfMemoryError表示當JVM發生OOM時,自動生成DUMP檔案

2 通過java自帶的jvm調優診斷工具jvisualvm

JDK 自帶的全能分析工具,可以分析:記憶體快照、執行緒快照、程式死鎖、監控記憶體的變化、gc 變化等。

D:JAVA_Environmentjdkjdk1.8bin>jvisualvmD:JAVA_Environmentjdkjdk1.8bin>The launcher has determined that the parent process has a console and will reuse it for its own console output.Closing the console will result in termination of the running program.Use '--console suppress' to suppress console output.Use '--console new' to create a separate console window.

3 利用記憶體快照工具JProfiler分析dump檔案

Dump檔案是程序的記憶體映象,可以把程式的執行狀態通過偵錯程式儲存到dump檔案中利用記憶體快照工具JProfiler分析Dump記憶體檔案,快速定位記憶體洩漏;獲得堆中的檔案;獲得大的物件…# 配置當JVM發生OOM時,自動生成DUMP檔案(.hprof)-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

4 Arthas工具

Arthas 使用者文件 — Arthas 3.5.0 文件 (gitee.io)

11. 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 詳細資訊。

,https://blog.csdn.net/qq_45173404/article/details/117278963


IT145.com E-mail:sddin#qq.com