不同版本JVM內存劃分的變化
1.6教寂,使用永久代(PermGen)來實現方法區(qū)捏鱼,運行時常量池在方法區(qū)中。
1.7酪耕,還有永久代导梆,運行時常量池在堆中。
1.8,沒有永久代看尼,使用元空間(在直接內存)
總結:1.7 運行時常量池到了堆递鹉,1.8方法區(qū)被元空間替換
什么變量會在棧里,什么變量會在堆里藏斩?
方法里的局部變量
對象中的成員變量
JVM 內存結構
JVM內存的劃分
(1)線程私有區(qū):
- 程序計數器躏结,記錄正在執(zhí)行的虛擬機字節(jié)碼的地址;
- Java虛擬機棧:Java方法執(zhí)行的內存區(qū)狰域,每個方法執(zhí)行時會在虛擬機棧中創(chuàng)建棧幀媳拴;
- 本地方法棧:虛擬機的Native方法執(zhí)行的內存區(qū);
(2)線程共享區(qū):
Java堆:對象分配內存的區(qū)域北专;
-
方法區(qū):存放類信息禀挫、常量、靜態(tài)變量拓颓、編譯器編譯后的代碼等數據语婴;
- 常量池:存放編譯器生成的各種字面量和符號引用,是方法區(qū)的一部分驶睦。
程序計數器(PC 寄存器)
程序計數器的定義
程序計數器是一塊較小的內存空間砰左,是當前線程正在執(zhí)行的那條字節(jié)碼指令的地址。若當前線程正在執(zhí)行的是一個本地方法场航,那么此時程序計數器為Undefined
缠导。
程序計數器的作用
- 字節(jié)碼解釋器通過改變程序計數器來依次讀取指令,從而實現代碼的流程控制溉痢。
- 在多線程情況下僻造,程序計數器記錄的是當前線程執(zhí)行的位置,從而當線程切換回來時孩饼,就知道上次線程執(zhí)行到哪了髓削。
Java 虛擬機棧(Java 棧)
Java 虛擬機棧的定義
Java 虛擬機棧是描述 Java 方法運行過程的內存模型。
Java 虛擬機棧會為每一個即將運行的 Java 方法創(chuàng)建一塊叫做“棧幀”的區(qū)域镀娶,用于存放該方法運行過程中的一些信息立膛,如:
- 局部變量表
- 操作數棧
- 動態(tài)鏈接
- 方法出口信息
壓棧出棧過程
當方法運行過程中需要創(chuàng)建局部變量時,就將局部變量的值存入棧幀中的局部變量表中梯码。
Java 虛擬機棧的棧頂的棧幀是當前正在執(zhí)行的活動棧宝泵,也就是當前正在執(zhí)行的方法,PC 寄存器也會指向這個地址轩娶。只有這個活動的棧幀的本地變量可以被操作數棧使用儿奶,當在這個棧幀中調用另一個方法,與之對應的棧幀又會被創(chuàng)建鳄抒,新創(chuàng)建的棧幀壓入棧頂廓握,變?yōu)楫斍暗幕顒訔?/p>
方法結束后搅窿,當前棧幀被移出,棧幀的返回值變成新的活動棧幀中操作數棧的一個操作數隙券。如果沒有返回值,那么新的活動棧幀中操作數棧的操作數沒有變化闹司。
堆
堆的定義
堆是用來存放對象的內存空間娱仔,幾乎所有的對象都存儲在堆中。
堆的特點
- 線程共享游桩,整個 Java 虛擬機只有一個堆牲迫,所有的線程都訪問同一個堆。
- 在虛擬機啟動時創(chuàng)建借卧。
- 是垃圾回收的主要場所盹憎。
- 進一步可分為:新生代(Eden區(qū) From Survior To Survivor)、老年代铐刘。
不同的區(qū)域存放不同生命周期的對象陪每,這樣可以根據不同的區(qū)域使用不同的垃圾回收算法,更具有針對性镰吵。
堆的大小既可以固定也可以擴展檩禾,但對于主流的虛擬機,堆的大小是可擴展的疤祭,因此當線程請求分配內存盼产,但堆已滿,且內存已無法再擴展時勺馆,就拋出 OutOfMemoryError 異常戏售。
Java 堆所使用的內存不需要保證是連續(xù)的。而由于堆是被所有線程共享的草穆,所以對它的訪問需要注意同步問題灌灾,方法和對應的屬性都需要保證一致性。
方法區(qū)?受到版本變化
JDK 1.8 的時候续挟,方法區(qū)被徹底移除了紧卒,取而代之是元空間,元空間使用的是直接內存诗祸。
JDK 1.8 之前永久代還沒被徹底移除的時候通常通過下面這些參數來調節(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ū)存放以下信息:
- 已經被虛擬機加載的類信息
- 即時編譯器編譯后的代碼
方法區(qū)的特點
- 線程共享直颅。 方法區(qū)是堆的一個邏輯部分博个,因此和堆一樣,都是線程共享的功偿。整個虛擬機中只有一個方法區(qū)盆佣。
- 永久代往堡。 方法區(qū)中的信息一般需要長期存在,方法區(qū)又是堆的邏輯分區(qū)共耍,因此用堆的劃分方法虑灰,把方法區(qū)稱為“永久代”。永久代是 HotSpot 虛擬機對虛擬機規(guī)范中方法區(qū)的一種實現方式痹兜。
- 內存回收效率低穆咐。 方法區(qū)中的信息一般需要長期存在,回收一遍之后可能只有少量信息無效字旭。主要回收目標是:對常量池的回收对湃;對類型的卸載。
- Java 虛擬機規(guī)范對方法區(qū)的要求比較寬松遗淳。 和堆一樣拍柒,允許固定大小,也允許動態(tài)擴展屈暗,還允許不實現垃圾回收拆讯。
運行時常量池 Runtime Constant Pool
JDK1.7 及之后版本的 JVM 已經將運行時常量池從方法區(qū)中移了出來,在 Java 堆(Heap)中開辟了一塊區(qū)域存放運行時常量池恐锦。
運行時常量池是方法區(qū)的一部分往果。class文件常量池將在類加載后進入方法區(qū)的運行時常量池中存放。
一個類加載到JVM中后對應一個運行時常量池一铅,運行時常量池相對于class文件常量池來說具備動態(tài)性陕贮,即字面量可以動態(tài)的添加。Java語言并不要求常量一定只能在編譯期產生潘飘,運行期間也可能產生新的常量(基本類型包裝類和String)肮之,這些常量被放在運行時常量池中。
字面量:字符串字面量和聲明為final的常量值(基本數據類型)
-
符號引用:編譯語言層面的概念卜录,包括以下3類:
- 類和接口的全限定名
- 字段的名稱和描述符
- 方法的名稱和描述符
class文件常量池(靜態(tài)常量池)
class文件常量池是指編譯生成的class字節(jié)碼文件結構中的一個常量池(Constant Pool Table)戈擒,用于存放編譯期間生成的各種字面量和符號引用,這部分內容將在類加載后艰毒,存放于方法區(qū)的運行時常量池筐高。
字符串字面量除了類中所有雙引號括起來的字符串(包括方法體內的),還包括所有用到的類名丑瞧、方法名和這些類與方法的字符串描述柑土、字段(成員變量)的名稱和描述符
聲明為final的常量值指的是成員變量,不包含本地變量绊汹,本地變量是屬于方法的稽屏。
符號引用包括類和接口的全限定名(包括包路徑的完整名)、字段的名稱和描述符西乖、方法的名稱和描述狐榔。只不過是以一組符號來描述所引用的目標坛增,和內存并無關,所以稱為符號引用薄腻,直接指向內存中某一地址的引用稱為直接引用
當類被 Java 虛擬機加載后收捣, .class 文件中的常量池就存放在方法區(qū)的運行時常量池中。
而且在運行期間庵楷,可以向常量池中添加新的常量坏晦。如 String 類的 intern() 方法就能在運行期間向常量池中添加字符串常量。
運行時常量池和字符串常量池
運行時常量池存儲的是一系列字節(jié)嫁乘,字符串是被序列化的,而不是字符串對象球碉,還有其他常量蜓斧,不止字符串。
字符串常量池是在運行時用的睁冬,里面都是java對象挎春。
字符串字面量是讀String類對象的引用。
Class類的對象存在JVM哪里直奋?
Class對象是存放在堆區(qū)的。
類的元數據(元數據并不是類的Class對象施禾,Class對象是加載的最終產品脚线。類的方法代碼,變量名弥搞,方法名邮绿,訪問權限,返回值等等都是在方法區(qū)的)才是存在方法區(qū)的攀例。
一個對象的內存劃分船逮。
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;
}
從內存區(qū)域來分析
- 虛擬機棧:只存放局部變量
- 堆:存儲對象的實例
- 方法區(qū):存放Class信息和常量信息(final的東西)。
從變量的角度來分析
-
局部變量:
存放在虛擬機棧中(具體應為[棧->棧幀->局部變量表])
- 基本類型的值直接存在棧中粤铭。如age=10
- 如果是對象的實例挖胃,則只存儲對象實例的引用。如s=ref
實例變量:存放在堆中的對象實例中梆惯。如Student的實例變量 name=ref
-
靜態(tài)變量:
存放在方法區(qū)中的常量池中酱鸭。如Student.class中的birthday=ref。
- 如果常量的類型是對象的實例則只存儲對象實例的引用地址
通過變量的角度來分析加袋,我們就可以了解為什么靜態(tài)變量不用new就能調用凛辣,而實例變量必須new出對象,才能調用职烧。
參考
https://github.com/doocs/jvm/blob/master/docs/01-jvm-memory-structure.md