JVM基礎結構與位元組碼執行引擎

  • 2020 年 4 月 10 日
  • 筆記

JVM基礎結構

JVM內部結構如下:棧、堆。
file

JVM中的棧主要是指執行緒裡面的棧,裡面有方法棧、native方法棧、PC暫存器等等;每個方法棧是由棧幀組成的;每個棧幀是由局部變數表、操作數棧等組成。

每個棧幀其實就代表一個方法

java中所有對象都在堆中分配;堆中對象又分為年輕代、老年代等等,不同代的對象使用不同垃圾回收演算法。

-XMs:啟動虛擬機預留的記憶體
-Xmx:最大的堆記憶體

一、堆的分代假設

根據研究表明,堆中對象大部分都是創建後,立馬就可以被銷毀的。如:
Plumbr_Handbook_Java_Garbage_Collection

為了優化堆中的記憶體,將堆中對象分為不同代。在年輕代中,GC發生比較頻繁;在老年代中,GC發生比較少。

二、堆的分代

  • 年輕代:Young Generation
  • 老年代:Old Generation/Tenured
  • 永久代:Permanent Generation

永久代在Java虛擬機規範中是沒有的,但是Host Spot虛擬機中有。
file

三、方法區

方法區被所有執行緒共享;方法區是用來存儲編譯後的程式碼,即存儲每個類的運行時常量池、欄位和方法。
方法區在虛擬機啟動時創建;雖然方法區在邏輯上是堆的一部分,但在一些簡單的實現中,方法區可以選擇不進行垃圾回收和緊湊化。

方法區在java8的變化

  • java7之前:方法區的實現:永久代,是作為堆的一部分;
  • java8之後:方法區的實現:metaspace,是堆外的記憶體;

1、為什麼要這樣改變?
因為java可以動態載入位元組碼資訊,這樣方法區就會慢慢的擠占堆中記憶體。為了避免與堆爭搶記憶體,java8將方法區的實現移至堆外。
2、方法區、永久代、MetaSpace的區別?
方法區是java虛擬機規範所規定的一個概念。其中java7實現方法區的地方稱為永久代;java8實現方法區的地方稱為MetaSpace


位元組碼文件的結構

java程式在運行的時候,將源碼編譯成位元組碼,位元組碼在不同系統上的JVM翻譯成對應的機器碼。這是Java平台無關性的基礎
file

但是,編譯後的位元組碼是如何讀取到JVM中的?位元組碼執行引擎是如何識別、執行指令?
file

1、如何查看位元組碼文件

  • classpy工具
  • IDEAjclasslib Bytecode viewer插件

2、位元組碼文件結構
一個位元組碼文件包含以下部分:
file
(1)magic:0xCAFEBABE
class文件的magic code,用於標識該文件是class文件。

(2)minor_versionmajor_version
用於標識該class文件的版本,防止高版本的class文件被低版本的JVM讀取並執行。

(3)constant_pool:常量池
用於存儲該class文件經常被使用的資訊,優化記憶體。比如說System.out.print()

(4)access_flag
表示這個類得訪問許可權,對應到java源碼就是publicfinal之類的


位元組碼執行引擎

這裡以一個執行緒為例。一般來說,一個方法棧最底層的棧幀都是Thread.run方法。當一個執行緒準備調用另一個方法時,會先將實參拷貝一份到新棧幀的局部變數表裡,然後再執行程式碼。
1、局部變數表
每次調用新方法時,會默認將當前對象的地址this作為局部變數表的第一個參數;後面存放傳過來的參數。這與javascript的做法很相似。

2、方法調用的相關指令

  • invokevirtual:一般實例方法,有多態;
  • invokeinterface:介面方法,有多態
  • invokestatuc:靜態方法,無多態
  • invokespecial:特殊方法,無多態
  • invokedynamic:動態調用,JDK7新增,方法無需在編譯時確定

3、方法調用的過程
(1)在開始時

  • 方法棧新增一個棧幀;
  • 實例方法的this、參數放到局部變數表中;
  • 開始新棧幀中位元組碼的執行;

(2)在返回時

  • 將返回值放在調用者方法棧幀中的操作數棧上;

(3)在異常出現時

  • 尋找匹配的異常處理程式碼

(4)在finally

  • 為每個分支新增一個跳轉

4、為什麼MockitoEasyMock無法對privatestatic方法進行mock
因為他們mock方法是通過覆蓋這些方法來實現的,而privatestatic沒法被覆蓋。PowerMock是通過修改位元組碼文件達到mock私有、靜態方法的。

原部落格地址