
[TOC]
## 1 概述
代表性的編譯器產品:
- 前端編譯器: JDK的Javac、Eclipse JDT中的增量式編譯器(ECJ)
- 即時編譯器: HotSpot虛擬機的C1、C2編譯器,Graal編譯器
- 提前編譯器: JDK的Jaotc、GNU Compiler for the Java(GCJ)、Excelsior JET
> 即時編譯器在運行期的優化,支撐了程序執行效率的不斷提升;而前端編譯器在編譯期的優化過程,支撐著程序員的編碼效率和語言使用者的幸福感的提高
## 2 Javac 編譯器
Javac編譯過程大致分為1個準備過程和3個處理過程:
- (1) 準備過程: 初始化插入式注解處理器
- (2) 解析與填充符號表過程:
- 詞法、語法分析: 將源代碼的字符流轉變為標記集合,構造出抽象語法樹
- 填充符號表: 產生符號地址和符號信息
- (3) 插入式注解處理器的注解處理過程
- 如果產生新的符號,就必須回到之前的解析與填充符號表過程
- (4) 分析與字節碼生成過程:
- 標注檢查: 對語法的靜態信息進行檢查
- 數據流分析與控制流分析: 對程序動態運行過程進行檢查
- 解語法糖: 將簡化代碼編寫的語法糖還原為原有的形式
- 字節碼生成: 將前面各個步驟所生成的信息轉化成字節碼
> javac 源碼 `com.sun.tools.javac.main.JavaCompiler`
```java
initProcessAnnotations(processors); // 準備過程: 初始化插入式注解處理器
delegateCompiler =
processAnnotations( // 過程2: 執行注解處理
enterTrees( // 過程1.2 填充符號表
stopIfError(CompileState.PARSE,
parseFiles(sourceFileObjects) // 過程1.1 詞法分析、語法分析
)
),
classnames
);
delegateCompiler.compile2(); // 過程3 分析及字節碼生成
/*
case BY_TODO:
while (!todo.isEmpty()) {
// 過程3.4 生成字節碼( 過程3.3 解語法糖( 過程3.2 數據流分析( 過程3.1 標注
generate(desugar(flow(attribute(todo.remove()))));
}
*/
```
### 2.1 解析與填充符號表
#### 2.1.1 詞法、語法分析
- 詞法分析過程由 `com.sun.tools.javac.parser.Scanenr` 將源碼字符流轉換成Token
- 語法分析過程由 `com.sun.tools.javac.tree.JCTree` 按照Token序列構造出抽象語法樹(AST)
- 后續操作都是建立在AST上
#### 2.1.2 填充符號表
- 符號表(Symbol Table)是由一組符號地址和符號信息構成的數據結構,由 `com.sun.tools.javac.comp.Enter` 實現,輸出一個待處理列表,包含每一個編譯單元的AST的頂級節點以及 `package-info.java` 的頂級節點
### 2.2 注解處理器
- 插入式注解(Anntations)處理器工作時,可以讀取、修改、添加AST中的任意元素,如果進行過修改操作,編譯器將回到解析及填充符號表的過程,直到所有插入式注解處理器都沒有再對AST進行修改,每次循環成為一個輪次(Round)
- 例如 `Lombok` 可以通過注解來實現 `getter/setter` 等方法
- 其初始化過程在 `initProcessAnnotations()` 方法,其執行過程在 `processAnnotations()` 方法,如果有新的注解處理器需要執行,則通過 `com.sun.tools.javac.processing.JavaProcessingEnvironment#doProcessing()` 方法生成一個新的 `JavaCompiler` 對象,對編譯后續步驟進行處理
### 2.3 語義分析與字節碼生成
語法分析的主要任務是對結構上正確的源程序進行上下文相關性質的檢查,比如類型檢查、控制流程檢查、數據流檢查等
- 例如重復定義變量,則不會通過檢查
- IDE上紅線標注的錯誤,絕大部分源于語法分析階段的檢查結果
#### 2.3.1 標注檢查
標注檢查由 `com.sun.tools.javac.comp.Attr` 和 `com.sun.tools.javac.comp.Check` 實現,要檢查的內容包括:
- 變量使用前是否已被聲明
- 變量與賦值之間的數據類型是否能夠匹配
- ...
- 在標注檢查中,會進行常量折疊(Constant Floding)的優化
> `常量折疊`優化的例子
```java
int a = 1 + 2; // 等價于int a = 3;
String str = "hello" + " " + "World"; // 等價于String str = "hello World";
```
#### 2.3.2 數據流分析與控制流分析
數據流分析和控制流分析由 `com.sun.tools.javac.comp.Flow` 實現,是對程序上下文邏輯更進一步的驗證,可以檢查:
- 程序局部變量在使用前是否有賦值
- 方法的每條路徑是否有返回值
- 是否所有的受檢異常都被正確處理
- ...
> 由于局部變量在常量池中沒有 `CONSTANT_Fieldref_info` 的符號引用,所以 `final` 修飾的局部變量對運行時沒有影響:
```java
void method1(final int i) {
final int j = 0;
}
void method2(int i) {
int j = 0;
}
// 字節碼反編譯結果
/*
void method1(int);
Code:
0: iconst_1
1: istore_2
2: return
void method2(int);
Code:
0: iconst_1
1: istore_2
2: return
*/
```
#### 2.3.3 解語法糖
解語法糖的過程由 `com.sun.tools.javac.comp.TransTypes` 和 `com.sun.tools.javac.comp.Lower` 完成
#### 2.3.4 字節碼生成
字節碼(Byte Code)由 `com.sun.tools.javac.jvm.Gen` 完成,生成過程中不僅把AST和符號表轉化成字節碼指令寫到磁盤,還進行了少量的代碼添加和轉換,主要內容包含:
- 實例構造器 `<init>()` 方法和類構造器 `<clinit>()` 方法在這個這個階段被添加到AST中
- 把語句塊(對于實例構造器是`{}`,對于類構造器是`static{}`)、變量初始化、調用父類的實例構造器等操作收斂到`<init>()` 和 `<clinit>()` 方法中
- 按照先執行父類的實例構造器,初始化變量,最后執行語句塊的順序進行(由 `Gen::nomalizeDefs()` 方法實現)
- 把字符串的加號操作替換成StirngBuffer(JDK 5以上為StringBuilder)的append操作等
完成了AST的遍歷和調整之后,就會通過 `com.sun.tools.javac.jvm.ClassWriter#writeClass` 方法將填充好的符號表輸出成字節碼,生成Class文件,整個編譯過程結束。
## 3 Java 語法糖
語法糖(Syntactic Sugar)是指在計算機語言中添加的某種語法,對語言的編譯結果和功能并沒有實際影響,但是卻能更方便程序員使用該語言。通常來說使用語法糖能夠減少代碼量、增加程序的可讀性,從而減少程序代碼出錯的機會
### 3.1 泛型
泛型的本質是參數化類型(Parameterized Type)或者參數多態化(Parametric Polymorphism)的應用,即可以將操作的數據類型指定為方法簽名中的一種特殊參數
#### 3.1.1 實現方式
Java泛型的實現方式為類型擦除式形式(Type Erasure Generics),只在源碼中存在,編譯后的字節碼文件中,都被替換為原來的裸類型(Raw Type)。因此Java泛型有以下不被支持的用法:
> Java 中不支持的泛型用法
```java
public class A<E> {
public void method(Object item) {
if (item instanceof E) { // 無法對泛型進行實例判斷
// ...
}
E e = new E(); // 無法使用泛型創建對象
E[] arr = new E[2]; // 無法使用泛型創建數組
}
}
```
#### 3.1.2 歷史背景
Martin Odersky教授在剛發布一年的Java上實現了函數式編程的三大特性,泛型、高階函數和模式匹配,形成了Scala語言的前身Pizza語言,后來和Java團隊建立了名為 `Generic Java` 的項目,目標是把Pizza語言的泛型移植到Java中,但是需要保證二進制向后兼容性(Binary Backwards Compatibility,明確寫入《Java語言規范》中的對Java使用者的嚴肅承諾,譬如一個在JDK1.2中編譯出來的Class文件,必須保證能夠在JDK12乃至以后的版本中也能夠正常運行),意味著以前沒有的限制不能突然間冒出來。
> `Java 5.0` 以前數組支持協變(Covariant),集合類可以存入不同類型的元素,下面的代碼可以被正常編譯:
```java
Object[] array = new String[10];
array[0] = 10; // 編譯成功,運行出錯
ArrayList list = new ArrayList();
list.add(10); // 編譯成功,運行成功
list.add("Hello");
```
因此設計者有兩種選擇:
- 平行地加一套泛型化版本的新類型,例如C#添加了 `System.Collections.Generic`,Java引入了`Vector` 和 `Hashtable`(Java曾經的嘗試)
- 直接把所有的類型泛型化(Java后來的選擇),例如將ArrayList寫為 `ArrayList<T>`,Java中使用類型擦除實現
#### 3.1.3 泛型擦除
以ArrayList為例,裸類型的實現有兩種選擇:
- 在運行期由JVM來自動地、真實地構造出 `ArrayList<Integer>` 這樣的類型,并自動實現從 `ArrayList<Integer>` 派生自ArrayList的繼承關系來滿足裸類型的定義
- 直接在編譯時把ArrayList<Integer>還原為ArrayList,在元素訪問、修改時自動插入一些強制類型轉換和檢查指令
> 以下是一個展示`類型擦除`前后的例子:
```java
// 類型擦除前
Map<String, String> map = new HashMap<>();
map.put("Hello", "你好");
map.get("Hello");
// 類型擦除后
Map map = new HashMap();
map.put("Hello", "你好");
map.get((String)"Hello");
```
以下是類型擦除帶來的問題:
- 由于不支持int、long與Object之間的強制轉換,因此目前的Java不支持對原始類型(Primitive Types)的泛型,當時Java給出的解決方案是將泛型中的Integer等包裝類自動轉換成原始類型,這個決定導致了無數構造包裝類和裝箱、拆箱的開銷,成為Java泛型慢的重要原因,也成為 [Valhalla](https://zhuanlan.zhihu.com/p/87960146) 項目要重點解決的問題之一
- 由于運行期無法獲取到泛型類型的信息,因此一些代碼會變得繁瑣,例如寫一個泛型版本的從List到數組的轉換方法,由于不能從List中取得`T`,所以不得不新增一個參數傳入數組的類型
> `List<T>` 到 `T[]` 的轉換方法
```java
public static <T> T[] convert(List<T> list, Class<T> type) {
T[] array = (T[]) Array.newInstance(type, list.size());
// ...
}
```
### 3.2 自動裝箱、拆箱與遍歷循環
以下的例子包含了泛型、自動裝箱、拆箱與遍歷循環、變長參數5種語法糖,兩個方法編譯出來的字節碼完全相同。
> 包含泛型、自動裝箱、拆箱與遍歷循環、變長參數5種語法糖的例子
```java
void before() {
List<Integer> list = Arrays.asList(1, 2, 3);
int sum = 0;
for (int i : list) {
sum += i;
}
System.out.println(sum);
}
void after() {
List list = Arrays.asList(new Integer[]{
Integer.valueOf(1),
Integer.valueOf(2),
Integer.valueOf(3)
});
int sum = 0;
for (Iterator iter = list.iterator(); iter.hasNext(); ) {
int i = ((Integer) iter.next()).intValue();
sum += i;
}
System.out.println(sum);
}
```
避免對包裝類使用運算符,下面是一些陷阱:
> 一個關于`自動裝箱、拆箱`陷阱的例子
```java
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 128;
Integer f = 128;
Long g = 3L;
System.out.println(c == d); // true,Integer.valueOf(c) == Integer.valueOf(d),比較的是Integer.IntegerCache.cache,地址相同
System.out.println(e == f); // false,Integer.valueOf(e) == Integer.valueOf(f),比較的是new Integer(),地址不同
System.out.println(g.equals(a + b)); // false,Long.equals(Integer.valueOf(a.intValue() + b.intValue())),Long#equals方法的instanceof比較中返回false
```
> 以下是 `Integer#valueOf` 和 `Long#equals` 的源碼
```java
// java.lang.Integer
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
// java.lang.Long
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
```
### 3.3 條件編譯
以下是關于條件編譯的示例,method0()、method1()、method2()三個方法編譯生成的字節碼完全相同,method3()方法中由于變量的緣故,不進行條件編譯。
> 條件編譯的例子
```java
void method0() {
System.out.println(true);
}
void method1() {
if (true) {
System.out.println(true);
} else {
System.out.println(false);
}
}
void method2() {
if (1 + 1 == 2) {
System.out.println(true);
} else {
System.out.println(false);
}
}
void method3() {
int a = 1;// 變量不參與條件編譯
if (a + 1 == 2) {
System.out.println(true);
} else {
System.out.println(false);
}
}
```
## 4 插入式注解處理器
可以使用插入式注解處理器API來對Java編譯子系統的行為施加影響
### 4.1 NameCheckProcessor: 名稱檢查插件
> 以下是名稱檢查插件的代碼 `NameCheckProcessor.java`
```
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.Set;
/**
* 名稱檢查插件
*/
@SupportedAnnotationTypes("*") // 不限于特定的注解,檢查任何代碼
@SupportedSourceVersion(SourceVersion.RELEASE_6) // 能處理基于JDK 6的源碼
public class NameCheckProcessor extends AbstractProcessor {
/** 名稱檢查器 */
private NameChecker nameChecker;
/**
* 初始化名稱檢查插件
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
nameChecker = new NameChecker(processingEnv);
}
/**
* 對輸入的語法樹的各個節點進行名稱檢查
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
// 在當前輪次(round)中每一個根節點傳入檢查器進行檢查
for (Element element : roundEnv.getRootElements()) {
nameChecker.checkNames(element);
}
}
return false;
}
}
```
### 4.2 NameChecker: 名稱檢查器
> 以下是名稱檢查器的代碼 `NameChecker.java`
```java
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.util.ElementScanner6;
import javax.tools.Diagnostic;
import java.util.EnumSet;
import static javax.lang.model.element.ElementKind.*;
import static javax.lang.model.element.Modifier.*;
/**
* 名稱檢查器
*/
public class NameChecker {
/** 消息通知器 */
private final Messager messager;
/** 自定義的名稱掃描器 */
NameCheckerScanner nameCheckerScanner = new NameCheckerScanner();
/**
* 名稱檢查器的構造函數
*/
public NameChecker(ProcessingEnvironment processingEnv) {
this.messager = processingEnv.getMessager();
}
/**
* 對Java程序的命名進行檢查
*/
public void checkNames(Element element) {
nameCheckerScanner.scan(element);
}
class NameCheckerScanner extends ElementScanner6<Void, Void> {
/**
* 檢查類命名是否合法
*/
@Override
public Void visitType(TypeElement e, Void p) {
scan(e.getTypeParameters(), p);
checkCamelCase(e.getSimpleName().toString(), true);
super.visitType(e, p);
return null;
}
/**
* 檢查方法命名是否合法
*/
@Override
public Void visitExecutable(ExecutableElement e, Void p) {
if (e.getKind() == METHOD) {
Name name = e.getSimpleName();
if (name.contentEquals(e.getEnclosingElement().getSimpleName())) {
messager.printMessage(Diagnostic.Kind.WARNING, "方法 '" + name + "' 不應該與類名重復", e);
checkCamelCase(e.getSimpleName().toString(), false);
}
}
super.visitExecutable(e, p);
return null;
}
/**
* 檢查變量命名是否合法
*/
@Override
public Void visitVariable(VariableElement e, Void aVoid) {
// 如果是枚舉或常量,則必須大寫,否則小駝峰
if (e.getKind() == ENUM_CONSTANT || e.getConstantValue() != null || checkConstant(e)) {
checkAllCaps(e.getSimpleName().toString());
} else {
checkCamelCase(e.getSimpleName().toString(), false);
}
return null;
}
/**
* 判斷一個變量是否常量
*/
private boolean checkConstant(VariableElement e) {
if (e.getEnclosingElement().getKind() == INTERFACE) {
return true;
} else if (e.getKind() == FIELD && e.getModifiers().containsAll(EnumSet.of(PUBLIC, STATIC, FINAL))) {
return true;
} else {
return false;
}
}
/**
* 檢查是否符合駝峰
*
* @param b 是否大駝峰
*/
private void checkCamelCase(String name, boolean b) {
System.out.println("檢查是否符合駝峰: " + name);
}
/**
* 檢查是否全部大寫
*/
private void checkAllCaps(String name) {
System.out.println("檢查是否全部大寫: " + name);
}
}
}
```
### 4.3 BADLY_NAMED_CODE: 被檢查的代碼
> 一段自定義的命名不規范的代碼 `BADLY_NAMED_CODE.java`
```java
/**
* 包含多出不規范命名的代碼樣例
* 類名未遵循大駝峰
*/
public class BADLY_NAMED_CODE {
/**
* 枚舉大駝峰
*/
enum colors {
// 枚舉值要大寫
red,
blue,
green
}
/**
* 常量大寫
*/
static final int _FORTY_XXX = 43;
/**
* 變量未遵循小駝峰
*/
public static int NOt_A_CONSTANT = _FORTY_XXX;
/**
* 方法名與類名相同
*/
protected void BADLY_NAMED_CODE(){
return;
}
/**
* 方法沒有遵循小駝峰
*/
public void NOTcamelCASEEmxxx() {
return;
}
}
```
### 4.4 使用插入式注解處理器編譯文件
> 上述三個java文件在同一目錄,在該目錄下打開終端,輸入以下命令:
```bash
# 設置當前目錄為類路徑,否則無法編譯
export CLASSPATH=.:$CLASSPATH
# 編譯NameChecker.java
javac NameChecker.java
# 編譯NameCheckProcessor.java
javac NameCheckProcessor.java
# 通過插件編譯BADLY_NAMED_CODE.java
javac -processor NameCheckProcessor BADLY_NAMED_CODE.java
```
> 接下終端會輸出以下信息:
```bash
檢查是否符合駝峰: BADLY_NAMED_CODE
檢查是否符合駝峰: colors
檢查是否符合駝峰: name
檢查是否全部大寫: red
檢查是否全部大寫: blue
檢查是否全部大寫: green
檢查是否符合駝峰: args
檢查是否全部大寫: _FORTY_XXX
檢查是否符合駝峰: NOt_A_CONSTANT
檢查是否符合駝峰: BADLY_NAMED_CODE
警告: 來自注釋處理程序 'NameCheckProcessor' 的受支持 source 版本 'RELEASE_6' 低于 -source '1.8'
BADLY_NAMED_CODE.java:32: 警告: 方法 'BADLY_NAMED_CODE' 不應該與類名重復
protected void BADLY_NAMED_CODE(){
^
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接口