1. 程式人生 > >Java虛擬機器詳解----JVM記憶體結構

Java虛擬機器詳解----JVM記憶體結構

http://www.cnblogs.com/smyhvae/p/4748392.htm

 


主要內容如下:

  • JVM啟動流程
  • JVM基本結構
  • 記憶體模型
  • 編譯和解釋執行的概念

 

一、JVM啟動流程:

587f1e97-d4ed-4d7f-89c6-dabe944fed12

JVM啟動時,是由java命令/javaw命令來啟動的。

二、JVM基本結構:

JVM基本結構圖:

16865f32-a87b-4fee-9aed-a793016cf2a7

《深入理解Java虛擬機器(第二版)》中的描述是下面這個樣子的:

96698196-0f6b-4ef6-9a20-85dd35d75f27

 

Java中的記憶體分配:

Java程式在執行時,需要在記憶體中的分配空間。為了提高運算效率,就對資料進行了不同空間的劃分,因為每一片區域都有特定的處理資料方式和記憶體管理方式。

具體劃分為如下5個記憶體空間:(非常重要)

  • 棧:存放區域性變數
  • 堆:存放所有new出來的東西
  • 方法區:被虛擬機器載入的類資訊、常量、靜態常量等。
  • 程式計數器(和系統相關)
  • 本地方法棧

1、程式計數器:

每個執行緒擁有一個PC暫存器

線上程建立時建立

指向下一條指令的地址

執行本地方法時,PC的值為undefined

2、方法區: 

儲存裝載的類資訊

  型別的常量池

  欄位,方法資訊

  方法位元組碼

通常和永久區(Perm)關聯在一起

3、堆記憶體:

和程式開發密切相關

應用系統物件都儲存在Java堆中

所有執行緒共享Java堆

對分代GC來說,堆也是分代的

GC管理的主要區域

現在的GC基本都採用分代收集演算法,如果是分代的,那麼堆也是分代的。如果堆是分代的,那堆空間應該是下面這個樣子:

8d74ba35-9865-4434-8be5-73cec9e93295

上圖是堆的基本結構,在之後的文章中再進行詳解。

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個形參。其對應的區域性變量表如下:

1272c229-ef70-4898-9ede-66828e6b84f7

上方表格中,靜態方法和例項方法對應的區域性變量表基本類似。但有以下區別:例項方法的表中,第一個位置存放的是當前物件的引用。

 

4、2  Java棧之函式呼叫組成棧幀:

方法每次被呼叫的時候都會建立一個棧幀,例如下面這個方法:

public static int runStatic(int i,long l,float  f,Object o ,byte b){
       return runStatic(i,l,f,o,b);
}

 

當它每次被呼叫的時候,都會建立一個幀,方法呼叫結束後,幀出棧。如下圖所示:

1244af96-c081-4059-98de-7881ec4c792e

 

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的值,那麼運算元棧的變化如下圖所示:

35721707-fff7-4516-86a7-dda30320dc06

 

 

4.4  Java棧之棧上分配:

小物件(一般幾十個bytes),在沒有逃逸的情況下,可以直接分配在棧上

直接分配在棧上,可以自動回收,減輕GC壓力

大物件或者逃逸物件無法棧上分配

棧、堆、方法區互動:

7efdd9ad-03a3-41e8-9635-36d5506a23f9f29029fe-8cef-47ea-b091-e3f19d6a9801

 

 

三、記憶體模型:

每一個執行緒有一個工作記憶體。工作記憶體和主存獨立。工作記憶體存放主存中變數的值的拷貝。

7883501b-7e5c-4064-af0d-438a118acbf6

當資料從主記憶體複製到工作儲存時,必須出現兩個動作:第一,由主記憶體執行的讀(read)操作;第二,由工作記憶體執行的相應的load操作;當資料從工作記憶體拷貝到主記憶體時,也出現兩個操作:第一個,由工作記憶體執行的儲存(store)操作;第二,由主記憶體執行的相應的寫(write)操作。

每一個操作都是原子的,即執行期間不會被中斷

對於普通變數,一個執行緒中更新的值,不能馬上反應在其他變數中。如果需要在其他執行緒中立即可見,需要使用volatile關鍵字作為標識。

d481032e-ed23-46c2-9c5c-5f34e3193ef9

1、可見性:

  一個執行緒修改了變數,其他執行緒可以立即知道

保證可見性的方法:

volatile

synchronized (unlock之前,寫變數值回主存)

final(一旦初始化完成,其他執行緒就可見)

2、有序性:

  在本執行緒內,操作都是有序的

  線上程外觀察,操作都是無序的。(指令重排 或 主記憶體同步延時)

3、指令重排:

142d481b-f4a3-4c23-b56d-cf3ec5b63a2e

指令重排:破壞了執行緒間的有序性:

154748eb-cc14-48ee-b66f-cdcc26068cd1

 

 

指令重排:保證有序性的方法:

e0bcca41-a6e1-4a5c-b723-e0d721875acc

指令重排的基本原則:

程式順序原則:一個執行緒內保證語義的序列性

volatile規則:volatile變數的寫,先發生於讀

鎖規則:解鎖(unlock)必然發生在隨後的加鎖(lock)前

傳遞性:A先於B,B先於C 那麼A必然先於C

執行緒的start方法先於它的每一個動作

執行緒的所有操作先於執行緒的終結(Thread.join())

執行緒的中斷(interrupt())先於被中斷執行緒的程式碼

物件的建構函式執行結束先於finalize()方法

 

四、解釋執行和編譯執行的概念:

解釋執行:

解釋執行以解釋方式執行位元組碼

解釋執行的意思是:讀一句執行一句

編譯執行(JIT):

將位元組碼編譯成機器碼

直接執行機器碼

執行時編譯

編譯後效能有數量級的提升

編譯執行的效能優於解釋執行。