初識JVM——運行區域

運行時區域

在Java虛擬機加載Java程序的時候會將它所管理的內存區域劃分幾個不同的數據區域,每個區域都有其特定的用途、創建時間以及銷燬時間。

在這裏插入圖片描述

程序計數器

程序計數器在內存中佔有較小的區域,可以看做是當前線程所執行的字節碼的行號指示器(如果當前執行的是Java方法,則指向的是正在執行的虛擬機字節碼指令的地址;如果當前執行的是Native方法,則該計數器中的值爲空)。字節碼指示器工作的時候就是通過改變這個計數器的值來選取下一條需執行的指令。在這個區域中,是虛擬機中唯一沒有規定任何OOM(Out Of Memory,內存溢出)情況的區域。

虛擬機棧

Java虛擬機棧和程序計數器一樣也是線程私有的,其生命週期與線程相同,它所描述的是Java方法執行的內存模型:每個方法在執行的同時會在棧中創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接、方法的返回地址等,每一個方法在調用到執行完成的過程,就對應着一個棧幀的入棧出棧過程。

本地方法棧

本地方法棧與Java虛擬機棧所發揮的作用是相似的。其中的區別在於Java虛擬機棧爲虛擬機執行Java方法服務,而本地方法棧是爲虛擬機使用Native方法而服務。本地方法棧同樣也會拋出StackOverflowError和OutOfMemoryError異常。

Java堆

相較於大多數應用而言,Java堆是Java虛擬機所管理的內存中最大的一塊,它在虛擬機啓動時創建,其唯一目的就是存放對象實例。

Java堆是垃圾收集器管理的唯一場所。從內存回收的角度看,該內存區域分爲新生代與老年代(目前的收集器主要採用分代收集算法),對於新生代還可細分爲Eden區、From Survivor區和To Survivor區(Eden與Survivor區域比爲8:1)。從內存分配的角度而言,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝區。

根據Java虛擬機規範的規定,Java堆可以處於不連續的物理內存空間中,只需保證其在邏輯上是連續的即可。故此在實現時,可以將其實現爲固定大小,也可以使爲可拓展的(使用-Xmx和-Xms控制)。當堆中的內存無法完成實例的分配且此時的堆也無法進行擴展時,便會拋出OOM異常。

方法區

方法區域Java堆一樣,是各個線程的共享的內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。在Java虛擬機規範中,將方法區描述爲堆的邏輯部分,但事實它與堆是需要區分開的。

在早期的HotSpot虛擬機中,其設計人員將GC的分代收集拓展到了方法區,故方法區被稱爲「永久代」(但事實是並不成立的,官方也在考慮放棄永久代)。

Java虛擬機對於方法區的實現限制十分寬鬆,除了和堆一樣不需要連續的物理內存空間和可以固定大小或者可拓展之外,還可以不實現垃圾收集。這個內存區域的回收目標主要針對於常量池的回收以及類型的卸載。

常量池

常量池是方法區的一部分。在Java的字節碼文件中,除了包含類的版本、字段、方法、接口等一些描述信息,其還有一項信息是常量池,,用於存放編譯期所產生的各種字面量和符號引用,這部分的內容在類加載後將放在運行時數據區域的常量池中。

直接內存

該部分不屬於運行時數據區域的一部分,也不是Java虛擬機規範中定義的內存區域。

在JDK1.4後新增的NIO引入的一種基於通道和緩衝區的I/O方式,它可以通過Native函數庫直接分配堆外內存,然後通過一個存儲在Java堆中的DirectByteBuffer對象對這塊內存的引用進行操作,避免了在Native堆與Java堆中來回複製數據,顯著提高了性能。

版本的改變

相較於JDK1.6,JDK1.7將常量池從方法區中移至堆中;

相較於JDK1.6,JDK1.8直接將方法區移除,在本地內存中增加元數據空間,常量池仍然存放在堆中,元數據空間用於存儲類加載信息。

參數設定

-Xms 初始堆內存大小 -Xmx 最大堆內存大小 -Xss 單個線程棧大小 -XX:NewSize 初始新生代堆大小 -XX:MaxNewSize 生代最大堆大小 -XX:PermSize 方法區初始大小(JDK1.7及以前) -XX:MaxPermSize 方法區最大大小(JDK1.7及以前) -XX:MetaspaceSize 元數據區初始值(JDK1.8) -XX:MaxMetaspaceSize 元數據區最大值(JDK1.8)