[二]Java虛擬機 jvm內存結構 運行時數據內存 class文件與jvm內存結構的映射 jvm數據類型 虛擬機棧 方法區 堆 含義

前言簡介

class文件是源代碼通過編譯後的一種平臺中立的格式
裏面包含了虛擬機運行所須要的全部信息,至關於 JVM的機器語言
JVM全稱是Java Virtual Machine  ,既然是虛擬機,他終歸要運行在物理機上
在操做系統中體現出來的也就是一個進程
操做系統會給他分配資源,割一塊內存做爲他的地盤
class文件是靜態的,想要運行程序,JVM須要將class文件中的信息加載到加載到他的地盤
而後處理他能夠處理的數據類型的數據
 
JVM將這塊內存按照功能進行了更細的劃分,不過終究是一個規範,虛擬機的廠商在實現的時候仍舊有很大的自由度
接下來將會從兩個方面    虛擬機能夠處理的數據類型  以及    運行時的數據區的內存模型
對虛擬機進行簡單的介紹

 

數據類型

數據類型分類

虛擬機能夠處理的數據類型分爲:基本類型和引用類型兩類
因此對於值,也就存在基本類型值 和 引用值 兩種類型的值
基本類型又分爲 數值類型/boolean/returnAddress 三種
 
數值類型又分爲整數類型和浮點數類型
 
整數類型(byte short int long char) 與浮點數(float  double)  與java語言中的值域在任何地方都是一致的,好比 取值範圍表示含義
 
boolean編譯後使用Java虛擬機中的int 數據類型代替,不過Java虛擬機支持boolean類型的數組,0表示false 1表示true
 
returnAddress 在Java語言中並不存在相應的類型 也就是程序員不能使用這個類型 ,並且也沒法在程序運行期間更改
 
引用類型分爲三種  類類型  接口類型 數組類型
值都是動態建立對象的引用
類類型的值是對類實例的引用
數組類型的值是對數組對象的引用
接口類型的值 是對實現了該接口的某個類實例的引用
另外還有一個特殊的引用null
 
 
image_5b84bfb7_6acf
 

取值範圍

byte 8位   有符號 二進制補碼整數  默認值零(-2^7到2^7-1  包括兩端的值在內)
short 16位 有符號 二進制補碼整數 默認值零(-2^15到2^15-1  包括兩端的值在內)
int
32位 有符號 二進制補碼整數 默認值零(-2^31到2^31-1  包括兩端的值在內)
long
64位 有符號 二進制補碼整數 默認值零(-2^64到2^64-1  包括兩端的值在內)
char 16位 無符號 Unicode字符 默認值爲null的碼點 '\u0000'   (0 到2^16-1  包括兩端的值在內)
float 32位 IEEE754標準單精度浮點數 默認值正數0
double 64位 IEEE754標準雙精度浮點數 默認值正數0
returnAddress 同一方法中某操做碼的地址
reference 堆中堆某對象的引用,或者是null



內存結構

內存結構組成部分

上面說過,程序運行,必然須要裝載數據到內存
class文件會經由classLoader加載到JVM的運行時數據區域
JVM的內存結構爲下圖右側部分
從圖中能夠看得出來
大體分爲 方法區/堆/程序計數器/虛擬機棧/本地方法棧   五部分
接下來逐個進行介紹
ps:在抽象一點,邏輯上來講其實能夠理解爲堆/棧/程序計數器 三類 
程序的運行 , 須要數據還須要方法,還須要說明從哪一個指令位置開始執行
程序計數器就是指向要執行的指令地址,標誌從哪一個位置開始執行
棧是方法調用概念的具體化數據結構,描述了怎麼執行
堆用於保存程序運行須要用到的數據對象等,描述了 執行什麼,操做什麼
image_5b84bfb7_4329
 
 

內存結構各部分詳情

一個運行時的java虛擬機實例就是負責一個java程序的運行
啓動一個Java程序一個虛擬機實例也就誕生,當這個Java程序關閉,這個虛擬機實例就銷燬 
每一個Java程序都運行於他本身的Java虛擬機實例中
一個虛擬機實例中,堆和方法區是這個Java程序全部線程共享的
Java虛擬機棧和本地方法棧和程序計數器 是線程隔離獨有的
下面全部說到的都是基於一個Java程序內的場景
 
image_5b84bfb8_34ac
 
 

方法區

方法區是可供各個線程共享的運行時內存區域,存儲了每個類的結構信息,如:
運行時常量池/字段和方法數據/構造函數和普通方法的字節碼內容/類實例接口初始化時用到的特殊方法
方法區在虛擬機啓動的時候建立
方法區也能夠被垃圾收集
方法區大小沒必要是固定的 能夠根據須要動態擴展  
方法區空間也沒必要是連續的
 
具體存儲的信息包括:
類型信息
類的全限定名
類型的直接超類全限定名
類型 類仍是接口
訪問修飾符
直接超接口的全限定名
字段信息
字段名
字段類型
字段的修飾符
方法信息
方法名
方法的返回類型
方法的參數數量和類型
方法的修飾符
方法的字節碼(有方法體的)
操做數棧和該方法棧幀中的局部變量表   的大小(其實也仍是class文件屬性表的內容 靜態的)
常量池--下面的運行時常量池區域
除了常量之外的全部類變量
類變量是全部類實例共享的,即便沒有任何類實例,他也能夠被訪問,這些變量僅僅和類有關
因此
類變量老是做爲類型信息的一部分存儲在方法區
除了在類中聲明的編譯時常量外,虛擬機使用某個類以前 必須在方法區中爲這些類分配空間
編譯時常量指的是final聲明以及用編譯時已知的值初始化的類變量
這種和通常的類變量還不同,每一個使用編譯時常量的類型,都會複製他的全部常量到本身的常量池中 或者嵌入到他的字節碼流中
說白了對於這種值不變的,直接複製過去
類ClassLoader的引用/Class類的引用
每一個類被裝載後都必須跟蹤他是由哪一個類加載器加載的
對於每一個被裝載的類型,不論是類仍是接口,虛擬機都會相應的爲他建立一個java.lang.Class類的實例
並且虛擬機還必須以某種方式把這個實例和存儲在方法區中的類型數據關聯起來

運行時常量池

運行時常量池屬於方法區的一部分
class文件中每個類或者接口的常量池表 constant_pool table 運行時的表示形式
只須要記住與class文件中的constant_pool相對應便可理解所包含的內容
包括了若干種不一樣的常量
從編譯器可知的數值字面量到必須在運行期解析後才能得到的方法或字段引用
運行時常量在Java虛擬機的方法區分配  加載類或者接口到虛擬機後,就建立對應的運行時常量池
 
總結:
全部的類型信息,靜態數據信息,都加載到方法區中
另外類加載器以及當前Class對象這種運行時必須的信息,也被保存在方法區

Java堆

一個java程序獨佔一個虛擬機實例,也就是每一個java程序一個獨立的堆空間
可是對於同一個java程序   堆是各個線程共享的運行時內存區域 
是全部類實例和數組對象分配內存的區域
Java堆在虛擬機啓動時就被建立了
存儲了被自動內存管理系統 也就是GC( garbage Collector) 所管理的各類對象
這些受管理的對象不須要也也不能顯式的銷燬
之因此這麼說是由於有分配新對象的指令,卻沒有釋放內存的指令,因此就不能顯式的銷燬
 
堆是垃圾收集器工做的主要區域 
堆空間沒必要連續也能夠動態擴展或者收縮
對象的內部表示形式,規範並無規定 實現者能夠按需發揮
 


image_5b84bfb8_331e
 
 

Java虛擬機棧

java虛擬機棧,是對方法調用這一抽象概念的具體化描述,方法執行的內存模型
啓動一個新線程 Java虛擬機就會爲他分配一個Java棧,用於保存棧幀
虛擬機只會直接對Java棧執行兩種操做   以棧幀爲單位的出棧或者入棧
 
每一個方法在執行的同時都會建立一個棧幀  棧幀用於存儲局部變量表 操做數棧 動態連接 方法出口等信息
每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機中從入棧到出棧的過程
 
棧上全部的數據都是線程私有的任何線程都不能訪問另外一個線程的棧數據
也就是說,徹底無需考慮多線程狀況下數據的訪問同步問題
當一個線程調用另外一個方法時,方法的局部變量保存在調用線程的Java虛擬機棧的棧幀中
只有一個線程老是能訪問那些局部變量即調用方法的線程

棧幀

三部分組成:  局部變量表  操做數棧 以及棧幀數據區
當虛擬機調用一個方法時,從對應類的類型信息中獲得此方法的局部變量表和操做數棧的大小(code 屬性)
並以此分配棧幀內存,而後壓入Java棧中
棧幀隨着方法的調用而建立隨着方法結束而銷燬,不管方法正常完成仍是異常完成都算做方法結束
 
局部變量表 長度由編譯期決定,經過方法code屬性提供 除了long和double 使用兩個局部變量外,其他類型均爲一個
保存了對應方法的參數和局部變量
 
操做數棧 後進先出,操做數棧最大深度編譯期決定經過code屬性保存提供 每一個位置能夠保存一個java虛擬機中定義的任意數據類型的值包括long double
操做數棧做爲虛擬機的工做區,大多數指令都要從這裏彈出數據執行計算而後把結果壓回操做數棧
 
棧幀數據區 除了局部變量和操做數棧外,還須要一些其餘的數據,好比 常量池的入口信息,每當虛擬機要執行某個須要用到常量池數據的指令時
都會經過棧幀數據區中指向常量池的指針來訪問他
 
 

本地方法棧

本地方法棧並非虛擬機明肯定義的,是可選的
Java虛擬機實現可能會使用到傳統的棧(一般稱之爲C stack) 來支持native方法(指 用Java之外的其餘語言編寫的方法)
這個棧就是本地方法棧 當Java虛擬機使用其餘語言好比C語言,來實現指令集解釋器的時候,也可使用本地方法棧
若是Java虛擬機自己不支持native方法,或是自己不依賴傳統棧,那麼能夠不提供本地方法棧
若是支持本地方法棧  那這個棧通常會在建立線程的時候按線程分配
 
 

程序計數器 java

程序計數器 又叫作 PC寄存器 PC爲program counter
無論稱呼如何,其本意是保存當前正在執行的指令的地址,
此處,咱們能夠看作是當前線程所執行的字節碼的行號指示器
也就是程序的運行徹底依賴PC寄存器,須要依靠他獲取下一條須要執行的字節碼指令
 
JVM的多線程時經過線程輪流切換並分配處理器執行時間片的方式實現的,在任何一個肯定的時刻
一個處理器(一個內核) 都只能執行一條線程中的指令,爲了線程切換後能恢復到正確的位置
因此每一個線程都須要一個獨立的程序計數器,因此程序計數器是線程私有的 線程啓動時建立
 
若是執行的是Java方法,值爲正在執行的虛擬機的字節碼指令地址
若是是Native方法,計數器值爲空 Undefined  ,此區域 沒有OOM
 

直接內存

直接內存並非虛擬機運行時的數據區,也不是Java虛擬機規範中定義的內存區
可是這部份內存也被頻繁的調用,也可能致使OOM
是引入NIO後,引入的一種基於通道與緩衝區的IO方式
可使用native 函數庫直接分配堆外內存,而後經過Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做
能在一些場景中顯著提升性能
既然不屬於java堆,天然不受制於Java堆大小的限制,可是,必須運行於物理機
天然受制於本機總內存大小
 
 

總結

JVM運行時的內存結構,就是爲了執行字節碼文件,而將class文件中的信息加載到內存中的一個邏輯映射
class文件是源代碼的靜態抽象的數據結構描述
運行時內存結構是對於class文件的執行行爲的結構描述
 
以上全部的要求說明都是屬於規範上的並不要求全部的實現與規範中定義的抽象元素徹底的對應起來
抽象的內部組件和行爲的描述,僅僅是定義Java虛擬機所應該呈現出來的外部行爲
也就是說,一個具體的虛擬機實現,可能與咱們說過的規範相同,也可能與規範有出入
可是隻要他的外部行爲是一致的,正確識別class文件,遵照class文件中包含的Java代碼的語義,可以按照規定所須要呈現出來的行爲結果
執行字節碼文件便可
至於方法區到底應該如何分配空間,對象的內部表現形式如何,垃圾收集器如何運做,如何加載類都是由設計者來決定實現的.
 
舉一個淺顯的例子
你去超市購物,爲了方便攜帶,你可能會按照他們的形狀或者類別組織放到購物袋裏面
好比 生鮮放到一個袋子,零食放到一個袋子,或者放置稍大商品的購物袋裏面的縫隙處,放置一些小的商品
這是屬於全部商品的靜態描述組織
 
回到家須要作飯,可能你會把魚拿出來放到盤子裏,可能你會把青菜放到水槽中浸泡清洗,而後你可能會準備做料,洗鍋準備作菜等等
一切都按照你下廚的習慣來放置食材以及步驟進行作菜
這就是屬於動態執行行爲的結構描述
 
咱們的內存結構 程序計數器  堆   棧 就是對於代碼執行行爲過程的一種描述
能夠理解你想要先作那道菜? 程序計數器((若是把想要作的菜都列一個清單,程序計數器就是從什麼位置開始作,就是先作哪道菜)
都有哪些食材? 檯面上有青菜 魚 豆腐...  這都是存放在堆中
具體的怎麼作? 紅燒仍是清蒸?這些具體的行爲封裝在虛擬機棧的棧幀中  每次作一道菜就是入棧,作好了刷鍋就是出棧
而每道菜所須要的調味料和配菜多是獨有的,不能亂放,這些就至關於棧幀中的局部變量和操做數棧
相關文章
相關標籤/搜索