深入瞭解JVM之內存區域(一)

一、概述

    瞭解JAVA的內存區域是學習JVM第一步,只有懂得了各個區域的工作原理、服務對象以及其中可能存在的問題,才能爲日後的JVM調優、內存溢出排錯等問題提供幫助。
    根據《虛擬機規範》第二版規定,JVM管理的內存將包含以下幾個運行時數據區域:
在這裏插入圖片描述

二、各個區域

    這一部分的闡述是基於JDK1.6,JDK1.7和1.8的改動會在下一部分進行講解。

1.程序計數器

    當前線程字節碼運行的行號指示器,通過程序計數器實現分支、循環、跳轉、異常處理、線程恢復等基礎功能。這是唯一不會發生內存溢出的區域。

2.虛擬機棧

    每一個方法的執行都會創建一個棧幀,用於存儲局部變量表、方法出口、動態鏈接、操作棧等信息。每一個方法從執行到結束都對應了一個棧幀從虛擬機棧入棧到出棧的過程。
    局部變量表:存放了編譯器可知的各種基礎數據類型(boolean、byte、char、short、int、float、long、double),對象應用(reference類型,代表對象的句柄或者指向對象起始地址的引用指針 ),和returnAddress(指向了一條字節碼指令的地址)。局部變量表所需的空間是編譯期間已經確定,在運行期間不會改變大小 。
    動態鏈接:每一個棧幀都包含了當前方法指向運行時常量池中的符號引用以支持動態鏈接。這些符號引用在運行時轉化爲直接引用。
    操作棧:每一個棧幀都包含了一個先進後出棧,稱爲操作數棧。操作數棧是方法調用和執行的空間,通過彈棧/壓棧的形式來操作數據。JVM提供了將局部變量或常量加載到操作數棧的字節碼指令,用這些指令將參與運算的值壓入操作數棧,然後彈出這些值,進行運算,將結果壓回操作數棧。
    方法返回地址:方法執行後下一步指令的地址,方法正常退出時,程序計數器的值可以作爲返回地址;異常退出時,返回地址要通過異常處理器決定。

3.本地方法棧

    爲虛擬機使用到的native方法服務。

4.直接內存

    在JDK1.4後引入了NIO,一種基於通道channel和緩衝區buffer的I/O方式,它可以使用native函數庫直接分配堆外內存,然後通過一個存儲在Java堆裏面的DirectByteBuffer對象作爲這塊內存的引用進行操作,這樣可以避免在java堆和native堆中來回複製數據,在某些場景顯著提高性能

5.方法區

    存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據,也稱爲非堆(Non-Heap)。該區域很少出現垃圾回收,回收目標主要是針對常量池的回收和對類型的卸載。

6.運行時常量池

    運行時常量池是方法區的一部分,用於存放多種類型的常量,範圍是從編譯期已知的字面量到必須在編譯期解析的方法和字段引用。
    String.intern()方法可用來返回常量池中的某字符串,如果常量池中已經存在該字符串,則直接返回常量池中該對象的引用。如果不存在,則在該字符串放入常量池中。
    對於直接做+運算的兩個字符串(字面量)常量,並不會放入字符串常量池中,而是直接把運算後的結果放入字符串常量池中。(String s = “abc”+ “def”, 會直接生成“abcdef"字符串常量 而不把 “abc” "def"放進常量池)
    對於先聲明的字符串字面量常量,會放入字符串常量池,但是若使用字面量的引用進行運算就不會把運算後的結果放入字符串常量池中了(String s = new String(“abc”) + new String(“def”),在構造過程中不會生成“abcdef"字符串常量)
    字符串常量池:在JDK1.7之前,還在運行時常量池;在JDK1.7之後,移到堆中,存儲字符串常量和字符串引用

7.堆

    用於存放對象實例,是垃圾收集器的主要工作區域。Java堆分爲新生代和老年代,其中新生代還分爲Eden區、From Survior區和To Survior區。新生代和老年代的默認比例是1:2。爲了更好地回收和分配內存,線程共享的堆還劃分出了多個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)。

三、版本更新

    引用JDK1.7和JDK1.8的內存模型比較這篇博客的一個回答:

在JDK1.7之前,運行時常量池、字符串常量池存放在方法區, 此時hotspot虛擬機對方法區的實現爲永久代。
在JDK1.7,字符串常量池被從方法區拿到了堆中, 這裏沒有提到運行時常量池,也就是說字符串常量池被單獨拿到堆,運行時常量池剩下的東西還在方法區, 也就是hotspot中的永久代
在JDK1.8 hotspot移除了永久代用元空間(Metaspace)取而代之, 這時候字符串常量池還在堆, 運行時常量池還在方法區, 只不過方法區的實現從永久代變成了元空間(Metaspace)

    JDK1.8內存模型:
JDK1.8內存模型

四、相關問題

1、什麼是字面量和符號引用?

    字面量包括:1.文本字符串 2.八種基本類型的值 3.被聲明爲final的常量等;
    符號引用包括:1.類和方法的全限定名 2.字段的名稱和描述符 3.方法的名稱和描述符。

2、方法區中除了運行時常量池,還有什麼?

    class常量池:我們寫的每一個Java類被編譯後,就會形成一份class文件;class文件中除了包含類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池(constant pool table),用於存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References);
    字符串常量池:在HotSpot VM裏實現的string pool功能的是一個StringTable類,它是一個Hash表,默認值大小長度是1009;這個StringTable在每個HotSpot VM的實例只有一份,被所有的類共享。字符串常量由一個一個字符組成,放在了StringTable上。
    在JDK6.0中,StringTable的長度是固定的,長度就是1009,因此如果放入String Pool中的String非常多,就會造成hash衝突,導致鏈表過長,當調用String#intern()時會需要到鏈表上一個一個找,從而導致性能大幅度下降;可以存放字符串。
    在JDK7.0中,StringTable的長度可以通過參數-XX:StringTableSize指定,可以存放字符串以及放於堆內的字符串對象的引用。

五、參考

Java虛擬機棧之操作數棧
JVM虛擬機規範官方文檔
JDK1.7和JDK1.8的內存模型比較
Java中的常量池(字符串常量池、class常量池和運行時常量池)
《深入瞭解Java虛擬機:JVM高級特性與最佳實踐》書籍

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