詳解Java 虛擬機器(第①篇)——執行時資料區域

無敵天驕發表於2020-04-11

一、程式計數器(Program Counter Register)

  • 當前執行緒所執行的位元組碼行號指示器(邏輯)
  • 通過改變計數器的值來選取下一條需要執行的位元組碼指令
  • 和執行緒一對一的關係,即“執行緒私有”
  • 對 Java 方法計數,如果是 Native 方法則計數器值為 Undefined
  • 只是計數,不會發生記憶體洩漏

二、Java 虛擬機器棧

每個 Java 方法在執行的同時會建立一個棧幀用於儲存區域性變數表、運算元棧、常量池引用等資訊。從方法呼叫直至執行完成的過程,就對應著一個棧幀在 Java 虛擬機器棧中入棧和出棧的過程。

可以通過 -Xss 這個虛擬機器引數來指定每個執行緒的 Java 虛擬機器棧記憶體大小:

java -Xss512M HackTheJava

該區域可能丟擲以下異常:

  • 當執行緒請求的棧深度超過最大值,會丟擲 StackOverflowError 異常;
  • 棧進行動態擴充套件時如果無法申請到足夠記憶體,會丟擲 OutOfMemoryError 異常。

區域性變數表和運算元棧

  • 區域性變數表:包含方法執行過程中的所有變數
  • 運算元棧:入棧、出棧、複製、交換、產生消費變數
    public class JVMTest {
      public static int add(int a ,int b) {
          int c = 0;
          c = a + b;
          return c;
      }
    }
    
javap -verbose JVMTest

三、本地方法棧

本地方法棧與 Java 虛擬機器棧類似,它們之間的區別只不過是本地方法棧為本地方法服務。

本地方法一般是用其它語言(C、C++ 或組合語言等)編寫的,並且被編譯為基於本機硬體和作業系統的程式,對待這些方法需要特別處理。

四、堆

所有物件都在這裡分配記憶體,是垃圾收集的主要區域(”GC 堆”)。

現代的垃圾收集器基本都是採用分代收集演算法,其主要的思想是針對不同型別的物件採取不同的垃圾回收演算法。可以將堆分成兩塊:

  • 新生代(Young Generation)
  • 老年代(Old Generation)

堆不需要連續記憶體,並且可以動態增加其記憶體,增加失敗會丟擲 OutOfMemoryError 異常。

可以通過 -Xms 和 -Xmx 這兩個虛擬機器引數來指定一個程式的堆記憶體大小,第一個引數設定初始值,第二個引數設定最大值。

java -Xms1M -Xmx2M HackTheJava

1. Java 記憶體分配策略

  • 靜態儲存:編譯時確定每個資料目標在執行時的儲存空間需求
  • 棧式儲存:資料區需求在編譯時未知,執行時模組入口前確定
  • 堆式儲存:編譯時或執行時模組入口都無法確定,動態分配

2. 問題一:堆和棧的聯絡

引用物件、陣列時,棧裡定義變數儲存堆中目標的首地址。

3. 問題二:棧和堆的區別

①. 實體地址

  • 堆的實體記憶體分配是不連續的;
  • 棧的實體記憶體分配是連續的

②. 分配記憶體

  • 堆是不連續的,分配的記憶體是在執行期確定的,大小不固定;
  • 棧是連續的,分配的記憶體在編譯器就已經確定,大小固定

③. 存放內容

  • 堆中存放的是物件和陣列,關注的是資料的儲存;
  • 棧中存放區域性變數,關注的是程式方法的執行

④. 是否執行緒私有

  • 堆記憶體中的物件對所有執行緒可見,可被所有執行緒訪問;
  • 棧記憶體屬於某個執行緒私有的

⑤. 異常

  • 棧擴充套件失敗,會丟擲 StackOverflowError;
  • 堆記憶體不足,會丟擲 OutOfMemoryError

五、方法區

用於存放已被載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。

和堆一樣不需要連續的記憶體,並且可以動態擴充套件,動態擴充套件失敗一樣會丟擲 OutOfMemoryError 異常。

對這塊區域進行垃圾回收的主要目標是對常量池的回收和對類的解除安裝,但是一般比較難實現。

HotSpot 虛擬機器把它當成永久代來進行垃圾回收。但很難確定永久代的大小,因為它受到很多因素影響,並且每次 Full GC 之後永久代的大小都會改變,所以經常會丟擲 OutOfMemoryError 異常。為了更容易管理方法區,從 JDK 1.8 開始,移除永久代,並把方法區移至元空間,它位於本地記憶體中,而不是虛擬機器記憶體中。

方法區是一個 JVM 規範,永久代與元空間都是其一種實現方式。在 JDK 1.8 之後,原來永久代的資料被分到了堆和元空間中。元空間儲存類的元資訊,靜態變數和常量池等放入堆中。

1. 元空間(MetaSpace)與永久代(PermGen)的區別

元空間使用本地記憶體,而永久代使用 JVM 的記憶體。

2. 元空間(MetaSpace)相比永久代(PermGen)的優勢

  • 字串常量池存在永久代中,容易出現效能問題和記憶體溢位
  • 類和方法的資訊大小難以確定,給永久代的大小指定帶來困難
  • 永久代會為 GC 帶來不必要的複雜性

六、執行時常量池

執行時常量池是方法區的一部分。

Class 檔案中的常量池(編譯器生成的字面量和符號引用)會在類載入後被放入這個區域。

除了在編譯期生成的常量,還允許動態生成,例如 String 類的 intern()。

直接記憶體

在 JDK 1.4 中新引入了 NIO 類,它可以使用 Native 函式庫直接分配堆外記憶體,然後通過 Java 堆裡的 DirectByteBuffer 物件作為這塊記憶體的引用進行操作。這樣能在一些場景中顯著提高效能,因為避免了在堆記憶體和堆外記憶體來回拷貝資料。

七、JVM常見引數

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69964492/viewspace-2685653/,如需轉載,請註明出處,否則將追究法律責任。

相關文章