您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關Java虛擬機中JVM內存結構是怎么樣的的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
一、JVM啟動流程:
JVM啟動時,是由java命令/javaw命令來啟動的。
二、JVM基本結構:
JVM基本結構圖:
《深入理解Java虛擬機(第二版)》中的描述是下面這個樣子的:
Java中的內存分配:
Java程序在運行時,需要在內存中的分配空間。為了提高運算效率,就對數據進行了不同空間的劃分,因為每一片區域都有特定的處理數據方式和內存管理方式。
具體劃分為如下5個內存空間:(非常重要)
棧:存放局部變量
堆:存放所有new出來的東西
方法區:被虛擬機加載的類信息、常量、靜態常量等。
程序計數器(和系統相關)
本地方法棧
1、程序計數器:
每個線程擁有一個PC寄存器
在線程創建時創建
指向下一條指令的地址
執行本地方法時,PC的值為undefined
2、方法區:
保存裝載的類信息
類型的常量池
字段,方法信息
方法字節碼
通常和永久區(Perm)關聯在一起
3、堆內存:
和程序開發密切相關
應用系統對象都保存在Java堆中
所有線程共享Java堆
對分代GC來說,堆也是分代的
GC管理的主要區域
現在的GC基本都采用分代收集算法,如果是分代的,那么堆也是分代的。如果堆是分代的,那堆空間應該是下面這個樣子:
上圖是堆的基本結構,在之后的文章中再進行詳解。
4、棧內存:
線程私有,生命周期和線程相同
棧由一系列幀組成(因此Java棧也叫做幀棧)
幀保存一個方法的局部變量、操作數棧、常量池指針
每一次方法調用創建一個幀,并壓棧
解釋:
Java虛擬機棧描述的是Java方法執行的內存模型:每個方法被調用的時候都會創建一個棧幀,用于存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程就對應著一個棧幀在虛擬機中從入棧到出棧的過程。
在Java虛擬機規范中,對這個區域規定了兩種異常情況:
(1)如果線程請求的棧深度太深,超出了虛擬機所允許的深度,就會出現StackOverFlowError(比如無限遞歸。因為每一層棧幀都占用一定空間,而 Xss 規定了棧的最大空間,超出這個值就會報錯)
(2)虛擬機棧可以動態擴展,如果擴展到無法申請足夠的內存空間,會出現OOM
4.1 Java棧之局部變量表:包含參數和局部變量
局部變量表存放了基本數據類型、對象引用和returnAddress類型(指向一條字節碼指令的地址)。其中64位長度的long和double類型的數據會占用2個局部變量空間(slot),其余數據類型只占用1個。局部變量表所需的內存空間在編譯期間完成分配。
例如,我寫出下面這段代碼:
1 package test03; 2 3 /** 4 * Created by smyhvae on 2015/8/15. 5 */ 6 public class StackDemo { 7 8 //靜態方法 9 public static int runStatic(int i, long l, float f, Object o, byte b) { 10 return 0; 11 } 12 13 //實例方法 14 public int runInstance(char c, short s, boolean b) { 15 return 0; 16 } 17 18 }
上方代碼中,靜態方法有6個形參,實例方法有3個形參。其對應的局部變量表如下:
上方表格中,靜態方法和實例方法對應的局部變量表基本類似。但有以下區別:實例方法的表中,第一個位置存放的是當前對象的引用。
4、2 Java棧之函數調用組成棧幀:
方法每次被調用的時候都會創建一個棧幀,例如下面這個方法:
public static int runStatic(int i,long l,float f,Object o ,byte b){ return runStatic(i,l,f,o,b); }
當它每次被調用的時候,都會創建一個幀,方法調用結束后,幀出棧。如下圖所示:
4.3 Java棧之操作數棧
Java沒有寄存器,所有參數傳遞都是使用操作數棧
例如下面這段代碼:
public static int add(int a,int b){ int c=0; c=a+b; return c; }
壓棧的步驟如下:
0: iconst_0 // 0壓棧
1: istore_2 // 彈出int,存放于局部變量2
2: iload_0 // 把局部變量0壓棧
3: iload_1 // 局部變量1壓棧
4: iadd //彈出2個變量,求和,結果壓棧
5: istore_2 //彈出結果,放于局部變量2
6: iload_2 //局部變量2壓棧
7: ireturn //返回
如果計算100+98的值,那么操作數棧的變化如下圖所示:
4.4 Java棧之棧上分配:
小對象(一般幾十個bytes),在沒有逃逸的情況下,可以直接分配在棧上
直接分配在棧上,可以自動回收,減輕GC壓力
大對象或者逃逸對象無法棧上分配
棧、堆、方法區交互:
三、內存模型:
每一個線程有一個工作內存。工作內存和主存獨立。工作內存存放主存中變量的值的拷貝。
當數據從主內存復制到工作存儲時,必須出現兩個動作:第一,由主內存執行的讀(read)操作;第二,由工作內存執行的相應的load操作;當數據從工作內存拷貝到主內存時,也出現兩個操作:第一個,由工作內存執行的存儲(store)操作;第二,由主內存執行的相應的寫(write)操作。
每一個操作都是原子的,即執行期間不會被中斷
對于普通變量,一個線程中更新的值,不能馬上反應在其他變量中。如果需要在其他線程中立即可見,需要使用volatile關鍵字作為標識。
1、可見性:
一個線程修改了變量,其他線程可以立即知道
保證可見性的方法:
volatile
synchronized (unlock之前,寫變量值回主存)
final(一旦初始化完成,其他線程就可見)
2、有序性:
在本線程內,操作都是有序的
在線程外觀察,操作都是無序的。(指令重排 或 主內存同步延時)
3、指令重排:
指令重排:破壞了線程間的有序性:
指令重排:保證有序性的方法:
指令重排的基本原則:
程序順序原則:一個線程內保證語義的串行性
volatile規則:volatile變量的寫,先發生于讀
鎖規則:解鎖(unlock)必然發生在隨后的加鎖(lock)前
傳遞性:A先于B,B先于C 那么A必然先于C
線程的start方法先于它的每一個動作
線程的所有操作先于線程的終結(Thread.join())
線程的中斷(interrupt())先于被中斷線程的代碼
對象的構造函數執行結束先于finalize()方法
四、解釋運行和編譯運行的概念:
解釋運行:
解釋執行以解釋方式運行字節碼
解釋執行的意思是:讀一句執行一句
編譯運行(JIT):
將字節碼編譯成機器碼
直接執行機器碼
運行時編譯
編譯后性能有數量級的提升
編譯運行的性能優于解釋運行。
感謝各位的閱讀!關于“Java虛擬機中JVM內存結構是怎么樣的”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。