JVM總結之JVM內存結構


java虛擬機是是每個java程序員學習的重點。

JVM內存結構

JVM的內存結構主要由兩大部分組成:
1、線程私有部分:程序計數器、虛擬機棧、本地方法棧。
2、線程共享部分:方法區和堆。

另外直接內存:不是虛擬機運行
時數據區的一部分,也不是
java虛擬機規範中定義的內
存區域;
在這裏插入圖片描述

1、線程私有部分

① 程序計數器

較小的內存空間,當前線程執行的字節碼的行號指示器;每條線程都要有一個獨立的程序計數器,各線程互不影響;

正在執行 java 方法的話,計數器記錄的是虛擬機字節碼指令的地址(當前指令的地址)。如果還是 Native 方法,則爲空。

這個內存區域是唯一一個在虛擬機中沒有規定任何 OutOfMemoryError 情況的區域。

② 虛擬機棧

線程私有,生命週期和線程,每個方法在執行的同時都會創建一個棧幀 用於存儲局部變量表,操作數棧,動態鏈接,方法出口等信息 。方法的執行就對應着棧幀在虛擬機棧中入棧和出棧的過程; 棧裏面存放着各種基本數據類型和對象的引用

棧幀( Frame)是用來存儲數據和部分過程結果的數據結構,同時也被用來處理動態鏈接(Dynamic Linking)、 方法返回值和異常分派( Dispatch Exception)。棧幀隨着方法調用而創建,隨着方法結束而銷燬——無論方法是正常完成還是異常完成(拋出了在方法內未被捕獲的異常)都算作方法結束。
在這裏插入圖片描述
功能:
以棧幀的方式存儲方法調用的過程,並存儲方法調用過程中基本數據類型的變量(int、short、long、byte、float、double、boolean、char等)以及對象的引用變量,其內存分配在棧上,變量出了作用域就會自動釋放;

③ 本地方法區

本地方法區和 Java Stack 作用類似, 區別是虛擬機棧爲執行 Java 方法服務, 而本地方法棧則爲Native 方法服務,當一個JVM創建的線程調用native方法後,JVM不再爲其在虛擬機棧中創建棧幀,JVM只是簡單地動態鏈接並直接調用native方法。

2、線程共享部分

① 堆

Java堆是Javaer需要重點關注的一塊區域, 創建的對象和數組都保存在 Java 堆內存中,也是垃圾收集器進行垃圾收集的最重要的內存區域 。由於現代 VM 採用分代收集算法, 因此 Java 堆從 GC 的角度還可以細分爲: 新生代(Eden 區、From Survivor 區和 To Survivor 區)和老年代 ,在下文垃圾回收的內容會詳細介紹。
功能:
堆內存用來存儲Java中的對象。無論是成員變量,局部變量,還是類變量,它們指向的對象都存儲在堆內存中;

② 方法區(永久代)

方法區也叫永久區,在jdk1.6之前主要存放 被虛擬機加載的類信息Class 和 Meta(元數據)的信息、常量(“zdy”,"123"等)、靜態變量(static變量)、即時編譯器編譯後的代碼等數據,Class 在被加載的時候被放入永久區域,它和和存放實例的區域不同,GC 不會在主程序運行期對永久區域進行清理。所以這也導致了永久代的區域會隨着加載的 Class 的增多而脹滿,最終拋出 OOM 異常。從jdk1.7開始方法區開始有些改變,詳情在下文會介紹。

運行時常量池(Runtime Constant Pool)

運行時常量池是方法區的一部分。Class 文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載後存放到方法區的運行時常量池中。 Java 虛擬機對 Class 文件的每一部分(自然也包括常量池)的格式都有嚴格的規定,每一個字節用於存儲哪種數據都必須符合規範上的要求,這樣纔會
被虛擬機認可、裝載和執行。

堆和棧的區別

  1. 棧的內存要遠遠小於堆內存,棧的深度是有限制的,如果遞歸沒有及時跳出,很可能發生StackOverFlowError問題。
  2. 你可以通過-Xss選項設置棧內存的大小。-Xms選項可以設置堆的開始時的大小,-Xmx選項可以設置堆的最大值。

3、直接內存

直接內存不是虛擬機運行時數據區的一部分,也不是java虛擬機規範中定義的內存區域;

如果使用了NIO,這塊區域會被頻繁使用,在java堆內可以用directByteBuffer對象直接引用並操作;

這塊內存不受java堆大小限制,但受本機總內存的限制,可以通過MaxDirectMemorySize來設置(默認與堆內存最大值一樣),所以也會出現OOM異常;

在jdk1.8以後還把元空間放到直接內存彙總了,詳情看下文各jdk版本的區別

4、jdk1.6、jdk1.7和jdk1.8內存結構區別

jdk1.6、jdk1.7和jdk1.8內存結構區別主要體現在運行時常量池和方法去上,如下:

在jdk1.6和jdk1.6之前的版本,運行時常量池是在方法區中的:
在這裏插入圖片描述

到了jdk1.7就把運行時常量池放到堆內存中了:
在這裏插入圖片描述
到了jdk1.8以後就把方法區去掉了,方法區的內容都放到元空間裏,並且元空間是存在於本地直接內存的,所以類的數據不再受方法區空間的大小限制,只收本地內存大小的限制。
在這裏插入圖片描述

爲什麼去除方法區

  1. 永久代來存儲類信息、常量、靜態變量等數據不是個好主意, 很容易遇到內存溢出的問題.JDK8的實現中將類的元數據放入 native memory, 將字符串池和類的靜態變量放入java堆中. 可以使用MaxMetaspaceSize對元數據區大小進行調整;
  2. 對永久代進行調優是很困難的,同時將元空間與堆的垃圾回收進行了隔離,避免永久代引發的Full GC和OOM等問題;

注意: java8去掉了-XX:PermSize和-XX:MaxPermSize,新增了-XX:MetaspaceSize和-XX:MaxMetaspaceSize
在這裏插入圖片描述

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