【面試】JVM


關於內存、JVM、類加載機制

about conception:

 

JVM內存模型:

 

從大的方面講,JVM的內存模型分為兩大塊:

永久區內存(Permanent space)和堆內存(heap space)。

棧內存(stack space)一般不歸在JVM內存模型中,因為棧內存屬於線程級別。

每個線程都有個獨立的棧內存空間。

Permanent space是存放加載的Class類級對象 如class本身,method,field等等。

heap space主要存放對象實例和數組。

heap space由new Generation和old Generation組成,old Generation存放生命周期長久的實例對象,而新的對象實例一般存放在New Generation。

New Generation還可以再分為Eden。Survivor區,新的對象實例總是首先放在Eden區,Survivor區作為Eden區和old區的緩沖,可以向old區轉移活動的對象實例。

下圖是JVM在內存空間(堆空間)中申請新對象過程的活動圖:

沒錯,我們常見的OOM(out of memory)內存溢出異常,就是堆內存空間不足以存放新對象實例時導致。

永久區內存溢出相對少見,一般是由於需要加載海量的class數據,超過了堆內存的容量導致。通常出現在web應用剛剛啟動時,因此web應用推薦使用預加載機制,方便在部署時就發現並解決該問題。

棧內存也會溢出,但是更加少見。

堆和棧:

java棧是與每一個線程關聯的,JVM在創建每一個線程的時候,會分配一定的棧空間給線程。它主要用來存儲線程執行過程中的局部變量,方法的返回值,以及方法調用上下文。棧空間隨着線程的終止而釋放。StackOverflowError:如果在線程執行的過程中,棧空間不夠用,那么JVM就會拋出此異常,這種情況一般是死遞歸造成的。

Java堆是由所有的線程共享的一塊內存區域,堆用來保存各種java對象,比如數組,線程對象等。

堆和棧分離的好處:面向對象的設計,當然除了面向對象的設計帶來的維護性,復用性和擴展性方面的好處外,我們看看面向對象如何巧妙的利用了堆棧分離。如果從java內存模型的角度去理解面向對象的設計,我們就會發現對象它完美的表示了堆和棧,對象的數據放在堆中,而我們編寫的方法一般是運行在棧中的,因此面向對象的設計是一種非常完美的設計方式,它完美的統一了數據的存儲和運行。

堆內存優化:

調整JVM啟動參數-Xms -Xmx -XX:newSize -XX:MaxNewSize,如調整初始堆內存和最大堆內存 -Xms256M,-Xmx512M,或者調整初始New Generation的初始內存和最大內存 -XX:newSize=128M -XX:MaxNewSize=128M。

永久區內存優化:

調整PermSize參數 如-XX:PermSize=512M -XX:MaxPermSize=512M。

棧內存優化:

調整每個線程的棧內存容量,如 -Xss2028K。

最終,一個內存中JVM所占的內存=堆內存+永久區內存+所有線程所占的棧內存總和。

 

 

一個bean被new出來后,在內存空間的走向?

Student s = new Student();

1)加載Student類文件到棧內存,開辟空間;

2)在棧內存為s開辟空間;

3)在堆內存為Student對象開辟空間;

4)給Student的成員變量分配默認值;

5)如果成員變量有給定值則用給定值覆蓋默認值;

6)通過構造方法給成員變量賦值;

7)把Student對象在堆內存的地址值賦給s變量。

成員變量的初始化在構造函數之前。

 

類加載器:

java類裝載方式:

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

2)顯示裝載:通過class.forName()等方式,顯示加載需要的類。

 

類加載的動態體現:

一個應用程序是由n多個類組成,java程序啟動時,並不是一次性把所有的類全部加載后再運行,它總是先把保證程序運行的基礎類一次性加載到jvm中,其他類等到jvm用到的時候再加載,這樣的好處是節省了內存的開銷,因為java最早就是為嵌入式系統開發的,內存寶貴,這是一種可以理解的機制,而用到時再加載也是java動態性的一種體現。

 

java類裝載機制:

1)Bootstrap loader:Bootstrap加載器是用C++寫的,它是在虛擬機啟動后初始化的,主要負責加載%JAVA_HOME%/jre/lib,-Xbootclasspath參數指定的路徑以及%JAVA_HOME%/jre/classess中的類;

2)ExtClassLoader:BootStrap loader加載ExtClassLoader,並且將ExtClassLoader的父加載器設置為Bootstrap loader。ExtClassLoader是用java寫的,主要加載%JAVA_HOME%/jre/lib/ext路徑下所有的classes目錄以及java.ext.dirs系統變量指定的路徑中類庫。

3)AppClassLoader:BootStrap Loader加載完ExtClassLoader后,就會加載AppClassLoader,並且將AppClassLoader的父加載器指定為ExtClassLoader。AppClassLoader也是用java寫的。classLoader中有個getSystemClassLoader方法,此方法返回的正是AppClassLoader,AppClassLoader主要負責加載classpath所指定的位置的類或者是jar文檔,是java程序默認的類加載器。

關系如圖;

 

為什么要有三個類加載器,一方面是分工,各自負責各自的區域,另一方面是為了實現委托模型。

 

類加載器之間是如何協調工作的?

java采用了委托模型機制,這個機制簡單來說,就是“類裝載器有載入類的需求時,會先請示其Parent使用其搜索路徑幫忙載入,如果Parent找不到,那么才由自己按照自己的搜索路徑搜索類”。

 

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

【加載、驗證、准備、解析、初始化、使用、卸載 7個階段】

1)裝載:查找和導入Class文件;

2)鏈接:其中解析步驟是可以選擇的;

  • 檢查:檢查載入的class文件數據的正確性
  • 准備:給類的靜態變量分配存儲空間
  • 解析:將符號引用轉成直接引用

3)初始化:對靜態變量,靜態代碼塊執行初始化工作。

java裝載類使用“全盤負責委托機制”,“全盤負責”是指當一個ClassLoader裝載一個類時,除非顯示的使用另外一個ClassLoader,該類所依賴及引用的類也由這個ClassLoader載入;“委托機制”是指先委托父類裝載器尋找目標類,只有在找不到的情況下才從自己的類路徑中查找並裝載目標類。這一點是從安全方面考慮的,試想一個人如果寫了一個惡意的基礎類(java.lang.String)並加裝到JVM將會引起嚴重的后果,但有了全盤負責制,java.lang.String永遠是由根裝載器來裝載,避免了以上情況的發生。除了JVM默認的三個ClassLoader外,第三方可以編寫自己的類加載器,以實現一些特殊的需求。類文件被裝載解析后,在JVM中有一個對應的java.lang.Class對象,提供了類結構信息的描述。數組、枚舉,及基本數據類型,甚至void都擁有對應的Class對象。Class類沒有public的構造方法,Class對象是在裝載類時由JVM調用類裝載器中的defineClass()方法自動構造的。

 

為什么要使用這種雙親委托模式呢?

主要有兩方面的原因,第一是避免重復加載,當父加載器已經加載了該類的時候,就沒必要子加載器再來加載一次;

另一方面就是考慮到安全因素,我們試想一下,如果不使用這種委托機制,我們可以隨時使用自定義的String動態替代java核心api中定義的類型,這樣會存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因為String已經在啟動時被加載,所以用戶自定義類是無法加載一個自定義的ClassLoader。

 

定義自己的ClassLoader?

java中提供的默認類加載器,只能加載指定目錄下的jar和class,如果我們想加載其他位置的類或者jar時,比如,我要加載網絡上的一個class文件,通過動態加載到內存之后,要調用這個類中的方法實現我的業務邏輯。在這種情況下,默認的類加載器已經不能滿足我們的需求了,需要定義自己的ClassLoader。

定義自己的類加載器分為兩步:

1)繼承java.lang.ClassLoader;

2)重寫父類的findClass方法。

 

如何讓棧溢出,如何讓方法區溢出?

棧溢出:死循環;

方法區溢出:借助CGLib使方法區出現內存溢出異常。方法區用於存放Class的相關信息,對於這些區域測試的基本思路:運行時產生大量的類去填滿方法區,直到溢出。

參考:

https://www.cnblogs.com/kivi/p/3197825.html

https://blog.csdn.net/wang740209668/article/details/65474752

https://www.cnblogs.com/doit8791/p/5820037.html

 

寫出幾個JVM優化配置參數?

-Xms:初始堆大小。只要啟動,就占用的堆大小。

-Xmx:最大堆大小。java.lang.OutOfMemoryError:Java heap這個錯誤可以通過配置-Xms和-Xmx參數來設置。

-Xss:棧大小分配。棧是每個線程私有的區域,通常只有幾百K大小,決定了函數調用的深度,而局部變量、參數都分配到棧上。當出現大量局部變量,遞歸時,會發生棧空間OOM(java.lang.StackOverflowError)之類的錯誤。

-XX:NewSize=n :設置新生代大小的絕對值。

-XX:NewRatio=n :設置年輕代和年老代的比值。比如設置為3,則新生代:老年代=1:3,新生代占總heap的1/4。

-XX:MaxPermSize=n :設置持久代大小。java.lang.OutOfMemoryError:PermGenspace這個OOM錯誤需要合理調大PermSize和MaxPermSize大小。

-XX:SurvivorRatio=n :年輕代中Eden區與兩個Survivor區的比值。注意,Survivor區有form和to兩個。比如設置為8時,那么eden:form:to=8:1:1。

-XX:HeapDumpOnOutOfMemoryError:發生OOM時轉儲堆到文件,這是一個非常好的診斷方法。

-XX:HeapDumpPath:導出堆的轉儲文件路徑。

-XX:OnOutOfMemoryError:OOM時,執行一個腳本,比如發送郵件報警,重啟程序。后面跟着一個腳本的路徑。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM