1. 程式人生 > >Java記憶體區域與HotSpot虛擬機器物件

Java記憶體區域與HotSpot虛擬機器物件

執行時資料區域

這裡寫圖片描述

程式計數器(Program Counter Register)

程式計數器是一塊很小的記憶體空間,可以看做是當前執行緒所執行的位元組碼的行號指示器,儲存著當前執行的位元組碼指令,通過改變這裡的值來進行指令的呼叫。
在多執行緒中每個執行緒執行的命令都不相同且互不影響,所以程式計數器是執行緒私有的,這類記憶體區域“執行緒私有”的記憶體。
如果執行緒正在執行的是一個Java方法,這個計數器儲存的是正在執行的虛擬機器位元組碼指令的地址;如果執行的本地方法(Native方法),則為空(Unfefined)。

Java虛擬機器棧(Java Virtual Machine Stacks)

Java虛擬機器棧是描述Java方法執行的記憶體模型,所以跟程式計數器一樣也是執行緒私有的。每個方法在執行的同時都會建立一個棧幀(Stack Frame)用於儲存區域性變數、運算元棧、動態連結、方法出口等資訊。每一個方法的呼叫直至執行完成的過程,就對應著一個棧幀從入棧到出棧的過程。
如果方法在執行時需要呼叫另一個方法,則將另一個方法壓入棧頂,執行棧頂方法,執行完畢之後將返回值返回到呼叫者那裡。
這裡寫圖片描述

棧幀(Stack Frame)

每一個棧幀都包括了區域性變量表、運算元棧、動態連結、方法返回地址和一些其它資訊。在將Java程式碼編譯成Class檔案的時候就已經確定了局部變量表的大小和運算元棧的深度,並且寫入到了方法表中Code屬性中。
對於執行引擎來說,在活動執行緒中,只有位於棧頂的棧幀才是有效的,稱為當前棧幀(Current Stack Frame),與這個棧幀相關聯的方法稱為當前方法(Current Method)。
這裡寫圖片描述

區域性變量表(Local Variable Table)

區域性變量表是一組變數值儲存空間,用於存放方法引數和方法內部定義的變數。在Java程式編譯成Class檔案的時候,會將區域性變量表需要分配的大小寫入Class檔案中方法表裡的Code屬性的max_locals資料項中。
區域性變量表的容量以變數槽(Variable Slot)為最小單位,每個Slot都可以存放一個boolean、byte、char、int、float、reference或returnAddress型別的資料,其中reference型別表示對一個物件例項的引用。
虛擬機器通過索引定位的方式使用區域性變量表,索引值的範圍是從0開始到區域性變量表最大容量,如果執行的是例項方法,那區域性變量表中第0位Slot預設是用於儲存方法所屬物件例項的引用。
為了儘可能的節省棧幀空間,區域性變量表中的Slot是可以重用的。由於方法體中的變數並不一定都會覆蓋整個方法體,有的變數會在方法中途失去作用,這個時候該變數所佔用的Slot就會被新的變數所佔據。就跟坐火車一樣,同一個位置在不同路段總會有人上上下下。

運算元棧(Operand Stack)

運算元棧也成為操作棧,它是一個後入先出(Last In First Out)棧,如名字所說,就是用來進行一些指令操作的。通區域性變數一樣,運算元棧的最大深度也是在Java程式編譯成Class檔案的時候就已經確定了的,其最大深度會被寫入Class檔案中的方法表裡的Code屬性的max_stacks資料項中。
當一個方法剛剛開始執行的時候,這個方法的運算元棧是空的,在方法的執行過程中會有各種位元組碼指令往運算元棧中寫入和提取內容,也就是出棧/入棧操作。

動態連結(Dynamic Linking)

每個棧幀都包含一個指向執行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法呼叫過程中的動態連結。在上一篇Java Class檔案結構中說到Class檔案中的常量池中存有大量的符號引用,位元組碼中的方法呼叫指令就一常量池中指向方法的符號引用作為引數。這些符號引用一部分會在類載入階段或第一次使用的時候就轉化為直接引用,這種轉化成為靜態解析。另外一部分將在每一次執行期間轉化為直接引用,這部分成為動態連結。關於這兩部分會在以後詳解。

方法返回地址

當一個方法開始執行後,只有兩種方式可以退出這個方法。第一種方式是執行引擎遇到任意一個方法返回的位元組碼指令。另一種方式是在方法執行過程中出現異常。
無論是何種方式退出,在方法退出之後都需要返回到方法被呼叫的位置,程式才能繼續執行。方法退出就是將當前棧幀出棧,所以需要恢復上層方法的區域性變量表和運算元棧,把返回值(如果有)壓入呼叫者棧幀的運算元棧中,調整PC計數器的指令值指向方法呼叫指令後面的一條指令。

本地方法棧(Native Method Stack)

與Java虛擬機器棧作用相似,區別為Java虛擬機器棧執行Java方法,本地方法棧執行本地方法(Native Method)。

java堆(Java Heap)

對大多數應用老說,Java堆是Java虛擬機器所管理的記憶體中最大的一塊。Java堆是被所有執行緒共享的一塊區域,在虛擬機器啟動的時候建立,此區域唯一的目的就是存放物件例項,幾乎所有物件例項都在這裡分配記憶體。
Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱作“GC堆”(Garbage Collected Heap)。從記憶體回收的角度看,由於現在收集器基本上都採用分代收集演算法,所以Java堆還可以分為:新生代和老年代;再細分一下,新生代還可以分為:Eden空間、From Survivor空間和To Survivor空間。

方法區(Method Area)

方法區與Java堆一樣,也是執行緒共享區域,用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。
對於HotSpot虛擬機器來說,方法區還被稱為“永久代(Permanent Generation)”。
方法區中有一塊區域叫做執行時常量池(Runtime Constant Pool),上一篇文章Java Class檔案結構中講到的Class檔案中的常量池就是被載入到這裡的。

位元組碼指令表

這裡寫圖片描述
這裡寫圖片描述
- invokevirtual指令用於呼叫物件的例項方法,根據物件的實際型別進行分派。
- invokeinterface指令用於呼叫介面方法,它會在執行時搜尋一個實現了這個介面方法的物件,找出適合的方法進行呼叫。
- invokespecial指令用於呼叫一些需要特殊處理的例項方法,包括例項初始化方法、私有方法和父類方法。
- invokestatic用於呼叫靜態方法。
- invokedynamic指令用於在執行時動態解析出呼叫點限定符所引用的方法,並執行該方法,前面四條呼叫指令的分派邏輯都固化在Java虛擬機器內部,而invokedynamic指令的分派邏輯由使用者所設定的引導方法決定的。
更多的指令請Google搜尋或者閱讀書籍,這些指令夠閱讀下面的例子了。
放上上次的Java程式碼和它的Class檔案,應該能夠閱讀了。

package com.overridere.six;

public class Test {

    public static void main(String[] args) {
        TestClass tc = new TestClass();
        System.out.println(tc.print());
    }
}

javap命令解析的Class檔案:

public class com.overridere.six.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/overridere/six/TestClass
   #2 = Utf8               com/overridere/six/TestClass
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Methodref          #3.#11         // java/lang/Object."<init>":()V
  #11 = NameAndType        #7:#8          // "<init>":()V
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/overridere/six/TestClass;
  #16 = Utf8               print
  #17 = Utf8               ()I
  #18 = Fieldref           #1.#19         // com/overridere/six/TestClass.m:I
  #19 = NameAndType        #5:#6          // m:I
  #20 = Utf8               SourceFile
  #21 = Utf8               TestClass.java
{
  public com.overridere.six.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #10                 // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/overridere/six/TestClass;

  public int print();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #18                 // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/overridere/six/TestClass;
}

HotSpot虛擬機器物件

物件的建立

堆記憶體的分配方式:
指標碰撞
假設Java堆中記憶體是絕對規整的,所有用過的記憶體都放在一邊,空閒的記憶體放在另一邊,中間放著一個指標作為分界點的指示器,那所分配記憶體就僅僅是把那個指標向空閒那邊挪動一段對物件大小相等的距離,這種分配方式稱為“指標碰撞(Bump the Pointer)”。
空閒列表
如果Java堆中的記憶體並不是規整的,已使用的記憶體和空閒的記憶體相互交錯,虛擬機器就會維護一個列表,記錄上哪些記憶體塊是可用的,再分配的時候從列表中找到一塊足夠大的空間劃分給物件例項,並更新列表上的記錄,這種分配方式稱為“空閒列表(Free List)”。

記憶體分配的併發解決方案:
一種是對分配記憶體空間的動作進行同步處理——實際上虛擬機器採用CAS配上失敗重試的方式保證更新操作的原子性;
另一種是把記憶體分配按照執行緒劃分在不同的空間之中進行,即每個執行緒在Java堆中預先分配一小塊記憶體,稱為本地執行緒分配緩衝(Thread Local Allocation Buffer,TLAB)。

記憶體分配完成之後,虛擬機器需要將分配到的記憶體空間都初始化為零值(不包括物件頭)。

接下來虛擬機器要對物件進行必要的設定,例如這個物件是哪個類的例項、如何能找到類的元資料資訊、物件的雜湊碼、物件的GC分代年齡等資訊。

以上步驟完成了虛擬機器視角上的物件建立,Java程式視角上來看,還需要執行init方法。

物件的記憶體佈局

在HotSpot虛擬機器中,物件在記憶體中儲存的佈局可以分為3塊區域:物件頭(Header)、例項資料(Instance Data)和對齊填充(Padding)。

物件頭
HotSpot虛擬機器物件頭包括兩部分資訊,第一部分用於儲存物件自身的執行時資料如雜湊碼(HashCode)、GC分代年齡、鎖狀態標識、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等,這部分資料的長度在32位和64位的虛擬機器中分別為32bit和64bit,官方稱它為“Mark Word”。

物件頭的另一部分是型別指標,即物件指向它的類元資料的指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項。

例項資料
例項資料部分是物件真正儲存的有效資訊,也是在程式程式碼中所定義的各種型別的欄位內容。無論是從父類繼承下來的,還是在子類中定義的,都需要記錄下來。

對齊填充部分並不是必然存在的,也沒有特別含義,僅僅起著佔位符的作用。

物件的訪問定位

目前主流的訪問物件方式有使用控制代碼和直接指標兩種。

  • 如果使用控制代碼的話,那麼Java堆中將會劃分出一塊記憶體來作為控制代碼池,reference中儲存的就是物件的控制代碼地址,而控制代碼中包含了物件例項資料與型別資料各自的具體地址資訊,如下圖所示:
    這裡寫圖片描述
  • 如果使用直接指標訪問,那麼Java堆物件的佈局中就必須考慮如何放置訪問型別資料的相關資訊,而reference中儲存的直接就是物件地址,如下圖所示:
    這裡寫圖片描述

使用控制代碼訪問的最大好處就是reference中儲存的是穩定的控制代碼地址,在物件被移動時只會改變控制代碼中的例項資料指標,而reference本身不需要修改。

使用直接指標訪問的最大好處就是速度更快。

以上內容參考自《深入理解Java虛擬機器——JVM高階特性與最佳實踐(第2版)》第二章,第八章第二節。

相關推薦

深入理解Java虛擬機器讀書筆記1----Java記憶體區域HotSpot虛擬機器物件

一 Java記憶體區域與HotSpot虛擬機器物件 1 Java技術體系、JDK、JRE?     Java技術體系包括:         · Java程式設計語言;   

Java記憶體區域HotSpot虛擬機器物件

執行時資料區域 程式計數器(Program Counter Register) 程式計數器是一塊很小的記憶體空間,可以看做是當前執行緒所執行的位元組碼的行號指示器,儲存著當前執行的位元組碼指令,通過改變這裡的值來進行指令的呼叫。 在多執行緒中每個執

Java虛擬機器學習筆記(一):記憶體區域HotSpot虛擬機器物件探祕

執行時資料區域 Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨著虛擬機器程序的啟動而存在,有些區域則依賴使用者執行緒的啟動和結束而建立和銷燬。根據《Java虛擬機

JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域記憶體溢位異常 之 虛擬機器物件

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。 JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 虛擬機器物件 Java物件的建立 1、類載入過程

自動記憶體管理機制(1)- java記憶體區域虛擬機器物件

自動記憶體管理機制(1)- java記憶體區域與虛擬機器物件 1. 執行時資料區域 Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。有的區域隨著虛擬機器進行的啟動而存在,有些區域則以來使用者執行緒的啟動和結束而建立和銷燬。 有以下幾個區域

java虛擬機器—-java記憶體區域記憶體溢位異常

一,java虛擬機器所管理的執行時資料區域分為:程式計數器、java虛擬機器棧、本地方法棧、java堆、方法區、執行時常量池。                     1,程式計數器:

《深入理解Java虛擬機器—JVM高階特性實踐 周志明 著》之第2章 Java記憶體區域記憶體溢位異常

1、Java虛擬機器所管理的記憶體包括以下幾個執行時資料區域: 2、程式計數器:          1. 可以看作是當前執行緒所執行的位元組碼的行號指示器,是一塊較小的記憶體空間;  &nbs

JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域記憶體溢位異常 之 執行時資料區域

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。 JVM劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 執行時資料區域 概述 執行時資料區域 程式計數器 Java虛擬機

《深入理解java虛擬機器》讀書筆記(二)---- Java記憶體區域記憶體溢位異常

執行時資料區域 java虛擬機器所管理的記憶體將會包括以下幾個執行時資料區域: 1、程式計數器 程式計數器是一塊較小的記憶體空間,它可以看作是當前執行緒所執行位元組碼的行號指示器。在虛擬機器的概念模型裡,位元組碼直譯器的工作就是通過改變這個計數器的值來選取下一條需要執

虛擬機器學習之一:java記憶體區域記憶體溢位異常

1.執行時資料區域 java虛擬機器在執行java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。這些區域都有各自的用途和建立、銷燬時間,有的區域伴隨虛擬機器程序的啟動而存在,有些區域則依賴使用者執行緒的啟動和結束而建立和銷燬。 1.1程式計數器 程式計數器

【深入Java虛擬機器】之一:Java記憶體區域記憶體溢位

記憶體區域     Java虛擬機器在執行Java程式的過程中會把他所管理的記憶體劃分為若干個不同的資料區域。Java虛擬機器規範將JVM所管理的記憶體分為以下幾個執行時資料區:程式計數器、Java虛擬機器棧、本地方法棧、Java堆、方法區。下面詳細闡述各資料區所儲存的資料型

深入理解java虛擬機器---java記憶體區域記憶體溢位異常---3垃圾回收機制GC

  一、垃圾回收---物件存活演算法:     1、引用計數器法:在物件身上放上一個計數器,當有引用則加一,引用失效則減一,為零則可回收。(無法解決物件相互引用)     2、可達性分析法(java),GC roots為起始點,從節點向下搜尋,搜尋路徑為引用鏈,不在引用鏈的物件則是可回收的物件

深入理解java虛擬機器-第二章:java記憶體區域記憶體洩露異常

2.1概述:   java將記憶體的管理(主要是回收工作),交由jvm管理,確實很省事,但是一點jvm因記憶體出現問題,排查起來將會很困難,為了能夠成為獨當一面的大牛呢,自然要了解vm是怎麼去使用記憶體的。 2.2執行時的資料區域   vm會將管理的記憶體劃分為不同的區域,不同的區域間有各自的用途,以及建立和

深入理解java虛擬機器系列(一):java記憶體區域記憶體溢位異常

文章主要是閱讀《深入理解java虛擬機器:JVM高階特性與最佳實踐》第二章:Java記憶體區域與記憶體溢位異常 的一些筆記以及概括。 好了開始。如果有什麼錯誤或者遺漏,歡迎指出。 一、概述 先上一張圖 這張圖主要列出了Java虛擬機器管理的記憶體的幾個區域。 常有人

【深入Java虛擬機器(1)】:Java記憶體區域記憶體溢位

記憶體區域 Java虛擬機器在執行Java程式的過程中會把他所管理的記憶體劃分為若干個不同的資料區域。Java虛擬機器規範將JVM所管理的記憶體分為以下幾個執行時資料區:程式計數器、Java虛擬機器棧、本地方法棧、Java堆、方法區。下面詳細闡述各資料區所儲存的

《深入理解Java虛擬機器》讀書筆記:第二章Java記憶體區域記憶體溢位異常

Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域:方法區、虛擬機器棧、本地方法棧、堆、程式計數器 程式計數器(ProgramCounterRegister):一塊較小的記憶體空間,看作當前執行緒所執行的位元組碼的行號指示器;位元組碼

深入Java虛擬機器筆記(一):Java記憶體區域記憶體溢位異常

1、程式計數器為很小的記憶體空間,為當前執行緒執行的位元組碼的行號指示器,通過改變計數器的值來選取下一條需要執行的位元組碼指令,迴圈、分支等基礎功能都是需要計數器來完成的 2、Java虛擬機器棧為Java方法執行的記憶體模型,每個方法被執行時都會同時建立棧

Java記憶體區域虛擬機器類載入機制

一、Java執行時資料區域 1、程式計數器  “執行緒私有”的記憶體,是一個較小的記憶體空間,它

《深入理解java虛擬機器》讀書筆記2(java記憶體區域OOM)

1.java執行時記憶體劃分 》程式計數器 學過彙編的童鞋都知道程式執行時會記錄當前執行的位置,以便確認接下來執行什麼。這裡的程式計數器就是用來儲存當前執行緒所執行位元組碼的行號指示器,也就是地址,位元組碼指示器通過改變程式計數器的值來指定下一條執行

java虛擬機器java記憶體區域記憶體溢位異常

本文參考自《深入理解Java虛擬機器》一書。主要總結一下java虛擬機器記憶體的各個區域,以及這些區域的作用、服務物件以及其中可能產生的問題。 ##1. 執行時資料區域   java虛擬機器在執行ja