JVM基礎結構與位元組碼執行引擎
- 2020 年 4 月 10 日
- 筆記
JVM基礎結構
JVM內部結構如下:棧、堆。

棧
JVM中的棧主要是指執行緒裡面的棧,裡面有方法棧、native方法棧、PC暫存器等等;每個方法棧是由棧幀組成的;每個棧幀是由局部變數表、操作數棧等組成。
每個棧幀其實就代表一個方法
堆
java中所有對象都在堆中分配;堆中對象又分為年輕代、老年代等等,不同代的對象使用不同垃圾回收演算法。
-XMs:啟動虛擬機預留的記憶體
-Xmx:最大的堆記憶體
一、堆的分代假設
根據研究表明,堆中對象大部分都是創建後,立馬就可以被銷毀的。如:

為了優化堆中的記憶體,將堆中對象分為不同代。在年輕代中,GC發生比較頻繁;在老年代中,GC發生比較少。
二、堆的分代
- 年輕代:
Young Generation - 老年代:
Old Generation/Tenured - 永久代:
Permanent Generation
永久代在
Java虛擬機規範中是沒有的,但是Host Spot虛擬機中有。
三、方法區
方法區被所有執行緒共享;方法區是用來存儲編譯後的程式碼,即存儲每個類的運行時常量池、欄位和方法。
方法區在虛擬機啟動時創建;雖然方法區在邏輯上是堆的一部分,但在一些簡單的實現中,方法區可以選擇不進行垃圾回收和緊湊化。
方法區在java8的變化
java7之前:方法區的實現:永久代,是作為堆的一部分;java8之後:方法區的實現:metaspace,是堆外的記憶體;
1、為什麼要這樣改變?
因為java可以動態載入位元組碼資訊,這樣方法區就會慢慢的擠占堆中記憶體。為了避免與堆爭搶記憶體,java8將方法區的實現移至堆外。
2、方法區、永久代、MetaSpace的區別?
方法區是java虛擬機規範所規定的一個概念。其中java7實現方法區的地方稱為永久代;java8實現方法區的地方稱為MetaSpace
位元組碼文件的結構
java程式在運行的時候,將源碼編譯成位元組碼,位元組碼在不同系統上的JVM翻譯成對應的機器碼。這是Java平台無關性的基礎。

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

1、如何查看位元組碼文件
classpy工具IDEA的jclasslib Bytecode viewer插件
2、位元組碼文件結構
一個位元組碼文件包含以下部分:

(1)magic:0xCAFEBABE
class文件的magic code,用於標識該文件是class文件。
(2)minor_version、major_version
用於標識該class文件的版本,防止高版本的class文件被低版本的JVM讀取並執行。
(3)constant_pool:常量池
用於存儲該class文件經常被使用的資訊,優化記憶體。比如說System.out.print()
(4)access_flag
表示這個類得訪問許可權,對應到java源碼就是public、final之類的
位元組碼執行引擎
這裡以一個執行緒為例。一般來說,一個方法棧最底層的棧幀都是Thread.run方法。當一個執行緒準備調用另一個方法時,會先將實參拷貝一份到新棧幀的局部變數表裡,然後再執行程式碼。
1、局部變數表
每次調用新方法時,會默認將當前對象的地址this作為局部變數表的第一個參數;後面存放傳過來的參數。這與javascript的做法很相似。
2、方法調用的相關指令
invokevirtual:一般實例方法,有多態;invokeinterface:介面方法,有多態invokestatuc:靜態方法,無多態invokespecial:特殊方法,無多態invokedynamic:動態調用,JDK7新增,方法無需在編譯時確定
3、方法調用的過程
(1)在開始時
- 方法棧新增一個棧幀;
- 實例方法的
this、參數放到局部變數表中; - 開始新棧幀中位元組碼的執行;
(2)在返回時
- 將返回值放在調用者方法棧幀中的操作數棧上;
(3)在異常出現時
- 尋找匹配的異常處理程式碼
(4)在finally時
- 為每個分支新增一個跳轉
4、為什麼Mockito、EasyMock無法對private、static方法進行mock?
因為他們mock方法是通過覆蓋這些方法來實現的,而private、static沒法被覆蓋。PowerMock是通過修改位元組碼文件達到mock私有、靜態方法的。

