jvm運行時內存是怎麼分佈的?

Java內存模型

基礎知識。

存儲器的結構(摘自深刻理解計算機基礎第三版P421)

image.png

寄存器中的內存最小速度最快,硬盤容量最大,速度最小,cup的第三級緩存是共享內存。 java

image.png

對於一個在同一行的數據XY,會被同時加載進CPU,這個現象叫cache line緩存行對齊,緩存行對齊具體內容能夠看看這篇文章若是左邊的CPU核心加載了X並進行修改,可是此時並無將數據寫回進主內存,或者是第三級緩存,此時右邊的CPU核心將X讀進,並修改,此時就會產生數據不一致的狀況。對於左邊修改了數據右邊如何才能知道,這是硬件層級須要解決的。 多線程一致性的硬件支持有一下集中方式。數組

  1. 鎖總線的方式。總線鎖會鎖住bus總線,使得其餘CPU不能訪問內存中的其餘地址,於是效率極低。CPU的共享內存都是經過 緩存

    image.png

  2. MESI(英特爾CPU) 等等之類的緩存一致性協議,相對於總線鎖的方式MESI的性能好不少。bash

    1. M: 被修改(Modified) 當一個值被加載到一個CPU內存中,同時這個CPU修改了這個值,則用Modified來表示,覺得着在將來某個時刻會將這個值寫入進主存。
    2. E: 獨享的(Exclusive) 只有當前的CPU才能加載這個數據,就屬於Exclusive獨享的值。
    3. S: 共享的(Shared) 當多個CPU讀取了同一個值,則用Shared來表示。
    4. I: 無效的(Invalid) 當前CPU讀取的值已經被修改過了,則這個值是無效的用Invalid表示,覺得這將來某個時刻會從主存中讀取這個值。
  3. 在特定狀況下CPU會進行指令重排,以保證高效的執行。CPU如何保證特定的狀況下不不亂序執行。經過內存屏障來保證指令的有序性。多線程

    下面是三種CPU指令內存屏障指令。jvm

    1. sfence:在sfence指令前的寫操做必須在sfence後寫操做完成以前完成操做。即在兩次寫操做之間加個屏障使其不能被重排。性能

    2. lfence:在lfence指令前的讀操做必須在lfence後的讀操做完成以前完成操做。即在兩次讀操做之間加個屏障使其不能被重排。spa

    3. mfence:在mfence指令前的讀寫操做必須在mfence的讀寫操做完成以前完成操做。即在一次讀一次寫和另外一次讀和一次寫之間加一個屏障使其不能被重排。線程

      CPU保證有序執行除了可使用mfence,還可使用3d

    jvm內存屏障,對上面的CPU內存屏障進行組合,有以下四種屏障

    1. loadload屏障,對於這樣的語句Load1; LoadLoad; Load2, 在Load2及後續讀取操做要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
    2. storestore屏障,對於這樣的語句Store1; StoreStore; Store2,在Store2及後續寫入操做執行前,保證Store1的寫入操做對其它處理器可見。
    3. loadstore屏障,對於這樣的語句Load1; LoadStore; Store2,在Store2及後續寫入操做被刷出前,保證Load1要讀取的數據被讀取完畢。
    4. storeload屏障,對於這樣的語句Store1; StoreLoad; Load2,在Load2及後續全部讀取操做執行前,保證Store1的寫入對全部處理器可見。

Runtime Data Area and Instruction Set

jvms 2.4 2.5

指令集分類

  1. 基於寄存器的指令集
  2. 基於棧的指令集 Hotspot中的Local Variable Table = JVM中的寄存器

Runtime Data Area

PC 程序計數器 Program Counter

Each Java Virtual Machine thread has its own pc (program counter) register.

每一個Java虛擬機線程都有本身的程序計數器的存儲空間。

At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method for that thread.

在任什麼時候候,每一個Java虛擬機線程都在執行單個方法的代碼,即該線程的當前方法。

If that method is not native , the pc register contains the address of the Java Virtual Machine instruction currently being executed.

若是該方法不是本機方法,則pc寄存器包含當前正在執行的Java虛擬機指令的地址。

存放指令位置

虛擬機的運行,相似於這樣的循環:

while( not end ) {

取PC中的位置,找到對應位置的指令;

執行該指令;

PC ++;
}
複製代碼

JVM Stack

Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread.

每一個Java虛擬機線程都有一個私有Java虛擬機堆棧,與該線程同時建立。

A Java Virtual Machine stack stores frames

每一個Java虛擬機存儲的都是棧幀

  1. Frame - 每一個方法對應一個棧幀
    1. Local Variable Table 局部變量
    2. Operand Stack 操做數堆 當一個方法剛剛開始執行時,其操做數棧是空的,隨着方法執行和字節碼指令的執行,會從局部變量表或對象實例的字段中複製常量或變量寫入到操做數棧,再隨着計算的進行將棧中元素出棧到局部變量表或者返回給方法調用者,也就是出棧/入棧操做。一個完整的方法執行期間每每包含多個這樣出棧/入棧的過程。
    3. Dynamic Linking 動態連接 所謂動態連接就是指向運行時常量池的那個連接,而後看看指向的那個連接有沒有解析,若是已經解析直接拿過來使用,沒有解析嘗試進行解析。代表這個方式叫什麼,什麼類型等等信息。好比在a()方法中調用了一個b()方法,在a的應用確定有個b()可是b的具體內容在哪裏是dynamic linking去獲取。
    4. return address a() -> b(),方法a調用了方法b, b方法的返回值放在什麼地方

Heap

The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads.

java 虛擬機有個全部線程共享的堆內存。

The heap is the run-time data area from which memory for all class instances and arrays is allocated.

堆是運行時數據區,從中分配全部類實例和數組的內存。

Method Area

The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads.

Java虛擬機具備一個在全部Java虛擬機線程之間共享的方法區域。

It stores per-class structures

存儲每一個類的結構

  1. Perm Space (<1.8) 字符串常量位於PermSpace FGC不會清理 大小啓動的時候指定,不能變
  2. Meta Space (>=1.8) 字符串常量位於堆 會觸發FGC清理 不設定的話,最大就是物理內存

Runtime Constant Pool

A run-time constant pool is a per-class or per-interface run-time representation of the constant_ pool table ina class file

Native Method Stack

An implementation of the Java Virtual Machine may use conventional stacks called native method stacks

Direct Memory

JVM能夠直接訪問的內核空間的內存 (OS 管理的內存)

NIO , 提升效率,實現zero copy

上面的內容總結起來就是下面這張圖

image.png

每一個線程都有本身的棧內存,裏面存放的是棧幀,若是有調用native方法就還存在native method stack,還有本身program counter程序計數器,用來記錄每條指令的執行,當CPU切換線程的時候方便知道以前線程執行到哪條指令,全部線程共享堆內存和方法區,方法區有兩種實現,在jdk8以前方法區叫PermGen永久代,jdk8及其之後的版本都叫Meta Space 元數據區。

棧幀:A frame is used to store data and partial results, as well as to perform dynamic linking, return values for methods, and dispatch exceptions.

棧幀是用來存儲數據部分結果,和執行動態連接,返回方法的值和處理異常調度。

image.png

咱們看一下一個簡單的方法的執行過程的棧幀是怎麼樣的。

pubilc class Test1{
  pubilc static void main(String [] args){
   Test h = new Test();
    h.m1();
  }
}

pubilc class Test{
  public void m1(){
    int i = 200;
  }
}
複製代碼

編譯後的Test1字節碼爲:

0 new #2 <com/yhx/Test>
3 dup
4 invokespcial #3<com/yhx/Test<init>>
7 istore_1
8 iload_1
9 invokespcial #3<com/yhx/Test<m1>>
12 return
複製代碼

對應Test字節碼爲:

0 ipush 200
3 istore_1
4 return
複製代碼

image.png
我咱們看一下執行過程,在含有主方法的字節碼中,首先執行一個new指令。new一個對象的話,包括三個操做:

  1. 首先開闢一個內存空間放在棧幀中,此時就包含了一個Dynamic Linking 動態連接的過程要把 Test h = new Test()中的Test類對應成這個類內存地址;
  2. 而後給這個對象的成員變量付出初始值(不是默認值);
  3. 最後初始化給成員變量一個默認值(init)。

咱們看上面指令,在這條 invokespcial #3<com/yhx/Test>也就是初始化以前執行了一個dup指令,也就是上面初始化對象的是哪一個流程執行完第二個流程,此時這個對象的成員變量的值若是是引用型的則仍是爲null,這個dup指令是將main方法棧幀中Operand Stacks的(如上圖)h複製一份,而後出棧,init操做拿着出棧後的值進行初始化操做,這個時候棧內的對象引用就變爲初始化以後的了。 接下來就是「istore_1」變量1就是h,將初始化好的內存引用賦值給h,而後「iload_1」把變量1加載進本地變量表。

相關文章
相關標籤/搜索