JVM內(nèi)存劃分

不同版本JVM內(nèi)存劃分的變化

1.6,使用永久代(PermGen)來實現(xiàn)方法區(qū),運行時常量池在方法區(qū)中。

1.7,還有永久代,運行時常量池在堆中。

1.8,沒有永久代,使用元空間(在直接內(nèi)存)

總結(jié):1.7 運行時常量池到了堆,1.8方法區(qū)被元空間替換

什么變量會在棧里,什么變量會在堆里?

方法里的局部變量

對象中的成員變量


JVM 內(nèi)存結(jié)構(gòu)

JVM內(nèi)存的劃分

(1)線程私有區(qū):

  • 程序計數(shù)器,記錄正在執(zhí)行的虛擬機字節(jié)碼的地址;
  • Java虛擬機棧:Java方法執(zhí)行的內(nèi)存區(qū),每個方法執(zhí)行時會在虛擬機棧中創(chuàng)建棧幀;
  • 本地方法棧:虛擬機的Native方法執(zhí)行的內(nèi)存區(qū);

(2)線程共享區(qū):

  • Java堆:對象分配內(nèi)存的區(qū)域;

  • 方法區(qū):存放類信息、常量、靜態(tài)變量、編譯器編譯后的代碼等數(shù)據(jù);

    • 常量池:存放編譯器生成的各種字面量和符號引用,是方法區(qū)的一部分。

程序計數(shù)器(PC 寄存器)

程序計數(shù)器的定義

程序計數(shù)器是一塊較小的內(nèi)存空間,是當前線程正在執(zhí)行的那條字節(jié)碼指令的地址。若當前線程正在執(zhí)行的是一個本地方法,那么此時程序計數(shù)器為Undefined。

程序計數(shù)器的作用

  • 字節(jié)碼解釋器通過改變程序計數(shù)器來依次讀取指令,從而實現(xiàn)代碼的流程控制。
  • 在多線程情況下,程序計數(shù)器記錄的是當前線程執(zhí)行的位置,從而當線程切換回來時,就知道上次線程執(zhí)行到哪了。

Java 虛擬機棧(Java 棧)

Java 虛擬機棧的定義

Java 虛擬機棧是描述 Java 方法運行過程的內(nèi)存模型。

Java 虛擬機棧會為每一個即將運行的 Java 方法創(chuàng)建一塊叫做“棧幀”的區(qū)域,用于存放該方法運行過程中的一些信息,如:

  • 局部變量表
  • 操作數(shù)棧
  • 動態(tài)鏈接
  • 方法出口信息

壓棧出棧過程

當方法運行過程中需要創(chuàng)建局部變量時,就將局部變量的值存入棧幀中的局部變量表中。

Java 虛擬機棧的棧頂?shù)臈钱斍罢趫?zhí)行的活動棧,也就是當前正在執(zhí)行的方法,PC 寄存器也會指向這個地址。只有這個活動的棧幀的本地變量可以被操作數(shù)棧使用,當在這個棧幀中調(diào)用另一個方法,與之對應的棧幀又會被創(chuàng)建,新創(chuàng)建的棧幀壓入棧頂,變?yōu)楫斍暗幕顒訔?/p>

方法結(jié)束后,當前棧幀被移出,棧幀的返回值變成新的活動棧幀中操作數(shù)棧的一個操作數(shù)。如果沒有返回值,那么新的活動棧幀中操作數(shù)棧的操作數(shù)沒有變化。


堆的定義

堆是用來存放對象的內(nèi)存空間,幾乎所有的對象都存儲在堆中。

堆的特點

  • 線程共享,整個 Java 虛擬機只有一個堆,所有的線程都訪問同一個堆。
  • 在虛擬機啟動時創(chuàng)建。
  • 是垃圾回收的主要場所。
  • 進一步可分為:新生代(Eden區(qū) From Survior To Survivor)、老年代。

不同的區(qū)域存放不同生命周期的對象,這樣可以根據(jù)不同的區(qū)域使用不同的垃圾回收算法,更具有針對性。

堆的大小既可以固定也可以擴展,但對于主流的虛擬機,堆的大小是可擴展的,因此當線程請求分配內(nèi)存,但堆已滿,且內(nèi)存已無法再擴展時,就拋出 OutOfMemoryError 異常。

Java 堆所使用的內(nèi)存不需要保證是連續(xù)的。而由于堆是被所有線程共享的,所以對它的訪問需要注意同步問題,方法和對應的屬性都需要保證一致性。


方法區(qū)?受到版本變化

JDK 1.8 的時候,方法區(qū)被徹底移除了,取而代之是元空間,元空間使用的是直接內(nèi)存。

JDK 1.8 之前永久代還沒被徹底移除的時候通常通過下面這些參數(shù)來調(diào)節(jié)方法區(qū)大小

-XX:PermSize=N //方法區(qū) (永久代) 初始大小
-XX:MaxPermSize=N //方法區(qū) (永久代) 最大大小,超過這個值將會拋出 OutOfMemoryError 異常:java.lang.OutOfMemoryError: PermGen

JDK 1.8

-XX:MetaspaceSize=N //設置 Metaspace 的初始(和最小大?。?-XX:MaxMetaspaceSize=N //設置 Metaspace 的最大大小

方法區(qū)的定義

Java 虛擬機規(guī)范中定義方法區(qū)是堆的一個邏輯部分。方法區(qū)存放以下信息:

  • 已經(jīng)被虛擬機加載的類信息
  • 即時編譯器編譯后的代碼

方法區(qū)的特點

  • 線程共享。 方法區(qū)是堆的一個邏輯部分,因此和堆一樣,都是線程共享的。整個虛擬機中只有一個方法區(qū)。
  • 永久代。 方法區(qū)中的信息一般需要長期存在,方法區(qū)又是堆的邏輯分區(qū),因此用堆的劃分方法,把方法區(qū)稱為“永久代”。永久代是 HotSpot 虛擬機對虛擬機規(guī)范中方法區(qū)的一種實現(xiàn)方式。
  • 內(nèi)存回收效率低。 方法區(qū)中的信息一般需要長期存在,回收一遍之后可能只有少量信息無效。主要回收目標是:對常量池的回收;對類型的卸載。
  • Java 虛擬機規(guī)范對方法區(qū)的要求比較寬松。 和堆一樣,允許固定大小,也允許動態(tài)擴展,還允許不實現(xiàn)垃圾回收。

運行時常量池 Runtime Constant Pool

JDK1.7 及之后版本的 JVM 已經(jīng)將運行時常量池從方法區(qū)中移了出來,在 Java 堆(Heap)中開辟了一塊區(qū)域存放運行時常量池。

運行時常量池是方法區(qū)的一部分。class文件常量池將在類加載后進入方法區(qū)的運行時常量池中存放。

一個類加載到JVM中后對應一個運行時常量池,運行時常量池相對于class文件常量池來說具備動態(tài)性,即字面量可以動態(tài)的添加。Java語言并不要求常量一定只能在編譯期產(chǎn)生,運行期間也可能產(chǎn)生新的常量(基本類型包裝類和String),這些常量被放在運行時常量池中。

  • 字面量:字符串字面量和聲明為final的常量值(基本數(shù)據(jù)類型)

  • 符號引用:編譯語言層面的概念,包括以下3類:

    • 類和接口的全限定名
    • 字段的名稱和描述符
    • 方法的名稱和描述符

class文件常量池(靜態(tài)常量池)

class文件常量池是指編譯生成的class字節(jié)碼文件結(jié)構(gòu)中的一個常量池(Constant Pool Table),用于存放編譯期間生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后,存放于方法區(qū)的運行時常量池。

字符串字面量除了類中所有雙引號括起來的字符串(包括方法體內(nèi)的),還包括所有用到的類名、方法名和這些類與方法的字符串描述、字段(成員變量)的名稱和描述符

聲明為final的常量值指的是成員變量,不包含本地變量,本地變量是屬于方法的。

符號引用包括類和接口的全限定名(包括包路徑的完整名)、字段的名稱和描述符、方法的名稱和描述。只不過是以一組符號來描述所引用的目標,和內(nèi)存并無關,所以稱為符號引用,直接指向內(nèi)存中某一地址的引用稱為直接引用

當類被 Java 虛擬機加載后, .class 文件中的常量池就存放在方法區(qū)的運行時常量池中。

而且在運行期間,可以向常量池中添加新的常量。如 String 類的 intern() 方法就能在運行期間向常量池中添加字符串常量。

運行時常量池和字符串常量池

運行時常量池存儲的是一系列字節(jié),字符串是被序列化的,而不是字符串對象,還有其他常量,不止字符串。

字符串常量池是在運行時用的,里面都是java對象。

字符串字面量是讀String類對象的引用。


Class類的對象存在JVM哪里?

Class對象是存放在堆區(qū)的。

類的元數(shù)據(jù)(元數(shù)據(jù)并不是類的Class對象,Class對象是加載的最終產(chǎn)品。類的方法代碼,變量名,方法名,訪問權(quán)限,返回值等等都是在方法區(qū)的)才是存在方法區(qū)的。


一個對象的內(nèi)存劃分。

https://cloud.tencent.com/developer/article/1129494

public class Student {

    private String name;
    private static Birthday birthday = new Birthday();

    public Student(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        Student s = new Student("zhangsan");
        int age = 10;
        System.out.println(age);
    }
}

class Birthday {
    private int year = 2010;
    private int month = 10;
    private int day = 1;
}
img

從內(nèi)存區(qū)域來分析

  • 虛擬機棧:只存放局部變量
  • 堆:存儲對象的實例
  • 方法區(qū):存放Class信息和常量信息(final的東西)。

從變量的角度來分析

  • 局部變量:

    存放在虛擬機棧中(具體應為[棧->棧幀->局部變量表])

    • 基本類型的值直接存在棧中。如age=10
    • 如果是對象的實例,則只存儲對象實例的引用。如s=ref
  • 實例變量:存放在堆中的對象實例中。如Student的實例變量 name=ref

  • 靜態(tài)變量:

    存放在方法區(qū)中的常量池中。如Student.class中的birthday=ref。

    • 如果常量的類型是對象的實例則只存儲對象實例的引用地址

通過變量的角度來分析,我們就可以了解為什么靜態(tài)變量不用new就能調(diào)用,而實例變量必須new出對象,才能調(diào)用。

參考

https://github.com/doocs/jvm/blob/master/docs/01-jvm-memory-structure.md

https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/jvm/Java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F.md

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容