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
私有、靜態方法的。