[TOC]
## 1 概述
javac 將語法樹輸出成字節碼,通過虛擬機執行時,可以達到折疊語法樹結點的目的。
## 2 運行時棧幀結構
Java 虛擬機以方法作為最基本的執行單元,棧幀(Stack Frame)則是用于支持虛擬機進行方法調用和方法執行背后的數據結構,也是虛擬機運行時數據區中的虛擬機棧(Virtual Machine Stack)的棧元素。
- 棧幀存儲了方法的局部變量表,操作數棧,動態連接和方法返回地址等信息,一個棧幀需要分配多少內存,并不會受到程序運行期變量數據的影響,而僅僅取決于程序源碼和具體的虛擬機實現的棧內存布局形式。
- 同一時刻、同一條線程里面,在調用堆棧的所有方法都同時處于執行執行狀態,而在執行引擎中,在活動線程里,只有位于棧頂的方法才是在運行的,位于棧頂的棧幀被稱為當前棧幀(Current Stack Frame),與這個棧幀所關聯的方法被稱為當前方法(Current Method)
### 2.1 局部變量表
局部變量表(Local Variables Table)是一組變量值的存儲空間,用于存放方法參數和方法內部定義的局部變量,在方法的Code屬性的max_locals數據項中確定了該方法所需分配的局部變量表的最大容量。
局部變量表的容量以變量槽(variable Slot)為最小單位,Java虛擬機規范中說到每個變量槽都應該能存放一個boolean、byte、char、short、int、float、reference或returnAddress類型的數據。
reference表示對一個對象實例的引用,需要支持兩件事:
- 根據引用直接或間接查找到對象在Java堆中的數據存放的起始地址或索引
- 根據引用直接或間接地查找對象所屬數據類型在方法區的存儲的類型信息
returnAddress目前已經很少見了,是為字節碼jsr、jsr_w、ret服務的,指向了一條字節碼指令的地址,某些古老的虛擬機用來實現異常處理時的跳轉,現在已經全部改為異常表了
當一個方法被調用時,虛擬機會使用局部變量表來完成實參到形參的傳遞
- 如果執行的是實例方法,那布局變量表中的第0位索引的變量槽默認是用于傳遞方法所屬對象實例的引用,在方法中可以通過關鍵字this來訪問到,其余參數則按照參數表順序排列,占用從1開始的局部變量槽,參數表分配完畢后,再根據方法體內部定義的變量順序和作用域分配其余的變量槽
- 為了盡可能節省棧幀耗用的內存空間,局部變量表中的變量槽是可以重用的
> 示例一
```java
byte[] placeholder = new byte[64 * 1024 * 1024];
System.gc(); // 未回收
```
> 示例二
```java
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
System.gc(); // 未回收
```
> 示例三
```java
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
int a = 0; // 變量槽復用
System.gc(); // 回收
```
假設前面占用了很大內存,而后面的方法又比較耗時,可以手動將局部變量設置為null;但是,一般不推薦手動將局部變量設置為null
### 2.2 操作數棧
操作數棧(Operand Stack)也常被稱為操作棧,32位數據元素所占的棧容量為1,64位的為2,javac的數據流分析工作保證了在方法執行時任何時候,操作數棧的深度都不會超過在max_stack數據項中設定的最大值。
### 2.3 動態連接
每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態連接(Dynamic Linking),Class文件的常量池中存有大量的符號引用,字節碼中的方法調用指令時就以常量池里面指向方法的符號引用作為參數,這些符號引用一部分會在類加載階段或者第一次使用的時候就被轉化為直接引用,這種轉化被稱為靜態解析。另一部分將在每一次運行期間都轉化為直接引用,這部分就稱為動態連接。
### 2.4 方法返回地址
一個方法被執行后,只有兩種方式退出,一種是正常調用完成(Normal Method Invocation Completion),一種是遇到異常沒有被catch,這種方式不會返回任何值,被稱為異常調用完成(Abrupt Method Invocation Completion)
### 2.5 附加信息
附加信息包括與調試、性能相關的信息,完全取決于虛擬機實現,一般會把動態連接,方法返回地址附加信息歸為一類,稱為棧幀信息。
## 3 方法調用
方法調用不等于方法被執行,其唯一的任務就是確定被調用的是哪一個方法。
### 3.1 解析
所有方法調用的目標方法在Class文件中都是一個常量池的符號引用,在類加載的解析階段,會將其中一部分符號引用轉化為直接引用,這種解析能成立的前提是:方法在程序真正運行之前就有一個可確定的版本,并且這個方法的調用版本在運行期是不可改變的。這些方法的調用被稱為解析(Resolution),符號`編譯期可知,運行期不可變`的方法,主要有靜態方法和私有方法兩大類,前者與類型直接關聯,后者在外部不可被訪問,這兩種方法各自的特點決定了他們不可能通過繼承或別的方式重寫出其他版本(性能優化點),因此他們都適合在類加載階段進行解析。
Java 虛擬機支持以下5種方法調用字節碼指令:
- invokestatic:用于調用靜態方法
- invokespecial:用于調用實例構造器方法`<init>()`、私有方法和父類中的方法
- invokevirtual:用于調用所有的虛方法
- invokeinterface:用于調用接口方法,會在運行時再確定一個實現該接口的對象
- invokedynamic:先在運行時動態解析出調用點限定符所引用的方法,然后再執行該方法
能被invokestatic和invokespecial指令調用的方法,都可以在解析階段中確定唯一的調用版本,Java語言里符合這個條件的方法共有:
- 靜態方法
- 私有方法
- 實例構造器
- 父類方法
- final修飾的方法(盡管它使用invokevirtual指令調用)
統稱為非虛方法(Non-Virtual Method),并且,Java語言規范中明確定義了被final修飾的方法是一種非虛方法
解析調用一定是一個靜態的過程,在編譯期完全確定;而另一種調用形式:分派(Dispatch)調用則要復雜許多,它可能是靜態的也可能是動態的,一共有4種:
- 靜態單分派
- 靜態多分派
- 動態單分派
- 動態多分派
### 3.2 靜態分派
> 重載方法靜態分派演示
```java
/**
* 靜態分派的示例
*/
public class Test {
static abstract class A {
}
static class B extends A {
}
static class C extends A {
}
public void echo(A a) {
System.out.println("A");
}
public void echo(B b) {
System.out.println("B");
}
public void echo(C c) {
System.out.println("C");
}
public static void main(String[] args) {
A b = new B();
A c = new C();
Test test = new Test();
test.echo(b); // A
test.echo(c); // A
}
}
```
在以上代碼中,A被稱為變量的靜態類型(Static Type)或者外觀類型(Apparent Type),B被稱為變量的實際類型(Actual Type)或者運行時類型(Runtime Type),
static 需要編譯期確定,對于重載(Overload)方法,將選擇靜態分派,在方法調用時選擇靜態類型。例如以下方法,編譯期不可知,因此定為A類型
```java
A d = new Random().nextBoolean() ? new B() : new C();
test.echo(d); // A
```
----
重載方法的優先級選擇也是編譯期通過靜態分派完成的。
> 一個關于重載方法匹配優先級的例子
```java
import java.io.Serializable;
/**
* 靜態分派的示例
*/
public class Test {
/** 0 優先級最高,直接匹配 */
static void echo(char arg) {
System.out.println("char");
}
/** 1 char > int */
static void echo(int arg) {
System.out.println("int");
}
/** 2 int > long */
static void echo(long arg) {
System.out.println("long");
}
/** 3 long > float */
static void echo(float arg) {
System.out.println("float");
}
/** 4 float > double */
static void echo(double arg) {
System.out.println("double");
}
/** 5 原始類型找盡,找char的包裝類 */
static void echo(Character arg) {
System.out.println("Character");
}
/** 6 Character的接口,如果再寫一個Comparable類型,由于優先級相同,會導致編譯失敗 */
static void echo(Serializable arg) {
System.out.println("Serializable");
}
/** 7 父類 */
static void echo(Object arg) {
System.out.println("Object");
}
/** 8 變長參數類型,優先級最低 */
static void echo(char... arg) {
System.out.println("char...");
}
/** 重載方法優先級選擇的測試程序 */
public static void main(String[] args) {
echo('a');
}
}
```
### 3.3 動態分派
動態分派的實現與重寫(Override)有著密切的關聯。
> 動態分派的示例
```java
package vm;
import java.io.Serializable;
/**
* 靜態分派的示例
*/
public class Test {
static abstract class A {
protected abstract void echo();
}
static class B extends A {
@Override
protected void echo() {
System.out.println("B");
}
}
static class C extends A {
@Override
protected void echo() {
System.out.println("C");
}
}
public static void main(String[] args) {
A b = new B();
A c = new C();
b.echo(); // B
c.echo(); // C
b = new C();
b.echo(); // C
}
}
/*
public static void main(java.lang.String[]);
Code:
0: new #2 // class vm/Test$B
3: dup
4: invokespecial #3 // Method vm/Test$B."<init>":()V
7: astore_1
8: new #4 // class vm/Test$C
11: dup
12: invokespecial #5 // Method vm/Test$C."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #6 // Method vm/Test$A.echo:()V
20: aload_2
21: invokevirtual #6 // Method vm/Test$A.echo:()V
24: new #4 // class vm/Test$C
27: dup
28: invokespecial #5 // Method vm/Test$C."<init>":()V
31: astore_1
32: aload_1
33: invokevirtual #6 // Method vm/Test$A.echo:()V
36: return
*/
```
第0~15行是準備動作,建立了b和c的內存空間、調用了B和C類型的實例構造器,將這兩個實例的引用存放在第1、2個局部變量表的變量槽中,對應了以下兩行:
```java
A b = new B();
A c = new C();
```
在第16~21行中,第16行和第20行的aload指令分別把剛剛創建的兩個對象的引用壓到棧頂,這兩個對象是將要執行的echo方法的所有者,稱為接受者(Receiver);第17行和第21行是方法調用指令,雖然相同,但是可以通過invokevirtual指令來分析,其運行時解析過程分為 :
- 找到操作數棧頂的第一個元素所指向的對象的運行時類型(Runtime Type),記作C
- 如果在類型C中找到與常量中的描述符和簡單名稱都相符的方法,則進行訪問權限校驗,如果通過則返回這個方法的直接引用,查找過程結束;不通過則拋出java.lang.IllegalAccessError異常
- 否則,按照繼承關系從下往上依次對C的各個父類進行第二步的搜索和驗證過程。
- 如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常
這個過程就是Java方法重寫(Override)的本質
----
字段沒有多態性,雖然子類的內存中會存在父類中的同名字段,但是子類的字段會遮蔽父類中的同名字段。
**字段沒有多態性**
```java
package vm;
/**
* 字段沒有多態性,子類會遮蔽父類的同名字段
*/
public class Test {
static class A {
public int a = 1;
public A() {
a = 2;
echo();
}
void echo() {
System.out.println("A#a " + a);
}
}
static class B extends A {
public int a = 3;
public B() {
a = 4;
echo();
}
@Override
void echo() {
System.out.println("B#a " + a);
}
}
public static void main(String[] args) {
A a = new B();
System.out.println("a.a " + a.a);
// 父類初始化 虛方法調用,子類echo(子類的字段b)方法,訪問子類的字段 輸出 B#a 0
// 子類初始化 虛方法調用,子類echo(子類的字段b)方法,訪問子類的字段 輸出 B#a 4
// 靜態調用 訪問父類字段 輸出 a.a 2
}
}
```
- 空白目錄
- 精簡版Spring的實現
- 0 前言
- 1 注冊和獲取bean
- 2 抽象工廠實例化bean
- 3 注入bean屬性
- 4 通過XML配置beanFactory
- 5 將bean注入到bean
- 6 加入應用程序上下文
- 7 JDK動態代理實現的方法攔截器
- 8 加入切入點和aspectj
- 9 自動創建AOP代理
- Redis原理
- 1 Redis簡介與構建
- 1.1 什么是Redis
- 1.2 構建Redis
- 1.3 源碼結構
- 2 Redis數據結構與對象
- 2.1 簡單動態字符串
- 2.1.1 sds的結構
- 2.1.2 sds與C字符串的區別
- 2.1.3 sds主要操作的API
- 2.2 雙向鏈表
- 2.2.1 adlist的結構
- 2.2.2 adlist和listNode的API
- 2.3 字典
- 2.3.1 字典的結構
- 2.3.2 哈希算法
- 2.3.3 解決鍵沖突
- 2.3.4 rehash
- 2.3.5 字典的API
- 2.4 跳躍表
- 2.4.1 跳躍表的結構
- 2.4.2 跳躍表的API
- 2.5 整數集合
- 2.5.1 整數集合的結構
- 2.5.2 整數集合的API
- 2.6 壓縮列表
- 2.6.1 壓縮列表的結構
- 2.6.2 壓縮列表結點的結構
- 2.6.3 連鎖更新
- 2.6.4 壓縮列表API
- 2.7 對象
- 2.7.1 類型
- 2.7.2 編碼和底層實現
- 2.7.3 字符串對象
- 2.7.4 列表對象
- 2.7.5 哈希對象
- 2.7.6 集合對象
- 2.7.7 有序集合對象
- 2.7.8 類型檢查與命令多態
- 2.7.9 內存回收
- 2.7.10 對象共享
- 2.7.11 對象空轉時長
- 3 單機數據庫的實現
- 3.1 數據庫
- 3.1.1 服務端中的數據庫
- 3.1.2 切換數據庫
- 3.1.3 數據庫鍵空間
- 3.1.4 過期鍵的處理
- 3.1.5 數據庫通知
- 3.2 RDB持久化
- 操作系統
- 2021-01-08 Linux I/O 操作
- 2021-03-01 Linux 進程控制
- 2021-03-01 Linux 進程通信
- 2021-06-11 Linux 性能優化
- 2021-06-18 性能指標
- 2022-05-05 Android 系統源碼閱讀筆記
- Java基礎
- 2020-07-18 Java 前端編譯與優化
- 2020-07-28 Java 虛擬機類加載機制
- 2020-09-11 Java 語法規則
- 2020-09-28 Java 虛擬機字節碼執行引擎
- 2020-11-09 class 文件結構
- 2020-12-08 Java 內存模型
- 2021-09-06 Java 并發包
- 代碼性能
- 2020-12-03 Java 字符串代碼性能
- 2021-01-02 ASM 運行時增強技術
- 理解Unsafe
- Java 8
- 1 行為參數化
- 1.1 行為參數化的實現原理
- 1.2 Java 8中的行為參數化
- 1.3 行為參數化 - 排序
- 1.4 行為參數化 - 線程
- 1.5 泛型實現的行為參數化
- 1.6 小結
- 2 Lambda表達式
- 2.1 Lambda表達式的組成
- 2.2 函數式接口
- 2.2.1 Predicate
- 2.2.2 Consumer
- 2.2.3 Function
- 2.2.4 函數式接口列表
- 2.3 方法引用
- 2.3.1 方法引用的類別
- 2.3.2 構造函數引用
- 2.4 復合方法
- 2.4.1 Comparator復合
- 2.4.2 Predicate復合
- 2.4.3 Function復合
- 3 流處理
- 3.1 流簡介
- 3.1.1 流的定義
- 3.1.2 流的特點
- 3.2 流操作
- 3.2.1 中間操作
- 3.2.2 終端操作
- 3.3.3 構建流
- 3.3 流API
- 3.3.1 flatMap的用法
- 3.3.2 reduce的用法
- 3.4 collect操作
- 3.4.1 collect示例
- 3.4.2 Collector接口