整理了好久,終于算是告一段落了,本次的學習指南系列將分為三個模塊
- 第一 Java知識點匯總
- 第二 Android基礎知識點匯總
- 第三 Android進階知識點匯總
后續的NDK、跨平臺、底層原始碼等技術,也會抽空給大家整理一下知識點,如果喜歡的話,希望大家給個關注,點個贊唄,可找我拿PDF版本的學習指南,
怎么找到我:Android.md · master · 讓開,我要吃人了 / Android · CODE CHINACODE CHINA——開源代碼托管平臺,獨立第三方開源社區,Git/Github/Gitlab
https://codechina.csdn.net/weixin_55362248/android/-/blob/master/Android.md
閑話少說,正文開始,
JVM
JVM 作業流程
運行時資料區(Runtime Data Area)
程式計數器
程式計數器(Program Counter Register) 是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器,
位元組碼解釋器作業時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、回圈、跳轉、例外處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成,
由于 Java 虛擬機的多執行緒是通過執行緒輪流切換并分配處理器執行時間的方式來實作的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內核)都只會執行一條執行緒中的指令,
因此,為了執行緒切換后能恢復到正確的執行位置,每條執行緒都需要有一個獨立的程式計數器,各條執行緒之間計數器互不影響,獨立存盤,我們稱這類記憶體區域為“執行緒私有”的記憶體,
- 如果執行緒正在執行的是一個 Java 方法,這個計數器記錄的是正在執行的虛擬機位元組碼指令的地址,
- 如果執行緒正在執行的是一個 Native 方法,這個計數器值則為空(Undefined),
此記憶體區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域,
Java 虛擬機堆疊
Java 虛擬機堆疊(Java Virtual Machine Stacks)也是執行緒私有的,它的生命周期與執行緒相同,虛擬機堆疊描述的是 Java 方法執行的記憶體模型,每個方法在執行的同時都會創建一個堆疊幀(Stack Frame) 用于存盤區域變數表、運算元堆疊、動態鏈接、方法出口等訊息,每一個方法從呼叫直至執行完成的程序,就對應著一個堆疊幀在虛擬機堆疊中入堆疊到出堆疊的程序,
區域變數表存放了編譯器可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件參考(reference型別,它不等同于物件本身,可能是一個指向物件起始地址的參考指標,也可能是指向一個代表物件的句柄或其他與此物件相關的位置)和 returnAddress 型別(指向了一條位元組碼指令的地址),
其中 64 位長度的 long 和 double 型別的資料會占用兩個區域變數空間(Slot),其余的資料型別只占用一個,區域變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的區域變數空間是完全確定的,在方法運行期間不會改變區域變數表的大小,
在 Java 虛擬機規范中,對這個區域規定了兩種例外狀態:
- 如果執行緒請求的堆疊深度大于虛擬機所允許的的深度,將拋出 StackOverflowError 例外,
- 如果虛擬機堆疊可以動態擴展(當前大部分的Java虛擬機都可動態擴展,只不過Java虛擬機規范中也允許固定長度的虛擬機堆疊),如果擴展時無法申請到足夠的記憶體,就會拋出 OutOfMemoryError 例外,
本地方法堆疊
本地方法堆疊(Native Method Stack) 與虛擬機堆疊所發揮的作用是非常相似的,它們之間的區別不過是虛擬機堆疊為虛擬機執行Java方法(也就是位元組碼)服務,而本地方法堆疊則為虛擬機使用到的Native方法服務,
在虛擬機規范中對本地方法堆疊中方法使用的語言、使用方式與資料結構并沒有強制規定,因此具體的虛擬機可以自由實作它,甚至有的虛擬機(例如:Sun HotSpot虛擬機)直接就把虛擬機堆疊和本地方法堆疊合二為一,與虛擬機堆疊一樣,本地方法堆疊區域也會拋出 StackOverflowError 和 OutOfMemoryError 例外,
Java 堆
對于大多數應用來說,Java 堆(Java Heap) 是 Java 虛擬機所管理的的記憶體中最大的一塊,Java 堆是被所有執行緒共享的一塊記憶體區域,在虛擬機啟動時創建,此記憶體區域的唯一目的就是存放物件實體,幾乎所有的物件實體都在這里分配記憶體,
Java堆是垃圾收集器管理的主要區域,從記憶體回收的角度來看,由于現在收集器基本采用分代收集演算法,所以Java堆中還可以細分為:新生代和老年代;再細致一點的有 Eden 空間、From Survivor 空間、To Survivor 空間等,
從記憶體分配的角度來看,執行緒共享的Java堆中可能劃分出多個執行緒私有的分配緩沖區(Thread Local Allocation Buffer,TLAB),不過無論如何劃分,都與存放內容無關,無論哪個區域,存盤的仍然是物件實體,進一步劃分的目的是為了更好地回收記憶體,或者更快地分配記憶體,
方法區
方法區(Method Area)與 Java 堆一樣,是各個執行緒共享的記憶體區域,它用于存盤已被虛擬機加載的類資訊、常量、靜態變數、即時編譯器編譯后的代碼等資料,
運行時常量池(Runtime Constant Pool) 是方法區的一部分,Class 檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(Constant Pool Table) ,用于存放編譯器生成的各種字面量和符號參考,這部分內容將在類加載后進入方法區的運行時常量池中存放,
既然運行時常量池是方法區的一部分,自然受到方法區記憶體的限制,當常量池無法再申請到記憶體時就會拋出 OutOfMemoryError 例外,
方法指令
| 指令 | 說明 |
|---|---|
| invokeinterface | 用以呼叫介面方法 |
| invokevirtual | 指令用于呼叫物件的實體方法 |
| invokestatic | 用以呼叫類/靜態方法 |
| invokespecial | 用于呼叫一些需要特殊處理的實體方法,包括實體初始化方法、私有方法和父類方法 |
類加載器
| 類加載器 | 說明 |
|---|---|
| BootstrapClassLoader | Bootstrap 類加載器負責加載 rt.jar 中的 JDK 類檔案,它是所有類加載器的父加載器,Bootstrap 類加載器沒有任何父類加載器,如果你呼叫 String.class.getClassLoader(),會回傳 null,任何基于此的代碼會拋出 NUllPointerException 例外,Bootstrap 加載器被稱為初始類加載器 |
| ExtClassLoader | 而 Extension 將加載類的請求先委托給它的父加載器,也就是Bootstrap,如果沒有成功加載的話,再從 jre/lib/ext 目錄下或者 java.ext.dirs 系統屬性定義的目錄下加載類,Extension 加載器由 sun.misc.Launcher$ExtClassLoader 實作 |
| AppClassLoader | 第三種默認的加載器就是 System 類加載器(又叫作 Application 類加載器)了,它負責從 classpath 環境變數中加載某些應用相關的類,classpath 環境變數通常由 -classpath 或 -cp 命令列選項來定義,或者是 JAR 中的 Manifest 的 classpath 屬性,Application 類加載器是 Extension 類加載器的子加載器 |
| 作業原理 | 說明 |
|---|---|
| 委托機制 | 加載任務委托交給父類加載器,如果不行就向下傳遞委托任務,由其子類加載器加載,保證 java 核心庫的安全性 |
| 可見性機制 | 子類加載器可以看到父類加載器加載的類,而反之則不行 |
| 單一性機制 | 父加載器加載過的類不能被子加載器加載第二次 |
垃圾回收 gc
物件存活判斷
- 參考計數
每個物件有一個參考計數屬性,新增一個參考時計數加1,參考釋放時計數減1,計數為0時可以回收,此方法簡單,無法解決物件相互回圈參考的問題,
- 可達性分析
從 GC Roots 開始向下搜索,搜索所走過的路徑稱為參考鏈,當一個物件到 GC Roots 沒有任何參考鏈相連時,則證明此物件是不可用的,不可達物件,
在Java語言中,GC Roots包括:
- 虛擬機堆疊中參考的物件,
- 方法區中類靜態屬性物體參考的物件,
- 方法區中常量參考的物件,
- 本地方法堆疊中 JNI 參考的物件,
垃圾收集演算法
- 標記 -清除演算法
“標記-清除”(Mark-Sweep)演算法,如它的名字一樣,演算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的物件,在標記完成后統一回收掉所有被標記的物件,之所以說它是最基礎的收集演算法,是因為后續的收集演算法都是基于這種思路并對其缺點進行改進而得到的,
它的主要缺點有兩個:一個是效率問題,標記和清除程序的效率都不高;另外一個是空間問題,標記清除之后會產生大量不連續的記憶體碎片,空間碎片太多可能會導致,當程式在以后的運行程序中需要分配較大物件時無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作,
- 復制演算法
“復制”(Copying)的收集演算法,它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當這一塊的記憶體用完了,就將還存活著的物件復制到另外一塊上面,然后再把已使用過的記憶體空間一次清理掉,
這樣使得每次都是對其中的一塊進行記憶體回收,記憶體分配時也就不用考慮記憶體碎片等復雜情況,只要移動堆頂指標,按順序分配記憶體即可,實作簡單,運行高效,只是這種演算法的代價是將記憶體縮小為原來的一半,持續復制長生存期的物件則導致效率降低,
- 標記-整理演算法
復制收集演算法在物件存活率較高時就要執行較多的復制操作,效率將會變低,更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的記憶體中所有物件都100%存活的極端情況,所以在老年代一般不能直接選用這種演算法,
根據老年代的特點,有人提出了另外一種“標記-整理”(Mark-Compact)演算法,標記程序仍然與“標記-清除”演算法一樣,但后續步驟不是直接對可回收物件進行清理,而是讓所有存活的物件都向一端移動,然后直接清理掉端邊界以外的記憶體,
- 分代收集演算法
GC 分代的基本假設:絕大部分物件的生命周期都非常短暫,存活時間短,
“分代收集”(Generational Collection)演算法,把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集演算法,在新生代中,每次垃圾收集時都發現有大批物件死去,只有少量存活,那就選用復制演算法,只需要付出少量存活物件的復制成本就可以完成收集,而老年代中因為物件存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”演算法來進行回收,
垃圾收集器
- CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,目前很大一部分的 Java 應用都集中在互聯網站或B/S系統的服務端上,這類應用尤其重視服務的回應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗,
從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“標記-清除”演算法實作的,它的運作程序相對于前面幾種收集器來說要更復雜一些,整個程序分為4個步驟,包括:
- 初始標記(CMS initial mark)
- 并發標記(CMS concurrent mark)
- 重新標記(CMS remark)
- 并發清除(CMS concurrent sweep)
其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”,初始標記僅僅只是標記一下GC Roots能直接關聯到的物件,速度很快,并發標記階段就是進行GC Roots Tracing的程序,而重新標記階段則是為了修正并發標記期間,因用戶程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比并發標記的時間短,
由于整個程序中耗時最長的并發標記和并發清除程序中,收集器執行緒都可以與用戶執行緒一起作業,所以總體上來說,CMS收集器的記憶體回收程序是與用戶執行緒一起并發地執行,老年代收集器(新生代使用ParNew)
- G1收集器
與CMS收集器相比G1收集器有以下特點:
1、空間整合,G1收集器采用標記整理演算法,不會產生記憶體空間碎片,分配大物件時不會因為無法找到連續空間而提前觸發下一次GC,
2、可預測停頓,這是G1的另一大優勢,降低停頓時間是G1和CMS的共同關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為N毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已經是實時 Java(RTSJ)的垃圾收集器的特征了,
使用G1收集器時,Java堆的記憶體布局與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續)Region 的集合,
G1的新生代收集跟 ParNew 類似,當新生代占用達到一定比例的時候,開始出發收集,和 CMS 類似,G1 收集器收集老年代物件會有短暫停頓,
記憶體模型與回收策略

Java 堆(Java Heap)是JVM所管理的記憶體中最大的一塊,堆又是垃圾收集器管理的主要區域,Java 堆主要分為2個區域-年輕代與老年代,其中年輕代又分 Eden 區和 Survivor 區,其中 Survivor 區又分 From 和 To 2個區,
- Eden 區
大多數情況下,物件會在新生代 Eden 區中進行分配,當 Eden 區沒有足夠空間進行分配時,虛擬機會發起一次 Minor GC,Minor GC 相比 Major GC 更頻繁,回收速度也更快, 通過 Minor GC 之后,Eden 會被清空,Eden 區中絕大部分物件會被回收,而那些無需回收的存活物件,將會進到 Survivor 的 From 區(若 From 區不夠,則直接進入 Old 區),
- Survivor 區
Survivor 區相當于是 Eden 區和 Old 區的一個緩沖,類似于我們交通燈中的黃燈,Survivor 又分為2個區,一個是 From 區,一個是 To 區,每次執行 Minor GC,會將 Eden 區和 From 存活的物件放到 Survivor 的 To 區(如果 To 區不夠,則直接進入 Old 區),Survivor 的存在意義就是減少被送到老年代的物件,進而減少 Major GC 的發生,Survivor 的預篩選保證,只有經歷16次 Minor GC 還能在新生代中存活的物件,才會被送到老年代,
- Old 區
老年代占據著2/3的堆記憶體空間,只有在 Major GC 的時候才會進行清理,每次 GC 都會觸發“Stop-The-World”,記憶體越大,STW 的時間也越長,所以記憶體也不僅僅是越大就越好,由于復制演算法在物件存活率較高的老年代會進行很多次的復制操作,效率很低,所以老年代這里采用的是標記——整理演算法,
Object
equals 方法
對兩個物件的地址值進行的比較(即比較參考是否相同)
public boolean equals(Object obj) {
return (this == obj);
}
hashCode 方法
hashCode() 方法給物件回傳一個 hash code 值,這個方法被用于 hash tables,例如 HashMap,
它的性質是:
- 在一個Java應用的執行期間,如果一個物件提供給 equals 做比較的資訊沒有被修改的話,該物件多次呼叫 hashCode() 方法,該方法必須始終如一回傳同一個 integer,
- 如果兩個物件根據 equals(Object) 方法是相等的,那么呼叫二者各自的 hashCode() 方法必須產生同一個 integer 結果,
- 并不要求根據 equals(Object) 方法不相等的兩個物件,呼叫二者各自的 hashCode() 方法必須產生不同的 integer 結果,然而,程式員應該意識到對于不同的物件產生不同的 integer 結果,有可能會提高 hash table 的性能,
在 JDK 中,Object 的 hashcode 方法是本地方法,也就是用 c 語言或 c++ 實作的,該方法直接回傳物件的 記憶體地址,在 String 類,重寫了 hashCode 方法
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
static
- static關鍵字修飾的方法或者變數不需要依賴于物件來進行訪問,只要類被加載了,就可以通過類名去進行訪問,
- 靜態變數被所有的物件所共享,在記憶體中只有一個副本,它當且僅當在類初次加載時會被初始化,
- 能通過 this 訪問靜態成員變數嗎? 所有的靜態方法和靜態變數都可以通過物件訪問(只要訪問權限足夠),
- static是不允許用來修飾區域變數
final
- 可以宣告成員變數、方法、類以及本地變數
- final 成員變數必須在宣告的時候初始化或者在構造器中初始化,否則就會報編譯錯誤
- final 變數是只讀的
- final 申明的方法不可以被子類的方法重寫
- final 類通常功能是完整的,不能被繼承
- final 變數可以安全的在多執行緒環境下進行共享,而不需要額外的同步開銷
- final 關鍵字提高了性能,JVM 和 Java 應用都會快取 final 變數,會對方法、變數及類進行優化
- 方法的內部類訪問方法中的區域變數,但必須用 final 修飾才能訪問
String、StringBuffer、StringBuilder
- String 是 final 類,不能被繼承,對于已經存在的 Stirng 物件,修改它的值,就是重新創建一個物件
- StringBuffer 是一個類似于 String 的字串緩沖區,使用 append() 方法修改 Stringbuffer 的值,使用 toString() 方法轉換為字串,是執行緒安全的
- StringBuilder 用來替代于 StringBuffer,StringBuilder 是非執行緒安全的,速度更快
例外處理
- Exception、Error 是 Throwable 類的子類
- Error 類物件由 Java 虛擬機生成并拋出,不可捕捉
- 不管有沒有例外,finally 中的代碼都會執行
- 當 try、catch 中有 return 時,finally 中的代碼依然會繼續執行
| 常見的Error | ||
|---|---|---|
| OutOfMemoryError | StackOverflowError | NoClassDeffoundError |
| 常見的Exception | ||
|---|---|---|
| 常見的非檢查性例外 | ||
| ArithmeticException | ArrayIndexOutOfBoundsException | ClassCastException |
| IllegalArgumentException | IndexOutOfBoundsException | NullPointerException |
| NumberFormatException | SecurityException | UnsupportedOperationException |
| 常見的檢查性例外 | ||
| IOException | CloneNotSupportedException | IllegalAccessException |
| NoSuchFieldException | NoSuchMethodException | FileNotFoundException |
內部類
- 非靜態內部類沒法在外部類的靜態方法中實體化,
- 非靜態內部類的方法可以直接訪問外部類的所有資料,包括私有的資料,
- 在靜態內部類中呼叫外部類成員,成員也要求用 static 修飾,
- 創建靜態內部類的物件可以直接通過外部類呼叫靜態內部類的構造器;創建非靜態的內部類的物件必須先創建外部類的物件,通過外部類的物件呼叫內部類的構造器,
匿名內部類
- 匿名內部類不能定義任何靜態成員、方法
- 匿名內部類中的方法不能是抽象的
- 匿名內部類必須實作介面或抽象父類的所有抽象方法
- 匿名內部類不能定義構造器
- 匿名內部類訪問的外部類成員變數或成員方法必須用 final 修飾
多型
- 父類的參考可以指向子類的物件
- 創建子類物件時,呼叫的方法為子類重寫的方法或者繼承的方法
- 如果我們在子類中撰寫一個獨有的方法,此時就不能通過父類的參考創建的子類物件來呼叫該方法
抽象和介面
- 抽象類不能有物件(不能用 new 關鍵字來創建抽象類的物件)
- 抽象類中的抽象方法必須在子類中被重寫
- 介面中的所有屬性默認為:public static final;
- 介面中的所有方法默認為:public abstract;
集合框架
- List介面存盤一組不唯一,有序(插入順序)的物件, Set介面存盤一組唯一,無序的物件,
HashMap
結構圖
- JDK 1.7 HashMap 結構圖

- JDK 1.8 HashMap 結構圖
HashMap 的作業原理
HashMap 基于 hashing 原理,我們通過 put() 和 get() 方法儲存和獲取物件,當我們將鍵值對傳遞給 put() 方法時,它呼叫鍵物件的 hashCode() 方法來計算 hashcode,讓后找到 bucket 位置來儲存 Entry 物件,當兩個物件的 hashcode 相同時,它們的 bucket 位置相同,‘碰撞’會發生,因為 HashMap 使用鏈表存盤物件,這個 Entry 會存盤在鏈表中,當獲取物件時,通過鍵物件的 equals() 方法找到正確的鍵值對,然后回傳值物件,
如果 HashMap 的大小超過了負載因子(load factor)定義的容量,怎么辦?
默認的負載因子大小為 0.75,也就是說,當一個 map 填滿了 75% 的 bucket 時候,和其它集合類(如 ArrayList 等)一樣,將會創建原來 HashMap 大小的兩倍的 bucket 陣列,來重新調整 map 的大小,并將原來的物件放入新的 bucket 陣列中,這個程序叫作 rehashing,因為它呼叫 hash 方法找到新的 bucket 位置,
為什么 String, Interger 這樣的 wrapper 類適合作為鍵?
因為 String 是不可變的,也是 final 的,而且已經重寫了 equals() 和 hashCode() 方法了,其他的 wrapper 類也有這個特點,不可變性是必要的,因為為了要計算 hashCode(),就要防止鍵值改變,如果鍵值在放入時和獲取時回傳不同的 hashcode 的話,那么就不能從 HashMap 中找到你想要的物件,不可變性還有其他的優點如執行緒安全,如果你可以僅僅通過將某個 field 宣告成 final 就能保證 hashCode 是不變的,那么請這么做吧,因為獲取物件的時候要用到 equals() 和 hashCode() 方法,那么鍵物件正確的重寫這兩個方法是非常重要的,如果兩個不相等的物件回傳不同的 hashcode 的話,那么碰撞的幾率就會小些,這樣就能提高 HashMap 的性能,
HashMap 與 HashTable 對比
HashMap 是非 synchronized 的,性能更好,HashMap 可以接受為 null 的 key-value,而 Hashtable 是執行緒安全的,比 HashMap 要慢,不接受 null 的 key-value,
HashMap.java
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
···
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
···
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
···
}
HashTable.java
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
···
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
···
addEntry(hash, key, value, index);
return null;
}
···
public synchronized V get(Object key) {
HashtableEntry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (HashtableEntry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
···
}
ConcurrentHashMap
Base 1.7
ConcurrentHashMap 最外層不是一個大的陣列,而是一個 Segment 的陣列,每個 Segment 包含一個與 HashMap 資料結構差不多的鏈表陣列,
正在上傳…重新上傳取消?
在讀寫某個 Key 時,先取該 Key 的哈希值,并將哈希值的高 N 位對 Segment 個數取模從而得到該 Key 應該屬于哪個Segment,接著如同操作 HashMap 一樣操作這個 Segment,
Segment 繼承自 ReentrantLock,可以很方便的對每一個 Segmen 上鎖,
對于讀操作,獲取 Key 所在的 Segment 時,需要保證可見性,具體實作上可以使用volatile關鍵字,也可使用鎖,但使用鎖開銷太大,而使用volatile時每次寫操作都會讓所有CPU內快取無效,也有一定開銷,ConcurrentHashMap 使用如下方法保證可見性,取得最新的Segment:
Segment<K,V> s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)
獲取 Segment 中的 HashEntry 時也使用了類似方法:
HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE)
對于寫操作,并不要求同時獲取所有 Segment 的鎖,因為那樣相當于鎖住了整個Map,它會先獲取該 Key-Value 對所在的 Segment 的鎖,獲取成功后就可以像操作一個普通的 HashMap 一樣操作該 Segment,并保證該 Segment 的安全性,同時由于其它 Segment 的鎖并未被獲取,因此理論上可支持 concurrencyLevel(等于Segment的個數)個執行緒安全的并發讀寫,
獲取鎖時,并不直接使用 lock 來獲取,因為該方法獲取鎖失敗時會掛起,事實上,它使用了自旋鎖,如果 tryLock 獲取鎖失敗,說明鎖被其它執行緒占用,此時通過回圈再次以 tryLock 的方式申請鎖,如果在回圈程序中該 Key 所對應的鏈表頭被修改,則重置 retry 次數,如果 retry 次數超過一定值,則使用 lock 方法申請鎖,
這里使用自旋鎖是因為自旋鎖的效率比較高,但是它消耗 CPU 資源比較多,因此在自旋次數超過閾值時切換為互斥鎖,
Base 1.8
1.7 已經解決了并發問題,并且能支持 N 個 Segment 這么多次數的并發,但依然存在 HashMap 在 1.7 版本中的問題:查詢遍歷鏈表效率太低,因此 1.8 做了一些資料結構上的調整,
其中拋棄了原有的 Segment 分段鎖,而采用了 CAS + synchronized 來保證并發安全性,
ConcurrentHashMap.java
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
···
}
else if (f instanceof TreeBin) {
···
}
else if (f instanceof ReservationNode)
throw new IllegalStateException("Recursive update");
}
}
···
}
addCount(1L, binCount);
return null;
}
ArrayList
ArrayList 本質上是一個動態陣列,第一次添加元素時,陣列大小將變化為 DEFAULT_CAPACITY 10,不斷添加元素后,會進行擴容,洗掉元素時,會按照位置關系把陣列元素整體(復制)移動一遍, ArrayList.java
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
···
// 增加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
···
// 洗掉元素
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
···
// 查找元素
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
···
}
LinkedList
LinkedList 本質上是一個雙向鏈表的存盤結構,
LinkedList.java
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
····
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
···
// 增加元素
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
···
// 洗掉元素
E unlink(Node<E> x) {
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
···
// 查找元素
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
···
}
對于元素查詢來說,ArrayList 優于 LinkedList,因為 LinkedList 要移動指標,對于新增和洗掉操作,LinedList 比較占優勢,因為 ArrayList 要移動資料,
CopyOnWriteArrayList
CopyOnWriteArrayList 是執行緒安全容器(相對于 ArrayList),增加洗掉等寫操作通過加鎖的形式保證資料一致性,通過復制新集合的方式解決遍歷迭代的問題,
CopyOnWriteArrayList.java
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
final transient Object lock = new Object();
···
// 增加元素
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
···
// 洗掉元素
public E remove(int index) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
}
}
···
// 查找元素
private E get(Object[] a, int index) {
return (E) a[index];
}
}
反射
try {
Class cls = Class.forName("com.jasonwu.Test");
//獲取構造方法
Constructor[] publicConstructors = cls.getConstructors();
//獲取全部構造方法
Constructor[] declaredConstructors = cls.getDeclaredConstructors();
//獲取公開方法
Method[] methods = cls.getMethods();
//獲取全部方法
Method[] declaredMethods = cls.getDeclaredMethods();
//獲取公開屬性
Field[] publicFields = cls.getFields();
//獲取全部屬性
Field[] declaredFields = cls.getDeclaredFields();
Object clsObject = cls.newInstance();
Method method = cls.getDeclaredMethod("getModule1Functionality");
Object object = method.invoke(null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
單例
餓漢式
public class CustomManager {
private Context mContext;
private static final Object mLock = new Object();
private static CustomManager mInstance;
public static CustomManager getInstance(Context context) {
synchronized (mLock) {
if (mInstance == null) {
mInstance = new CustomManager(context);
}
return mInstance;
}
}
private CustomManager(Context context) {
this.mContext = context.getApplicationContext();
}
}
雙重檢查模式
public class CustomManager {
private Context mContext;
private volatile static CustomManager mInstance;
public static CustomManager getInstance(Context context) {
// 避免非必要加鎖
if (mInstance == null) {
synchronized (CustomManger.class) {
if (mInstance == null) {
mInstacne = new CustomManager(context);
}
}
}
return mInstacne;
}
private CustomManager(Context context) {
this.mContext = context.getApplicationContext();
}
}
靜態內部類模式
public class CustomManager{
private CustomManager(){}
private static class CustomManagerHolder {
private static final CustomManager INSTANCE = new CustomManager();
}
public static CustomManager getInstance() {
return CustomManagerHolder.INSTANCE;
}
}
靜態內部類的原理是: 當 SingleTon 第一次被加載時,并不需要去加載 SingleTonHoler,只有當 getInstance() 方法第一次被呼叫時,才會去初始化 INSTANCE,這種方法不僅能確保執行緒安全,也能保證單例的唯一性,同時也延遲了單例的實體化,getInstance 方法并沒有多次去 new 物件,取的都是同一個 INSTANCE 物件,
虛擬機會保證一個類的 <clinit>() 方法在多執行緒環境中被正確地加鎖、同步,如果多個執行緒同時去初始化一個類,那么只會有一個執行緒去執行這個類的 <clinit>() 方法,其他執行緒都需要阻塞等待,直到活動執行緒執行 <clinit>() 方法完畢
缺點在于無法傳遞引數,如Context等
執行緒
執行緒是行程中可獨立執行的最小單位,也是 CPU 資源(時間片)分配的基本單位,同一個行程中的執行緒可以共享行程中的資源,如記憶體空間和檔案句柄,
屬性
| 屬性 | 說明 |
|---|---|
| id | 執行緒 id 用于標識不同的執行緒,編號可能被后續創建的執行緒使用,編號是只讀屬性,不能修改 |
| name | 名字的默認值是 Thread-(id) |
| daemon | 分為守護執行緒和用戶執行緒,我們可以通過 setDaemon(true) 把執行緒設定為守護執行緒,守護執行緒通常用于執行不重要的任務,比如監控其他執行緒的運行情況,GC 執行緒就是一個守護執行緒,setDaemon() 要在執行緒啟動前設定,否則 JVM 會拋出非法執行緒狀態例外,可被繼承, |
| priority | 執行緒調度器會根據這個值來決定優先運行哪個執行緒(不保證),優先級的取值范圍為 1~10,默認值是 5,可被繼承,Thread 中定義了下面三個優先級常量: - 最低優先級:MIN_PRIORITY = 1 - 默認優先級:NORM_PRIORITY = 5 - 最高優先級:MAX_PRIORITY = 10 |
狀態
正在上傳…重新上傳取消?
| 狀態 | 說明 |
|---|---|
| New | 新創建了一個執行緒物件,但還沒有呼叫start()方法, |
| Runnable | Ready 狀態 執行緒物件創建后,其他執行緒(比如 main 執行緒)呼叫了該物件的 start() 方法,該狀態的執行緒位于可運行執行緒池中,等待被執行緒調度選中 獲取 cpu 的使用權,Running 緒狀態的執行緒在獲得 CPU 時間片后變為運行中狀態(running), |
| Blocked | 執行緒因為某種原因放棄了cpu 使用權(等待鎖),暫時停止運行 |
| Waiting | 執行緒進入等待狀態因為以下幾個方法: - Object#wait() - Thread#join() - LockSupport#park() |
| Timed Waiting | 有等待時間的等待狀態, |
| Terminated | 表示該執行緒已經執行完畢, |
狀態控制
- wait() / notify() / notifyAll()
wait(),notify(),notifyAll() 是定義在Object類的實體方法,用于控制執行緒狀態,三個方法都必須在synchronized 同步關鍵字所限定的作用域中呼叫,否則會報錯 java.lang.IllegalMonitorStateException,
| 方法 | 說明 |
|---|---|
wait() | 執行緒狀態由 的使用權,Running 變為 Waiting, 并將當前執行緒放入等待佇列中 |
notify() | notify() 方法是將等待佇列中一個等待執行緒從等待佇列移動到同步佇列中 |
notifyAll() | 則是將所有等待佇列中的執行緒移動到同步佇列中 |
被移動的執行緒狀態由 Running 變為 Blocked,notifyAll 方法呼叫后,等待執行緒依舊不會從 wait() 回傳,需要呼叫 notify() 或者 notifyAll() 的執行緒釋放掉鎖后,等待執行緒才有機會從 wait() 回傳,
- join() / sleep() / yield()
在很多情況,主執行緒創建并啟動子執行緒,如果子執行緒中需要進行大量的耗時計算,主執行緒往往早于子執行緒結束,這時,如果主執行緒想等待子執行緒執行結束之后再結束,比如子執行緒處理一個資料,主執行緒要取得這個資料,就要用 join() 方法,
sleep(long) 方法在睡眠時不釋放物件鎖,而 join() 方法在等待的程序中釋放物件鎖,
yield() 方法會臨時暫停當前正在執行的執行緒,來讓有同樣優先級的正在等待的執行緒有機會執行,如果沒有正在等待的執行緒,或者所有正在等待的執行緒的優先級都比較低,那么該執行緒會繼續運行,執行了yield方法的執行緒什么時候會繼續運行由執行緒調度器來決定,
volatile
當把變數宣告為 volatile 型別后,編譯器與運行時都會注意到這個變數是共享的,因此不會將該變數上的操作與其他記憶體操作一起重排序,volatile 變數不會被快取在暫存器或者對其他處理器不可見的地方,JVM 保證了每次讀變數都從記憶體中讀,跳過 CPU cache 這一步,因此在讀取 volatile 型別的變數時總會回傳最新寫入的值,
當一個變數定義為 volatile 之后,將具備以下特性:
- 保證此變數對所有的執行緒的可見性,不能保證它具有原子性(可見性,是指執行緒之間的可見性,一個執行緒修改的狀態對另一個執行緒是可見的)
- 禁止指令重排序優化
- volatile 的讀性能消耗與普通變數幾乎相同,但是寫操作稍慢,因為它需要在本地代碼中插入許多記憶體屏障指令來保證處理器不發生亂序執行
AtomicInteger 中主要實作了整型的原子操作,防止并發情況下出現例外結果,其內部主要依靠 JDK 中的 unsafe 類操作記憶體中的資料來實作的,volatile 修飾符保證了 value 在記憶體中其他執行緒可以看到其值得改變,CAS(Compare and Swap)操作保證了 AtomicInteger 可以安全的修改value 的值,
synchronized
當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多只有一個執行緒執行該段代碼,
在 Java 中,每個物件都會有一個 monitor 物件,這個物件其實就是 Java 物件的鎖,通常會被稱為“內置鎖”或“物件鎖”,類的物件可以有多個,所以每個物件有其獨立的物件鎖,互不干擾,針對每個類也有一個鎖,可以稱為“類鎖”,類鎖實際上是通過物件鎖實作的,即類的 Class 物件鎖,每個類只有一個 Class 物件,所以每個類只有一個類鎖,
Monitor 是執行緒私有的資料結構,每一個執行緒都有一個可用 monitor record 串列,同時還有一個全域的可用串列,每一個被鎖住的物件都會和一個 monitor 關聯,同時 monitor 中有一個 Owner 欄位存放擁有該鎖的執行緒的唯一標識,表示該鎖被這個執行緒占用,Monitor 是依賴于底層的作業系統的 Mutex Lock(互斥鎖)來實作的執行緒同步,
根據獲取的鎖分類
獲取物件鎖
- synchronized(this|object) {}
- 修飾非靜態方法
獲取類鎖
- synchronized(類.class) {}
- 修飾靜態方法
原理
同步代碼塊:
- monitorenter 和 monitorexit 指令實作的
同步方法
- 方法修飾符上的 ACC_SYNCHRONIZED 實作
Lock
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
| 方法 | 說明 |
|---|---|
lock() | 用來獲取鎖,如果鎖被其他執行緒獲取,處于等待狀態,如果采用 Lock,必須主動去釋放鎖,并且在發生例外時,不會自動釋放鎖,因此一般來說,使用Lock必須在 try{}catch{} 塊中進行,并且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生, |
lockInterruptibly() | 通過這個方法去獲取鎖時,如果執行緒正在等待獲取鎖,則這個執行緒能夠回應中斷,即中斷執行緒的等待狀態, |
tryLock() | tryLock 方法是有回傳值的,它表示用來嘗試獲取鎖,如果獲取成功,則回傳 true,如果獲取失敗(即鎖已被其他執行緒獲取),則回傳 false,也就說這個方法無論如何都會立即回傳,在拿不到鎖時不會一直在那等待, |
tryLock(long,TimeUnit) | 與 tryLock 類似,只不過是有等待時間,在等待時間內獲取到鎖回傳 true,超時回傳 false, |
鎖的分類
悲觀鎖、樂觀鎖
悲觀鎖認為自己在使用資料的時候一定有別的執行緒來修改資料,因此在獲取資料的時候會先加鎖,確保資料不會被別的執行緒修改,Java 中,synchronized 關鍵字和 Lock 的實作類都是悲觀鎖,悲觀鎖適合寫操作多的場景,先加鎖可以保證寫操作時資料正確,
而樂觀鎖認為自己在使用資料時不會有別的執行緒修改資料,所以不會添加鎖,只是在更新資料的時候去判斷之前有沒有別的執行緒更新了這個資料,如果這個資料沒有被更新,當前執行緒將自己修改的資料成功寫入,如果資料已經被其他執行緒更新,則根據不同的實作方式執行不同的操作(例如報錯或者自動重試),樂觀鎖在 Java 中是通過使用無鎖編程來實作,最常采用的是 CAS 演算法,Java 原子類中的遞增操作就通過 CAS 自旋實作,樂觀鎖適合讀操作多的場景,不加鎖的特點能夠使其讀操作的性能大幅提升,
自旋鎖、適應性自旋鎖
阻塞或喚醒一個 Java 執行緒需要作業系統切換 CPU 狀態來完成,這種狀態轉換需要耗費處理器時間,如果同步代碼塊中的內容過于簡單,狀態轉換消耗的時間有可能比用戶代碼執行的時間還要長,
在許多場景中,同步資源的鎖定時間很短,為了這一小段時間去切換執行緒,執行緒掛起和恢復現場的花費可能會讓系統得不償失,如果物理機器有多個處理器,能夠讓兩個或以上的執行緒同時并行執行,我們就可以讓后面那個請求鎖的執行緒不放棄CPU的執行時間,看看持有鎖的執行緒是否很快就會釋放鎖,
而為了讓當前執行緒“稍等一下”,我們需讓當前執行緒進行自旋,如果在自旋完成后前面鎖定同步資源的執行緒已經釋放了鎖,那么當前執行緒就可以不必阻塞而是直接獲取同步資源,從而避免切換執行緒的開銷,這就是自旋鎖,
自旋鎖本身是有缺點的,它不能代替阻塞,自旋等待雖然避免了執行緒切換的開銷,但它要占用處理器時間,如果鎖被占用的時間很短,自旋等待的效果就會非常好,反之,如果鎖被占用的時間很長,那么自旋的執行緒只會白浪費處理器資源,所以,自旋等待的時間必須要有一定的限度,如果自旋超過了限定次數(默認是 10 次,可以使用 -XX:PreBlockSpin 來更改)沒有成功獲得鎖,就應當掛起執行緒,
自旋鎖的實作原理同樣也是 CAS,AtomicInteger 中呼叫 unsafe 進行自增操作的原始碼中的 do-while 回圈就是一個自旋操作,如果修改數值失敗則通過回圈來執行自旋,直至修改成功,
死鎖
當前執行緒擁有其他執行緒需要的資源,當前執行緒等待其他執行緒已擁有的資源,都不放棄自己擁有的資源,
參考型別
強參考 > 軟參考 > 弱參考
| 參考型別 | 說明 |
|---|---|
| StrongReferenc(強參考) | 當一個物件具有強參考,那么垃圾回收器是絕對不會的回收和銷毀它的,非靜態內部類會在其整個生命周期中持有對它外部類的強參考 |
| WeakReference (弱參考) | 在垃圾回收器運行的時候,如果對一個物件的所有參考都是弱參考的話,該物件會被回收 |
| SoftReference(軟參考) | 如果一個物件只具有軟參考,若記憶體空間足夠,垃圾回收器就不會回收它;如果記憶體空間不足了,才會回收這些物件的記憶體 |
| PhantomReference(虛參考) | 一個只被虛參考持有的物件可能會在任何時候被 GC 回收,虛參考對物件的生存周期完全沒有影響,也無法通過虛參考來獲取物件實體,僅僅能在物件被回收時,得到一個系統通知(只能通過是否被加入到 ReferenceQueue 來判斷是否被GC,這也是唯一判斷物件是否被 GC 的途徑), |
動態代理
示例:
// 定義相關介面
public interface BaseInterface {
void doSomething();
}
// 介面的相關實作類
public class BaseImpl implements BaseInterface {
@Override
public void doSomething() {
System.out.println("doSomething");
}
}
public static void main(String args[]) {
BaseImpl base = new BaseImpl();
// Proxy 動態代理實作
BaseInterface proxyInstance = (BaseInterface) Proxy.newProxyInstance(base.getClass().getClassLoader(), base.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("doSomething")) {
method.invoke(base, args);
System.out.println("do more");
}
return null;
}
});
proxyInstance.doSomething();
}
Proxy.java
public class Proxy implements java.io.Serializable {
// 代理類的快取
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
···
// 生成代理物件方法入口
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
// 找到并生成相關的代理類
Class<?> cl = getProxyClass0(loader, intfs);
// 呼叫代理類的構造方法生成代理類實體
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
cons.setAccessible(true);
}
return cons.newInstance(new Object[]{h});
}
···
}
···
// 定義和回傳代理類的工廠類
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 所有代理類的前綴
private static final String proxyClassNamePrefix = "$Proxy";
// 用于生成唯一代理類名稱的下一個數字
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
···
String proxyPkg = null; // 用于定義代理類的包名
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 確保所有 non-public 的代理介面在相同的包里
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// 如果沒有 non-public 的代理介面,使用默認的包名
proxyPkg = "";
}
{
List<Method> methods = getMethods(interfaces);
Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
validateReturnTypes(methods);
List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);
Method[] methodsArray = methods.toArray(new Method[methods.size()]);
Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);
// 生成代理類的名稱
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// Android 特定修改:直接呼叫 native 方法生成代理類
return generateProxy(proxyName, interfaces, loader, methodsArray,
exceptionsArray);
// JDK 使用的 ProxyGenerator.generateProxyClas 方法創建代理類
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} ···
}
}
···
// 最終呼叫 native 方法生成代理類
@FastNative
private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
ClassLoader loader, Method[] methods,
Class<?>[][] exceptions);
}
ProxyGenerator.java
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
元注解
@Retention:保留的范圍,可選值有三種,
| RetentionPolicy | 說明 |
|---|---|
| SOURCE | 注解將被編譯器丟棄(該型別的注解資訊只會保留在原始碼里,原始碼經過編譯后,注解資訊會被丟棄,不會保留在編譯好的class檔案里),如 @Override |
| CLASS | 注解在class檔案中可用,但會被 VM 丟棄(該型別的注解資訊會保留在原始碼里和 class 檔案里,在執行的時候,不會加載到虛擬機中),請注意,當注解未定義 Retention 值時,默認值是 CLASS, |
| RUNTIME | 注解資訊將在運行期 (JVM) 也保留,因此可以通過反射機制讀取注解的資訊(原始碼、class 檔案和執行的時候都有注解的資訊),如 @Deprecated |
@Target:可以用來修飾哪些程式元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER等,未標注則表示可修飾所有
@Inherited:是否可以被繼承,默認為false
@Documented:是否會保存到 Javadoc 檔案中
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/328041.html
標籤:其他
