天天看點

Java虛拟機詳解02----JVM記憶體結構

主要内容如下:

jvm啟動流程

jvm基本結構

記憶體模型

編譯和解釋運作的概念

一、jvm啟動流程:

Java虛拟機詳解02----JVM記憶體結構

jvm啟動時,是由java指令/javaw指令來啟動的。

二、jvm基本結構:

jvm基本結構圖:

Java虛拟機詳解02----JVM記憶體結構

《深入了解java虛拟機(第二版)》中的描述是下面這個樣子的:

Java虛拟機詳解02----JVM記憶體結構

java中的記憶體配置設定:

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

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

棧:存放局部變量

堆:存放所有new出來的東西

方法區:被虛拟機加載的類資訊、常量、靜态常量等。

程式計數器(和系統相關)

本地方法棧

1、程式計數器:

每個線程擁有一個pc寄存器 線上程建立時建立 指向下一條指令的位址 執行本地方法時,pc的值為undefined

2、方法區: 

儲存裝載的類資訊   類型的常量池   字段,方法資訊   方法位元組碼 通常和永久區(perm)關聯在一起

3、堆記憶體:

和程式開發密切相關 應用系統對象都儲存在java堆中 所有線程共享java堆 對分代gc來說,堆也是分代的 gc管理的主要區域

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

Java虛拟機詳解02----JVM記憶體結構

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

4、棧記憶體:

線程私有,生命周期和線程相同

棧由一系列幀組成(是以java棧也叫做幀棧)

幀儲存一個方法的局部變量、操作數棧、常量池指針

每一次方法調用建立一個幀,并壓棧

解釋:

java虛拟機棧描述的是java方法執行的記憶體模型:每個方法被調用的時候都會建立一個棧幀,用于存儲局部變量表、操作棧、動态連結、方法出口等資訊。每一個方法被調用直至執行完成的過程就對應着一個棧幀在虛拟機中從入棧到出棧的過程。

在java虛拟機規範中,對這個區域規定了兩種異常情況:

    (1)如果線程請求的棧深度太深,超出了虛拟機所允許的深度,就會出現stackoverflowerror(比如無限遞歸。因為每一層棧幀都占用一定空間,而 xss 規定了棧的最大空間,超出這個值就會報錯)

    (2)虛拟機棧可以動态擴充,如果擴充到無法申請足夠的記憶體空間,會出現oom

4.1  java棧之局部變量表:包含參數和局部變量

    局部變量表存放了基本資料類型、對象引用和returnaddress類型(指向一條位元組碼指令的位址)。其中64位長度的long和double類型的資料會占用2個局部變量空間(slot),其餘資料類型隻占用1個。局部變量表所需的記憶體空間在編譯期間完成配置設定。

例如,我寫出下面這段代碼:

Java虛拟機詳解02----JVM記憶體結構
Java虛拟機詳解02----JVM記憶體結構

上方代碼中,靜态方法有6個形參,執行個體方法有3個形參。其對應的局部變量表如下:

Java虛拟機詳解02----JVM記憶體結構

上方表格中,靜态方法和執行個體方法對應的局部變量表基本類似。但有以下差別:執行個體方法的表中,第一個位置存放的是目前對象的引用。

4、2  java棧之函數調用組成棧幀:

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

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

Java虛拟機詳解02----JVM記憶體結構

4.3  java棧之操作數棧

java沒有寄存器,所有參數傳遞都是使用操作數棧

例如下面這段代碼:

壓棧的步驟如下:

  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的值,那麼操作數棧的變化如下圖所示:

Java虛拟機詳解02----JVM記憶體結構

4.4  java棧之棧上配置設定:

小對象(一般幾十個bytes),在沒有逃逸的情況下,可以直接配置設定在棧上 直接配置設定在棧上,可以自動回收,減輕gc壓力 大對象或者逃逸對象無法棧上配置設定

棧、堆、方法區互動:

Java虛拟機詳解02----JVM記憶體結構

三、記憶體模型:

每一個線程有一個工作記憶體。工作記憶體和主存獨立。工作記憶體存放主存中變量的值的拷貝。

Java虛拟機詳解02----JVM記憶體結構

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

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

對于普通變量,一個線程中更新的值,不能馬上反應在其他變量中。如果需要在其他線程中立即可見,需要使用volatile關鍵字作為辨別。

Java虛拟機詳解02----JVM記憶體結構

1、可見性:

  一個線程修改了變量,其他線程可以立即知道

保證可見性的方法:

volatile synchronized (unlock之前,寫變量值回主存) final(一旦初始化完成,其他線程就可見)

2、有序性:

  在本線程内,操作都是有序的

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

3、指令重排:

Java虛拟機詳解02----JVM記憶體結構

指令重排:破壞了線程間的有序性:

Java虛拟機詳解02----JVM記憶體結構

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

Java虛拟機詳解02----JVM記憶體結構

指令重排的基本原則:

程式順序原則:一個線程内保證語義的串行性 volatile規則:volatile變量的寫,先發生于讀 鎖規則:解鎖(unlock)必然發生在随後的加鎖(lock)前 傳遞性:a先于b,b先于c 那麼a必然先于c 線程的start方法先于它的每一個動作 線程的所有操作先于線程的終結(thread.join()) 線程的中斷(interrupt())先于被中斷線程的代碼 對象的構造函數執行結束先于finalize()方法

四、解釋運作和編譯運作的概念:

解釋運作:

解釋執行以解釋方式運作位元組碼 解釋執行的意思是:讀一句執行一句

編譯運作(jit):

将位元組碼編譯成機器碼 直接執行機器碼 運作時編譯 編譯後性能有數量級的提升

編譯運作的性能優于解釋運作。