深入jvm內存結構

運行時數據區域

 程序計數器

  • 較小的內存空間,當前線程執行的字節碼的行號指示器;各線程之間獨立存儲,互不影響
  • 如果線程正在執行的是一個Java方法,則指明當前線程執行的代字節碼行數
  • 如果正在執行的是Natvie方法,這個計數器值則爲空(Undefined)
  • 此內存區域是唯一一個不會出現OutOfMemoryError情況的區域。

虛擬機棧

  • 線程請求的棧深度大於虛擬機所允許的深度:StackOverflowError
  • JVM動態擴展時無法申請到足夠的內存時:OutOfMemoryError

 

每個線程私有的,線程在運行時,在執行每個方法的時候都會打包成一個棧幀,存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息,然後放入棧。每個時刻正在執行的當前方法就是虛擬機棧頂的棧楨。方法的執行就對應着棧幀在虛擬機棧中入棧和出棧的過程。

棧的大小缺省爲1M,可用參數 –Xss調整大小,例如-Xss256k

在編譯程序代碼的時候,棧幀中需要多大的局部變量表,多深的操作數棧都已經完全確定了,並且寫入到方法表的Code屬性之中,因此一個棧幀需要分配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體的虛擬機實現。

局部變量表

顧名思義就是局部變量的表,用於存放我們的局部變量的。首先它是一個32位的長度,主要存放我們的Java的八大基礎數據類型,一般32位就可以存放下,如果是64位的就使用高低位佔用兩個也可以存放下,如果是局部的一些對象,比如我們的Object對象,我們只需要存放它的一個引用地址即可。(基本數據類型、對象引用、returnAddress類型)

操作數據棧

存放我們方法執行的操作數的,它就是一個棧,先進後出的棧結構,操作數棧,就是用來操作的,操作的的元素可以是任意的java數據類型,所以我們知道一個方法剛剛開始的時候,這個方法的操作數棧就是空的,操作數棧運行方法是會一直運行入棧/出棧的操作

動態連接

Java語言特性多態(需要類加載、運行時才能確定具體的方法,後續有詳細的講解)

返回地址

正常返回:(調用程序計數器中的地址作爲返回)

恢復上層方法的局部變量表和操作數棧、

把返回值(如果有的話)壓入調用者棧幀的操作數棧中、

調整PC計數器的值以指向方法調用指令後面的一條指令、

異常的話:(通過異常處理器表<非棧幀中的>來確定)

本地方法棧

 各虛擬機自由實現,本地方法棧native方法調用 JNI到了底層的C/C++(c/c++可以觸發彙編語言,然後驅動硬件)

方法區/永久代/元空間

用於存儲已經被虛擬機加載的類信息,常量("zdy","123"等),靜態變量(static變量)等數據,可用以下參數調整:

jdk1.7及以前:-XX:PermSize;-XX:MaxPermSize;

jdk1.8以後:-XX:MetaspaceSize; -XX:MaxMetaspaceSize

jdk1.8以後大小就只受本機總內存的限制

如:-XX:MaxMetaspaceSize=3M

類信息:

類的完整有效名、返回值類型、修飾符(public,private...)、變量名、方法名、方法代碼、這個類型直接父類的完整有效名(除非這個類型是interface或是 java.lang.Object,兩種情況下都沒有父類)、類的直接接口的一個有序列表

堆 

幾乎所有對象都分配在這裏,也是垃圾回收發生的主要區域 後續在細講,可用以下參數調整:

-Xms:堆的最小值;

-Xmx:堆的最大值;

-Xmn:新生代的大小;

-XX:NewSize;新生代最小值;

-XX:MaxNewSize:新生代最大值;

例如- Xmx256m

運行時常量池  

符號引用(一個概念) 

一個java類(假設爲People類)被編譯成一個class文件時,如果People類引用了Tool類,但是在編譯時People類並不知道引用類的實際內存地址,因此只能使用符號引用來代替。

而在類裝載器裝載People類時,此時可以通過虛擬機獲取Tool類的實際內存地址,因此便可以既將符號org.simple.Tool替換爲Tool類的實際內存地址,及直接引用地址。

即在編譯時用符號引用來代替引用類,在加載時再通過虛擬機獲取該引用類的實際地址.

以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現的內存佈局是無關的,引用的目標不一定已經加載到內存中。

字面量

文本字符串        String a = "abc",這個abc就是字面量

八種基本類型int a = 1; 這個1就是字面量

聲明爲final的常量

直接內存

直接內存使用場景

  • 有很大的數據需要存儲,它的生命週期很長
  • 適合頻繁的IO操作,例如網絡併發場景

使用Native函數庫直接分配堆外內存(NIO)

並不是JVM運行時數據區域的一部分,但是會被頻繁使用(可以通過-XX:MaxDirectMemorySize來設置(不設置的話默認與堆內存最大值一樣,也會出現OOM異常)

使用直接內存避免了在Java 堆和Native 堆中來回複製數據,能夠提高效率

測試用例JavaStack:設置JVM參數-Xmx100m,運行異常,因爲如果沒設置-XX:MaxDirectMemorySize,則默認與-Xmx參數值相同爲100M,分配128M直接內存超出限制範圍

站在線程角度來看

  • 虛擬機棧、本地方法棧、程序計數器三個區域的生命週期和線程相同。屬於線程私有
  • 線程共享區域:設計到生命週期管理和垃圾回收等概念,後續章節有細講。

深入辨析堆和棧

  • 以棧幀的方式存儲方法調用的過程,並存儲方法調用過程中基本數據類型的變量(int、short、long、byte、float、double、boolean、char等)以及對象的引用變量(reference),其內存分配在棧上,變量出了作用域就會自動釋放;
  • 而堆內存用來存儲Java中的對象。無論是成員變量,局部變量,還是類變量,它們指向的對象都存儲在堆內存中;

線程獨享還是共享

  • 棧內存歸屬於單個線程,每個線程都會有一個棧內存,其存儲的變量只能在其所屬線程中可見,即棧內存可以理解成線程的私有內存。
  • 堆內存中的對象對所有線程可見。堆內存中的對象可以被所有線程訪問

空間大小

  • 棧的內存要遠遠小於堆內存

棧溢出

參數:-Xss256k

java.lang.StackOverflowError  一般的方法調用是很難出現的,如果出現了可能會是無限遞歸。

虛擬機棧帶給我們的啓示:方法的執行因爲要打包成棧楨,所以天生要比實現同樣功能的循環慢,所以樹的遍歷算法中:遞歸和非遞歸(循環來實現)都有存在的意義。遞歸代碼簡潔,非遞歸代碼複雜但是速度較快。

OutOfMemoryError:不斷建立線程。(一般演示不出,演示出來機器也死了)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發佈了39 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章