深入理解Java虛擬機第二章讀書筆記:Java內存模型

Java虛擬機在執行Java程序的過程中會把它所管理的內存劃分成若干個不同的數據區域。這些區域都有自己的用途,以及創建和銷燬的時間。有的會隨着虛擬機進程的啓動而存在,有的區域則依賴用戶進程的啓動和結束而建立或銷燬。Java虛擬機所管理的內存區域主要有一下幾個運行時數據區域。

1.程序計數器(線程私有內存)

程序計數器是每個線程都有的一個內存區域。學過操作系統的都知道,一個CPU在同一時刻只能執行一個線程(以單核CPU爲例),並且會按照一定的規則(例如時間片、優先級)輪流執行所有的線程。舉個例子,現在有線程A和線程B,CPU輪流執行AB兩個線程,首先線程A執行到一半,CPU就開始執行線程B了,那麼執行完線程B以後,CPU怎麼知道線程A上次執行到哪了呢?這個時候程序計數器就起作用了,它記錄了該線程上一次執行到的那條字節碼指令的地址。

(Java之所以具有良好的移植性,就是因爲有Java虛擬機的存在,程序首先被編譯成.class字節碼文件,然後通過JVM來解釋翻譯成機器能夠明白的二進制代碼)這個過程中字節碼解釋器就是通過改變程序計數器中的值來選取下一條需要執行的字節碼指令

如果線程正在執行的是一個Java方法,那麼程序計數器記錄的就是虛擬機正在執行的字節碼指令的地址。

如果程序正在執行的是一個Native方法,那麼程序計數器的值爲空。

(什麼是Native方法?根據Java的文檔解釋就是一個java調用了非java代碼的接口,Native方法一般用於加載文件和動態鏈接庫。由於Java語言無法直接訪問系統底層信息,那麼這個時候就要藉助C語言來完成,附上一篇關於Native方法的文章鏈接https://www.cnblogs.com/HDK2016/p/7226840.html

2.Java虛擬機棧(線程私有內存)

Java虛擬機棧的生命週期與線程相同。虛擬機棧描述的是Java方法執行時的內存模型。每個方法在被執行時,會創建一個棧幀(Stack Frame)。一個方法從調用直至執行完成的過程,就對應着一個棧幀從入棧到出棧的過程。這個棧幀中包含以下幾個部分:

  1. 局部變量表:常說的堆和棧,準確的來說其中的棧指的就是局部變量表部分。局部變量表中存放着編譯時期各種可知的基本數據類型(boolean、int、byte、char、short、float、long、double)、對象引用(reference)。long和double會佔用2個局部變量空間,其餘類型只佔用一個。局部變量表所需的空間在編譯時期就已經確定好了大小,當棧幀進入虛擬機棧時就已經知道局部變量表在棧幀中的空間大小,並且在方法運行期間不會被改變。
  2. 操作數棧
  3. 動態鏈接
  4. 方法出口

3.Java堆(所有線程共享)

Java堆的空間在虛擬機啓動時就被分配,是JVM管理的最大的一塊內存。這塊內存區域的唯一目的就是存放對象實例,所有的對象實例以及數組都要在堆上進行分配。

Java堆也是垃圾收集器管理的主要區域。從內存回收的角度來看,由於現在的收集器基本採用分代收集算法,所以堆還可以被細分被新生代和老年代。從內存分配的角度來看,由於要保證原子性,所以會給每個線程分配一個分配緩衝區,每個線程都在各自的分配緩衝區內創建屬於自己線程的對象,當分配緩衝區的空間用完了,再向虛擬機重新請求擴充分配緩衝區大的大小。

根據Java虛擬機規範的規定,Java堆可以處於物理上不連續的內存空間中,只要邏輯上是連續的即可。當前主流的虛擬機都是按照可擴展來實現的(通過-Xmx和-Xms參數控制)。

4.本地方法棧(爲虛擬機使用到的Native方法服務)

本地方法棧的功能與虛擬機棧極爲相似,只不過虛擬機棧是爲Java方法(即字節碼)服務,而本地方法棧是爲Native方法服務。

有的虛擬機甚至直接把本地方法棧和虛擬機棧合二爲一。

5.方法區(所有線程共享)

方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。

Java虛擬機規範中把方法區描述成堆的一個邏輯部分,但是實際上爲了區分它和堆,給了它一個名字叫做Non-Heap(非堆)。

方法區和堆一樣,可以擁有物理上不連續的內存,並且可以選擇不實現垃圾收集。這個內存區域的垃圾收集條件十分嚴苛。

6.運行時常量池(方法區的一部分)

Class文件中出了有類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池,用於存放編譯期產生的各種字面量和符號引用。這一部分內容將在類加載之後,進入方法區的運行時常量池中存放。

7.直接內存

直接內存並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,但是經常被引用。

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

直接內存不會受到堆內存大小的限制,但是會受到本機總內存以及分頁文件大小的限制。

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