[toc]
參考:
Java虛擬機(jī)詳解02----JVM內(nèi)存結(jié)構(gòu)
深入理解java虛擬機(jī) 精華總結(jié)(面試)
深入理解JVM(一)——JVM內(nèi)存模型
JIT與JVM的三種執(zhí)行模式:解釋模式、編譯模式、混合模式
內(nèi)存結(jié)構(gòu)
JVM內(nèi)存結(jié)構(gòu)


- 程序計(jì)數(shù)器
- Java虛擬機(jī)棧
- 本地方法棧
- 堆
- 方法區(qū)。
1. 程序計(jì)數(shù)器
**關(guān)鍵詞:
行號(hào)指示器 當(dāng)前線程執(zhí)行的字節(jié)碼指令的地址 線程私有 隨著線程的生命周期 不會(huì)出現(xiàn)OutOfMemoryError
**
1.1. 什么是程序計(jì)數(shù)器?
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,可以把它看作當(dāng)前線程正在執(zhí)行的字節(jié)碼的行號(hào)指示器。也就是說,程序計(jì)數(shù)器里面記錄的是當(dāng)前線程正在執(zhí)行的那一條字節(jié)碼指令的地址。
注:但是,如果當(dāng)前線程正在執(zhí)行的是一個(gè)本地方法,那么此時(shí)程序計(jì)數(shù)器為空。
1.2. 程序計(jì)數(shù)器的作用
程序計(jì)數(shù)器有兩個(gè)作用:
- 字節(jié)碼解釋器通過改變程序計(jì)數(shù)器來(lái)依次讀取指令,從而實(shí)現(xiàn)代碼的流程控制,如:順序執(zhí)行、選擇、循環(huán)、異常處理。
- 在多線程的情況下,程序計(jì)數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置,從而當(dāng)線程被切換回來(lái)的時(shí)候能夠知道該線程上次運(yùn)行到哪兒了。
1.3. 程序計(jì)數(shù)器的特點(diǎn)
- 是一塊較小的存儲(chǔ)空間
- 線程私有。每條線程都有一個(gè)程序計(jì)數(shù)器。
- 是唯一一個(gè)不會(huì)出現(xiàn)OutOfMemoryError的內(nèi)存區(qū)域。
- 生命周期隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而死亡。
2. Java虛擬機(jī)棧
關(guān)鍵字:
創(chuàng)建棧幀區(qū)域 存放局部變量表 操作數(shù)棧 動(dòng)態(tài)鏈接 方法返回地址等
線程私有 隨線程的生命周期
在方法運(yùn)行時(shí)局部變量表的大小是不會(huì)發(fā)生改變
2.1. 什么是Java虛擬機(jī)棧?
Java虛擬機(jī)棧是描述Java方法運(yùn)行過程的內(nèi)存模型。
Java虛擬機(jī)棧會(huì)為每一個(gè)即將運(yùn)行的Java方法創(chuàng)建一塊叫做“棧幀”的區(qū)域,這塊區(qū)域用于存儲(chǔ)該方法在運(yùn)行過程中所需要的一些信息,這些信息包括:
- 局部變量表
存放基本數(shù)據(jù)類型變量、引用類型的變量、returnAddress類型的變量。 - 操作數(shù)棧
- 動(dòng)態(tài)鏈接
- 方法返回地址
- 等
當(dāng)一個(gè)方法即將被運(yùn)行時(shí),Java虛擬機(jī)棧首先會(huì)在Java虛擬機(jī)棧中為該方法創(chuàng)建一塊“棧幀”,棧幀中包含局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法返回地址等。當(dāng)方法在運(yùn)行過程中需要?jiǎng)?chuàng)建局部變量時(shí),就將局部變量的值存入棧幀的局部變量表中。
當(dāng)這個(gè)方法執(zhí)行完畢后,這個(gè)方法所對(duì)應(yīng)的棧幀將會(huì)出棧,并釋放內(nèi)存空間。
注意:人們常說,Java的內(nèi)存空間分為“?!焙汀岸选保瑮V写娣啪植孔兞?,堆中存放對(duì)象。
這句話不完全正確!這里的“堆”可以這么理解,但這里的“棧”只代表了Java虛擬機(jī)棧中的局部變量表部分。真正的Java虛擬機(jī)棧是由一個(gè)個(gè)棧幀組成,而每個(gè)棧幀中都擁有:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口信息。

2.2. Java虛擬機(jī)棧的特點(diǎn)
- 局部變量表的創(chuàng)建是在方法被執(zhí)行的時(shí)候,隨著棧幀的創(chuàng)建而創(chuàng)建。而且,局部變量表的大小在編譯時(shí)期就確定下來(lái)了,在創(chuàng)建的時(shí)候只需分配事先規(guī)定好的大小即可。此外,在方法運(yùn)行的過程中局部變量表的大小是不會(huì)發(fā)生改變的。
- Java虛擬機(jī)棧會(huì)出現(xiàn)兩種異常:StackOverFlowError和OutOfMemoryError。
a) StackOverFlowError:
若Java虛擬機(jī)棧的內(nèi)存大小不允許動(dòng)態(tài)擴(kuò)展,那么當(dāng)線程請(qǐng)求棧的深度超過當(dāng)前Java虛擬機(jī)棧的最大深度的時(shí)候,就拋出StackOverFlowError異常。
b) OutOfMemoryError:
若Java虛擬機(jī)棧的內(nèi)存大小允許動(dòng)態(tài)擴(kuò)展,且當(dāng)線程請(qǐng)求棧時(shí)內(nèi)存用完了,無(wú)法再動(dòng)態(tài)擴(kuò)展了,此時(shí)拋出OutOfMemoryError異常。 - Java虛擬機(jī)棧也是線程私有的,每個(gè)線程都有各自的Java虛擬機(jī)棧,而且隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的死亡而死亡。
注:StackOverFlowError和OutOfMemoryError的異同?
StackOverFlowError表示當(dāng)前線程申請(qǐng)的棧超過了事先定好的棧的最大深度,但內(nèi)存空間可能還有很多。
而OutOfMemoryError是指當(dāng)線程申請(qǐng)棧時(shí)發(fā)現(xiàn)棧已經(jīng)滿了,而且內(nèi)存也全都用光了。
3. 本地方法棧
關(guān)鍵字:
本地方法運(yùn)行的內(nèi)存模型 創(chuàng)建棧幀 存放本地的局部變量表等
拋兩種異常
3.1. 什么是本地方法棧?
本地方法棧和Java虛擬機(jī)棧實(shí)現(xiàn)的功能類似,只不過本地方法區(qū)是本地方法運(yùn)行的內(nèi)存模型。
本地方法被執(zhí)行的時(shí)候,在本地方法棧也會(huì)創(chuàng)建一個(gè)棧幀,用于存放該本地方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、出口信息。
方法執(zhí)行完畢后相應(yīng)的棧幀也會(huì)出棧并釋放內(nèi)存空間。
也會(huì)拋出StackOverFlowError和OutOfMemoryError異常。
4. 堆
關(guān)鍵字:
在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建 存放對(duì)象的內(nèi)存空間 線程共享
垃圾回收的主要場(chǎng)所
拋OutOfMemoryError異常
4.1. 什么是堆?
堆是用來(lái)存放對(duì)象的內(nèi)存空間。
幾乎所有的對(duì)象都存儲(chǔ)在堆中。
4.2. 堆的特點(diǎn)
- 線程共享
整個(gè)Java虛擬機(jī)只有一個(gè)堆,所有的線程都訪問同一個(gè)堆。而程序計(jì)數(shù)器、Java虛擬機(jī)棧、本地方法棧都是一個(gè)線程對(duì)應(yīng)一個(gè)的。 - 在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建
- 垃圾回收的主要場(chǎng)所。
- 可以進(jìn)一步細(xì)分為:新生代、老年代。
新生代又可被分為:Eden、From Survior、To Survior。
不同的區(qū)域存放具有不同生命周期的對(duì)象。這樣可以根據(jù)不同的區(qū)域使用不同的垃圾回收算法,從而更具有針對(duì)性,從而更高效。 - 堆的大小既可以固定也可以擴(kuò)展,但主流的虛擬機(jī)堆的大小是可擴(kuò)展的,因此當(dāng)線程請(qǐng)求分配內(nèi)存,但堆已滿,且內(nèi)存已滿無(wú)法再擴(kuò)展時(shí),就拋出OutOfMemoryError。
5. 方法區(qū)。
關(guān)鍵字:
方法區(qū)時(shí)堆中的邏輯部分 存放加載的類信息、常量、靜態(tài)變量、編譯后的代碼等
線程共享
內(nèi)存回收效率低
5.1. 什么是方法區(qū)?
Java虛擬機(jī)規(guī)范中定義方法區(qū)是堆的一個(gè)邏輯部分。
方法區(qū)中存放已經(jīng)被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等。
5.2. 方法區(qū)的特點(diǎn)
- 線程共享
方法區(qū)是堆的一個(gè)邏輯部分,因此和堆一樣,都是線程共享的。整個(gè)虛擬機(jī)中只有一個(gè)方法區(qū)。 - 永久代
方法區(qū)中的信息一般需要長(zhǎng)期存在,而且它又是堆的邏輯分區(qū),因此用堆的劃分方法,我們把方法區(qū)稱為老年代。 - 內(nèi)存回收效率低
方法區(qū)中的信息一般需要長(zhǎng)期存在,回收一遍內(nèi)存之后可能只有少量信息無(wú)效。
對(duì)方法區(qū)的內(nèi)存回收的主要目標(biāo)是:對(duì)常量池的回收 和 對(duì)類型的卸載。 - Java虛擬機(jī)規(guī)范對(duì)方法區(qū)的要求比較寬松。
和堆一樣,允許固定大小,也允許可擴(kuò)展的大小,還允許不實(shí)現(xiàn)垃圾回收。
5.3. 什么是運(yùn)行時(shí)常量池?
方法區(qū)中存放三種數(shù)據(jù):類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼。其中常量存儲(chǔ)在運(yùn)行時(shí)常量池中。
我們一般在一個(gè)類中通過public static final來(lái)聲明一個(gè)常量。這個(gè)類被編譯后便生成Class文件,這個(gè)類的所有信息都存儲(chǔ)在這個(gè)class文件中。
當(dāng)這個(gè)類被Java虛擬機(jī)加載后,class文件中的常量就存放在方法區(qū)的運(yùn)行時(shí)常量池中。而且在運(yùn)行期間,可以向常量池中添加新的常量。如:String類的intern()方法就能在運(yùn)行期間向常量池中添加字符串常量。
當(dāng)運(yùn)行時(shí)常量池中的某些常量沒有被對(duì)象引用,同時(shí)也沒有被變量引用,那么就需要垃圾收集器回收。
內(nèi)存模型
1 內(nèi)存間的交互操作
關(guān)于主內(nèi)存與工作內(nèi)存之間的具體協(xié)議,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存,如何從工作內(nèi)存同步到主內(nèi)存之間的實(shí)現(xiàn)細(xì)節(jié),Java內(nèi)存模型定義了一下八種操作來(lái)完成:
lock(鎖定):作用于主內(nèi)存的變量,把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占狀態(tài)。
unlock(解鎖):作用于主內(nèi)存變量,把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定。
read(讀取):作用于主內(nèi)存變量,把一個(gè)變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動(dòng)作使用
load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。
assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。
store(存儲(chǔ)):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write的操作。
write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存的變量中。
每一個(gè)線程有一個(gè)工作內(nèi)存。工作內(nèi)存和主存獨(dú)立。工作內(nèi)存存放主存中變量的值的拷貝。

當(dāng)數(shù)據(jù)從主內(nèi)存復(fù)制到工作內(nèi)存時(shí),必須出現(xiàn)兩個(gè)動(dòng)作:第一,由主內(nèi)存執(zhí)行的讀(read)操作;第二,由工作內(nèi)存執(zhí)行的相應(yīng)的load操作;
當(dāng)數(shù)據(jù)從工作內(nèi)存拷貝到主內(nèi)存時(shí),也出現(xiàn)兩個(gè)操作:第一個(gè),由工作內(nèi)存執(zhí)行的存儲(chǔ)(store)操作;第二,由主內(nèi)存執(zhí)行的相應(yīng)的寫(write)操作。
每一個(gè)操作都是原子的,即執(zhí)行期間不會(huì)被中斷
對(duì)于普通變量,一個(gè)線程中更新的值,不能馬上反應(yīng)在其他變量中。如果需要在其他線程中立即可見,需要使用volatile關(guān)鍵字作為標(biāo)識(shí)。

1、可見性:
一個(gè)線程修改了變量,其他線程可以立即知道
保證可見性的方法:
volatile:保持對(duì)內(nèi)存可見 防止重排序
synchronized (unlock之前,寫變量值回主存)
final(一旦初始化完成,其他線程就可見)
2、有序性:
在本線程內(nèi),操作都是有序的
在線程外觀察,操作都是無(wú)序的。(指令重排 或 主內(nèi)存同步延時(shí))
3、指令重排:
指令重排:破壞了線程間的有序性:
指令重排:保證有序性的方法:
指令重排的基本原則:
程序順序原則:一個(gè)線程內(nèi)保證語(yǔ)義的串行性
volatile規(guī)則:volatile變量的寫,先發(fā)生于讀
鎖規(guī)則:解鎖(unlock)必然發(fā)生在隨后的加鎖(lock)前
傳遞性:A先于B,B先于C 那么A必然先于C
線程的start方法先于它的每一個(gè)動(dòng)作
線程的所有操作先于線程的終結(jié)(Thread.join())
線程的中斷(interrupt())先于被中斷線程的代碼
對(duì)象的構(gòu)造函數(shù)執(zhí)行結(jié)束先于finalize()方法
jvm還有兩種執(zhí)行方式: 解釋執(zhí)行和編譯執(zhí)行
解釋執(zhí)行:
解釋執(zhí)行以解釋方式運(yùn)行字節(jié)碼
解釋執(zhí)行的意思是:讀一句執(zhí)行一句
編譯執(zhí)行(JIT):
將字節(jié)碼編譯成機(jī)器碼
直接執(zhí)行機(jī)器碼
運(yùn)行時(shí)編譯
編譯后性能有數(shù)量級(jí)的提升
編譯執(zhí)行的性能優(yōu)于解釋執(zhí)行,新版本的jvm默認(rèn)都是采用混合執(zhí)行模式


