jvm內存模型,java類從編譯到加載到執行的過程,jvm內存分配過程


一、jvm內存模型

JVM 內存模型主要分為堆、程序計數器、方法區、虛擬機棧和本地方法棧

1、堆

1.1、堆是 JVM 內存中最大的一塊內存空間。

1.2、該內存被所有線程共享,幾乎所有對象和數組都被分配到了堆內存中。

1.3、堆被划分為新生代和老年代,新生代又被進一步划分為 Eden 和 Survivor 區,最后 Survivor 由 From Survivor 和 To Survivor 組成。

2、程序計數器(Program Counter Register)

程序計數器是一塊很小的內存空間,用來記錄下一條運行的指令(實際是記錄各個線程執行的字節碼的地址,由於 Java 是多線程語言,當執行的線程數量超過 CPU 核數時,線程之間會根據時間片輪詢爭奪 CPU 資源。如果一個線程的時間片用完了,或者是其它原因導致這個線程的 CPU 資源被提前搶奪,那么這個退出的線程就需要單獨的一個程序計數器,來記錄下一條運行的指令。),例如,分支、循環、跳轉、異常、線程恢復等都依賴於計數器。

3、方法區(Method Area)

3.1、方法區!=永久代,根據虛擬機類型而定;HotSpot 虛擬機使用永久代來實現方法區,但在其它虛擬機中,例如,Oracle 的 JRockit、IBM 的 J9 就不存在永久代一說。

3.2、方法區主要是用來存放已被虛擬機加載的類相關信息,包括類信息(類的版本、字段、方法、接口和父類等信息)、運行時常量池、字符串常量。

ps1:JVM 在執行某個類的時候,必須經過加載、連接、初始化,而連接又包括驗證、准備、解析三個階段。在加載類的時候,JVM 會先加載 class 文件,而在 class 文件中除了有類的版本、字段、方法和接口等描述信息外,還有一項信息是常量池 (Constant Pool Table),用於存放編譯期間生成的各種字面量和符號引用()。

ps2:字面量包括字符串(String a=“b”)、基本類型的常量(final 修飾的變量)。

ps3:符號引用則包括類和方法的全限定名(例如 String 這個類,它的全限定名就是 Java/lang/String)、變量的名稱和描述符以及方法的名稱和描述符。

ps4:當類加載到內存中后,JVM 就會將 class 文件常量池中的內容存放到運行時的常量池中。

ps5:在解析階段,JVM 會把符號引用替換為直接引用(對象的索引值)。

3.3、方法區與堆空間類似,也是一個共享內存區,所以方法區是線程共享的。

3.2、在 Java6 版本中,永久代在非堆內存區;到了 Java7 版本,永久代的靜態變量和運行時常量池被合並到了堆中;而到了 Java8,永久代被元空間取代了,並且元空間的存儲位置是本地內存,永久代的靜態變量(class static variables)以及運行時常量池(runtime constant pool)則跟 Java7 一樣,轉移到了堆中。如下圖:

ps1:java8用直接內存元空間替代永久代的好處

1-1、移除永久代是為了融合 HotSpot JVM 與 JRockit VM 而做出的努力,因為 JRockit 沒有永久代,所以不需要配置永久代。

1-2、永久代內存經常不夠用或發生內存溢出,爆出異常 java.lang.OutOfMemoryError: PermGen。這是因為在 JDK1.7 版本中,指定的 PermGen 區大小為 8M,由於 PermGen 中類的元數據信息在每次 FullGC 的時候都可能被收集,回收率都偏低,成績很難令人滿意;還有,為 PermGen 分配多大的空間很難確定,PermSize 的大小依賴於很多因素,比如,JVM 加載的 class 總數、常量池的大小和方法的大小等。

4、虛擬機棧(VM stack)

4.1、Java 虛擬機棧是線程私有的內存空間,它和 Java 線程一起創建。

4.2、用來保存方法的局部變量、操作數棧、動態鏈接方法和返回地址等信息,並參與方法的調用和返回。每一個方法的調用都伴隨着棧幀的入棧操作,方法的返回則是棧幀的出棧操作。

5、本地方法棧(Native Method Stack)

5.1、本地方法棧跟 Java 虛擬機棧的功能類似,Java 虛擬機棧用於管理 Java 函數的調用,而本地方法棧則用於管理本地方法的調用。

5.2、本地方法並不是用 Java 實現的,而是由 C 語言實現的。

 

二、java類從編譯到加載到執行的過程

1、編譯

通過jdk自帶的javac工具完成

2、類加載

2.1、當一個類被創建實例或者被其它對象引用時,虛擬機在沒有加載過該類的情況下,會通過類加載器將字節碼文件加載到內存中。

2.2、不同的實現類由不同的類加載器加載,JDK 中的本地方法類一般由根加載器(Bootstrp loader)加載進來,JDK 中內部實現的擴展類一般由擴展加載器(ExtClassLoader )實現加載,而程序中的類文件則由系統加載器(AppClassLoader )實現加載。

2.3、在類加載后,class 類文件中的常量池信息以及其它數據會被保存到 JVM 內存的方法區中。

3、類連接

類在加載進來之后,會進行連接、初始化,最后才會被使用。在連接過程中,又包括驗證、准備和解析三個部分。

3.1、驗證

驗證類符合 Java 規范和 JVM 規范,在保證符合規范的前提下,避免危害虛擬機安全。

3.2、准備

為類的靜態變量分配內存,初始化為系統的初始值。對於 final static 修飾的變量,直接賦值為用戶的定義值。例如,private final static int value=123,會在准備階段分配內存,並初始化值為 123,而如果是 private static int value=123,這個階段 value 的值仍然為 0。

3.3、解析

將符號引用轉為直接引用的過程。我們知道,在編譯時,Java 類並不知道所引用的類的實際地址,因此只能使用符號引用來代替。類結構文件的常量池中存儲了符號引用,包括類和接口的全限定名、類引用、方法引用以及成員變量引用等。如果要使用這些類和方法,就需要把它們轉化為 JVM 可以直接獲取的內存地址或指針,即直接引用。

3.4、類初始化

編譯器會在將 .java 文件編譯成 .class 文件時,收集所有類初始化代碼,包括靜態變量賦值語句、靜態代碼塊、靜態方法,收集在一起成為 <clinit>() 方法。在這個階段中,JVM 首先將執行構造器 <clinit> 方法

初始化類的靜態變量和靜態代碼塊為用戶自定義的值,初始化的順序和 Java 源碼從上到下的順序一致。例如:

private static int i=1;
static{
  i=0;
}
public static void main(String [] args){
  System.out.println(i);
}

運行結果為:0

子類初始化時會首先調用父類的 <clinit>() 方法,再執行子類的 <clinit>() 方法,運行以下代碼:public class Parent{

public static String parentStr= "parent static string"; static{ System.out.println("parent static fields"); System.out.println(parentStr); } public Parent(){ System.out.println("parent instance initialization"); } } public class Sub extends Parent{ public static String subStr= "sub static string"; static{ System.out.println("sub static fields"); System.out.println(subStr); } public Sub(){ System.out.println("sub instance initialization"); } public static void main(String[] args){ System.out.println("sub main"); new Sub(); } }


運行結果為:

parent static fields
parent static string
sub static fields
sub static string
sub main
parent instance initialization
sub instance initialization

JVM 會保證 <clinit>() 方法的線程安全,保證同一時間只有一個線程執行。

JVM 在初始化執行代碼時,如果實例化一個新對象,會調用 <init> 方法對實例變量進行初始化,並執行對應的構造方法內的代碼。

類初始化完后就可以正式使用了,比如創建對象等。

4、運行時編譯

見:https://www.cnblogs.com/jetqiu/p/11669168.html

 

三、jvm內存分配過程

1、JVM 根據配置配置或默認參數向操作系統申請內存空間

2、JVM 獲得內存空間后,會根據配置參數分配堆、棧以及方法區的內存大小

3、class 文件加載、驗證、准備以及解析,其中准備階段會為類的靜態變量分配內存,初始化為系統的初始值

4、使用過程中內存分配


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM