理解 JVM:JVM 內存模型

Java 虛擬機在執行 Java 程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域,這些區域都有各自的用途,以及建立和銷燬的時間。有的區域隨着虛擬機進程的啓動就存在了, 有的區域則是依賴用戶線程。根據《Java虛擬機規範(第二版)》,Java 虛擬機所管理的內存包含如下的幾個區域。java

java-memory.jpg

運行時數據區(Runtime Data Area)

由上圖能夠看出,在運行時數據區中:虛擬機棧、本地方法棧、程序計數器屬於線程隔離的數據區,是單個線程私有的,它們的生命週期與線程相同;而方法區和堆屬於全部線程共享的數據區,是全部線程共享的。算法

程序計數器

程序計數器(Program Counter Register)是最小的一塊內存區域,它能夠看做是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏,字節碼解釋器工做時就是經過改變這個計數器的值來選取嚇一跳須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。在多線程環境下,當某個線程失去處理器執行權時,須要記錄該線程被切換出去時所執行的程序位置。從而方便該線程被切換回來(從新被處理器處理)時能恢復到當初的執行位置,所以每一個線程都須要有一個獨立的程序計數器。各個線程的程序計數器互不影響,而且獨立存儲。數組

  1. 若是線程正在執行一個 java 方法時,這個程序計數器記錄的時正在執行的虛擬機字節碼指令的地址;
  2. 若是正在執行的是 Native 方法,這個計數器的值則爲空(Undefined);
  3. 此內存區域是惟一一個在 java 虛擬機規範中沒有規定任何 OutOfMemoryError 狀況的區域。

Java 虛擬機棧

Java 虛擬機棧(Java Virtual Machine Stack)也是線程私有的,它的生命週期與線程相同。Java 虛擬機棧描述的是 Java 方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每一個方法從調用直至執行完成的過程,對應着一個棧幀在虛擬機中入棧到進棧的過程。緩存

局部變量表存放了編譯期剋制的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference 類型,它不等同於對象自己,多是一個指向對象起始地址的引用指針,也多是指向一個表明對象的句柄或其它與此對象相關的位置)和 returnAddress 類型(指向了一條字節碼指令的地址)。其中 64 位長度的 long 和 double 類型的數據會佔用 2 個局部變量表空間(Slot),其他的數據類型只佔 1 個。局部變量所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法須要在幀中分配多大的局部變量空間是徹底肯定了的,在方法運行期間不會改變局部變量表的大小。數據結構

在 Java 虛擬機規範中,對這個區域規定了兩種異常情況:多線程

  1. 若是線程請求的棧深度大於虛擬機所容許的深度,將拋出 StackOverflowError 異常;
  2. 若是虛擬機棧能夠動態擴展(當前大部分的Java虛擬機均可動態擴展,只不過Java虛擬機規範中也容許固定長度的虛擬機棧),當擴展時沒法申請到足夠的內存時會拋出 OutOfMemoryError 異常。

本地方法棧

本地方法棧(Native Method Stack)虛擬機棧所發揮的做用是很是類似的,它們的區別不過是虛擬機棧爲虛擬機執行 Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的 Native 方法服務。虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並無強制規定,所以具體的虛擬機能夠自由實現它。甚至有的虛擬機(譬如 Sun HotSpot 虛擬機)直接就把本地方法棧和虛擬機棧合二爲一。與虛擬機棧同樣,本地方法棧區域也會拋出 StackOverflowError 和 OutOfMemoryError 異常。spa

Java 堆

也叫作java 堆(Java Heap)、GC 堆(Garbage Collected Heap)是 java 虛擬機所管理的內存中最大的一塊內存區域,也是被各個線程共享的內存區域,在 JVM 啓動時建立。該內存區域存放了對象實例及數組(全部 new 的對象)。其大小經過 -Xms(最小值)和 -Xmx(最大值)參數設置,-Xms 爲 JVM啓動時申請的最小內存,默認爲操做系統物理內存的 1/64 但小於 1G,-Xmx 爲 JVM 可申請的最大內存,默認爲物理內存的 1/4 但小於 1G,默認當空餘堆內存小於 40% 時,JVM 會增大 Heap 到 -Xmx指定的大小,可經過 -XX:MinHeapFreeRation= 來指定這個比列;當空餘堆內存大於 70% 時,JVM 會減少 heap 的大小到 -Xms 指定的大小,可經過 XX:MaxHeapFreeRation= 來指定這個比列,對於運行系統,爲避免在運行時頻繁調整 Heap 的大小,一般 -Xms 與 -Xmx 的值設成同樣。操作系統

Java 堆是垃圾收集器管理的主要區域,從內存回收的角度來看,因爲如今收集器基本是採用分代收集算法,堆被劃分爲新生代和老年代。新生代主要存儲新建立的對象和還沒有進入老年代的對象。老年代存儲通過屢次新生代 GC(Minor GC) 任然存活的對象。線程

  1. 新生代: 程序新建立的對象都是重新生代分配內存,新生代由 Eden Space 和兩塊相同大小的Survivor Space(一般又稱 S0 和 S1 或 From 和 To)構成,在 Sun HotSpot 虛擬機中 Eden 和 Survivor 的大小比例是 8:1,也就是每次新生代中可用內存空間爲整個新生代容量的 90%(80% + 10%),可經過 -Xmn 參數來指定新生代的大小,也能夠經過 -XX:SurvivorRation 來調整 Eden Space 及 Survivor Space 的大小。
  2. 老年代: 用於存放通過屢次新生代 GC 任然存活的對象,例如緩存對象,新建的對象也有可能直接進入老年代,主要有兩種狀況:一、大對象,可經過啓動參數設置 -XX:PretenureSizeThreshold=1024(單位爲字節,默認爲0)來表明超過多大時就不在新生代分配,而是直接在老年代分配。二、大的數組對象,切數組中無引用外部對象。 老年代所佔的內存大小爲 -Xmx 對應的值減去 -Xmn 對應的值。

java-heap.jpg

  • Young Generation 即圖中的 Eden + From Space + To Space
    • Eden 存放新生的對象
    • Survivor Space 有兩個,存放每次垃圾回收後存活的對象
  • Old Generation Tenured Generation 即圖中的 Old Space 主要存放應用程序中生命週期長的存活對象。

方法區

方法區(Method Area)與 Java 堆同樣,是各個線程共享的內存區域,它用於內存已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然 Java 虛擬機規範把方法區描述爲堆的一個邏輯部分,可是它卻有一個別名叫作 Non-Heap(非堆)。對於 HotSpot 虛擬機,也把方法區成爲「永久代」(Permanent Generation),默認最小值爲16MB,最大值爲64MB,能夠經過 -XX:PermSize 和 -XX:MaxPermSize 參數限制方法區的大小。在 JDK 1.7 之後已經逐步改成採用 Native Memory 來實現方法區。當方法區沒法知足內存分配需求時,將拋出 OutOfMemoryError 異常。指針

運行時常量池

運行時常量池(Runtime Constant Pool)是方法區的一部分。Class 文件中處了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯器生成的各類符號引用,這部份內容將在類加載後放到方法區的運行時常量池中。當常量池沒法再申請到內存時會拋出 OutOfMemoryError 異常。對於 HotSpot 虛擬機,在 JDK 1.7 中,已經把本來放在永久代的字符串常量池移除。

直接內存

直接內存(Direct Memory)並非虛擬機運行時數據區的一部分,也不是 Java 虛擬機規範中定義的內存區域。可是這部份內存也被頻繁地使用,並且也可能致使 OutOfMemoryError 異常。jdk1.4 中新加入的NIO,引入了通道與緩衝區的IO方式,它能夠調用Native方法直接分配堆外內存,這個堆外內存就是本機內存,不會影響到堆內存的大小。

歡迎掃一掃關注 程序猿pdh 公衆號!

相關文章
相關標籤/搜索