JVM面試題(01)簡述JVM內存模型?

想要學習Java虛擬機,首先要了解內存模型!


內存劃分

Java虛擬機按照運行時內存使用區域劃分如圖:




一、程序計數器(Program Counter Register)

程序計數器就是記錄當前線程執行程序的位置,改變計數器的值來確定執行的下一條指令,比如循環、分支、方法跳轉、異常處理,線程恢復都是依賴程序計數器來完成。

Java虛擬機多線程是通過線程輪流切換並分配處理器執行時間的方式實現的。爲了線程切換能恢復到正確的位置,每條線程都需要一個獨立的程序計數器,所以它是線程私有的。

如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Native方法,這個計數器值則爲空(Undefined)。此內存區域是唯一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域。



二、Java虛擬機棧(VM Stack)

Java虛擬機棧是線程私有,生命週期與線程相同。創建線程的時候就會創建一個java虛擬機棧。

虛擬機執行java程序的時候,每個方法都會創建一個棧幀,棧幀存放在java虛擬機棧中,通過壓棧出棧的方式進行方法調用。

棧幀又分爲一下幾個區域:局部變量表、操作數棧、動態連接、方法出口等。

平時我們所說的變量存在棧中,這句話說的不太嚴謹,應該說局部變量存放在java虛擬機棧的局部變量表中。

Java的8中基本類型的局部變量的值存放在虛擬機棧的局部變量表中,如果是引用型的變量,則只存儲對象的引用地址。

注意:

當用戶請求web服務器,每個請求開啓一個線程負責用戶的響應計算(每個線程分配一個虛擬機棧空間),如果併發量大時,可能會導致內存溢出(OutOfMemoneyError),可以適當的把每個虛擬機棧的大小適當調小一點,減少內存的使用量來提高系統的併發量。

當棧空間調小以後,又會引發方法調用深度的的問題。因爲,每個方法都會生成一個棧幀,如果方法調用深度很深就意味着,棧裏面存放大量的棧幀,可能導致棧內存溢出(StackOverFlowError)。




三、本地方法棧(Native Method Stack)

本地方法棧 爲虛擬機使用到本地方法服務(native)。本地方法棧爲線程私有,功能和虛擬機棧非常類似。線程在調用本地方法時,來存儲本地方法的局部變量表,本地方法的操作數棧等等信息。

本地方法:是非java語言實現的方法,例如,java調用C語言,來操作某些硬件信息。



四、堆(Heap):                   

堆是被所有線程共享的區域,實在虛擬機啓動時創建的。堆裏面存放的都是對象的實例(new 出來的對象都存在堆中)。

我們平常所說的垃圾回收,主要回收的就是堆區。爲了提升垃圾回收的性能,又把堆分成兩塊區新生代(young)年老代(old),更細一點劃分新生代又可劃分爲Eden區和2個Survivor區(From Survivor和To Survivor)。

如下圖結構:

Eden:新創建的對象存放在Eden區

From Survivor和To Survivor:保存新生代gc後還存活的對象。(使用複製算法,導致有一個Survivor空間浪費)Hotspot虛擬機新生代Eden和Survivor的大小比值爲4:1,因爲有兩個Survivor,所以Eden:From Survivor:To Survivor比值爲8:1:1。

老年代:對象存活時間比較長(經過多次新生代的垃圾收集,默認是15次)的對象則進入老年的。當堆中分配的對象實例過多,且大部分對象都在使用,就會報內存溢出異常(OutOfMemoneyError)。




五、方法區

方法區是被所有線程共享區域,用於存放已被虛擬機加載的類信息,常量,靜態變量等數據。被Java虛擬機描述爲堆的一個邏輯部分。習慣是也叫它永久代(permanment generation)

永久代也會垃圾回收,主要針對常量池回收,類型卸載(比如反射生成大量的臨時使用的Class等信息)。

常量池用於存放編譯期生成的各種字節碼和符號引用,常量池具有一定的動態性,裏面可以存放編譯期生成的常量;運行期間的常量也可以添加進入常量池中,比如string的intern()方法。

當方法區滿時,無法在分配空間,就會拋出內存溢出的異常(OutOfMemoneyError)。

Java8中已經沒有方法區了,取而代之的是元空間(Metaspace)。




六:直接內存

直接內存(Direct Memory)並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,但是這部分內存也被頻繁地使用,而且也可能導致OutOfMemoryError異常出現。

JDK1.4加的NIO中,ByteBuffer有個方法是allocateDirect(int capacity) ,這是一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可以使用Native函數庫直接分配堆外內存,然後通過一個存儲在Java堆裏面的DirectByteBuffer對象作爲這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因爲避免了在Java堆和Native堆中來回複製數據。

JVM 內存模型 就是這麼簡單,如果看完還不太理解的,就多看幾遍,把 jvm 內存模型背下來,記到腦子裏。



我們的交流基地,“JAVA互聯網技術交流:789650498”歡迎小夥伴們一起來交流:

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