1. 程式人生 > >java復習基礎篇—-JVM內存結構(轉)

java復習基礎篇—-JVM內存結構(轉)

java棧 每一個 重要 分享圖片 info 創建 表格 href object

主要內容如下:

  • JVM啟動流程
  • 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):

將字節碼編譯成機器碼

直接執行機器碼

運行時編譯

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

編譯運行的性能優於解釋運行。

轉自大佬:http://www.cnblogs.com/smyhvae/p/4748392.htm

java復習基礎篇—-JVM內存結構(轉)