### Java網絡編程(Socket)
#### 1. Socket及其通訊原理?
Socket是什麼?
* Socket,又叫作套接字。網絡上的兩個程序經過一個雙向的通訊鏈接實現數據的交換,這個鏈接的一端稱爲一個socket。
* Socket沒有一個具體的實體,只是描述計算機之間完成通信的一種抽象功能,能夠理解爲交通工具,有了這個交通工具,你的數據就能夠在各個城市(主機)之間穿梭。
Socket的通訊原理:

* 主機A的應用程序要能和主機B的應用程序通訊,必須經過Socket創建鏈接,而創建Socket鏈接必須須要底層TCP/IP協議來創建TCP鏈接。
* 創建TCP鏈接須要底層IP協議來尋址網絡中的主機。網絡層使用的IP協議能夠幫助咱們根據IP地址來找到目標主機。
* 一臺主機上可能運行着多個應用程序,與指定的應用程序通訊就要經過端口號來指定。
* 這樣就能夠經過一個Socket實例**惟一表明一個主機上的一個應用程序**的通訊鏈路了。
#### 2. Socket編程基礎(TCP編程爲例子)

Server端:
```java
public class TCPServer {
public static void main(String[] args) throws Exception {
/**
* 基於TCP協議的Socket通訊,實現用戶登陸,服務端
*/
//一、建立一個服務器端Socket,即ServerSocket,指定綁定的端口,並監聽此端口
ServerSocket serverSocket = new ServerSocket(10086);//1024-65535的某個端口
//二、調用accept()方法開始監聽,等待客戶端的鏈接
Socket socket = serverSocket.accept();
//三、獲取輸入流,並讀取客戶端信息
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("我是服務器,客戶端說:" + info);
}
socket.shutdownInput();//關閉輸入流
//四、獲取輸出流,響應客戶端的請求
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("歡迎您!");
pw.flush();
//五、關閉資源
pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();
serverSocket.close();
}
}
```
Client端:
```java
public class TCPClient {
public static void main(String[] args) throws Exception {
//客戶端
//一、建立客戶端Socket,指定服務器地址和端口
Socket socket = new Socket("localhost", 10086);
//二、獲取輸出流,向服務器端發送信息
OutputStream os = socket.getOutputStream();//字節輸出流
PrintWriter pw = new PrintWriter(os);//將輸出流包裝成打印流
pw.write("用戶名:admin;密碼:123");
pw.flush();
socket.shutdownOutput();
//三、獲取輸入流,並讀取服務器端的響應信息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("我是客戶端,服務器說:" + info);
}
//四、關閉資源
br.close();
is.close();
pw.close();
}
}
```
#### 3. 參考文章
有關Socket的多線程改進、UDP編程,能夠參考:
[Java Socket編程----通訊是這樣煉成的](https://www.cnblogs.com/rocomp/p/4790340.html)
[面試記錄第二十三節——(java網絡編程、BIO/NIO/AI0)](https://blog.csdn.net/bobo89455100/article/details/78250104)
### Java中的BIO,NIO,AIO
#### 1. 理解同步/異步、阻塞/非阻塞
* 同步和異步是針對應用程序和內核的交互而言的:
* 同步:用戶進程觸發IO操做並等待或者輪詢的去查看IO操做是否就緒。
* 異步:用戶進程觸發IO操做之後便開始作本身的事情(其實是委託給操做系統來進行處理),而當IO操做已經就緒的時候會獲得系統的通知。
* 阻塞和非阻塞是針對於進程在進行IO操做的時候,是否須要等待完成:
* 阻塞:讀取或者寫入函數將一直等待。
* 非阻塞:讀取或者寫入函數會當即返回一個狀態值。
#### 2. Java中的BIO,NIO,AIO分別是什麼,應用場景是什麼?
##### BIO
* 概念:同步阻塞式IO,服務器端與客戶端三次握手創建鏈接後,**一個請求鏈路建立一個線程**進行**面向流**的通訊
* 特色:
* 面向流
* 一個請求對應一個線程
* 在任何一端出現網絡性能問題時都影響另外一端,沒法知足高併發高性能的需求
* 若是這個鏈接不作任何事情時,會形成沒必要要的線程開銷,可使用線程池進行改善
* 應用場景:BIO方式適用於鏈接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4之前的惟一選擇,但程序直觀簡單易理解
##### NIO
* 概念:同步非阻塞式IO,基於事件驅動的思想來實現了一個**多路複用器**,多路複用Reactor模式,當多路複用器輪詢到有I/O請求時才啓動一個線程進行處理
* 特色:
* 面向緩衝區
* 多個請求對應一個或者少許線程
* 讀寫過程是同步的,線程會等待。因此對於那些讀寫過程時間長的(例若有JDBC操做),NIO就不太適合。
* 應用場景:NIO方式適用於鏈接數目多且鏈接比較短(輕操做)的架構,好比聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持
##### AIO
* 概念:異步非阻塞IO,基於unix事件驅動,不須要多路複用器對註冊通道進行輪詢,採用Proactor設計模式。**當IO就緒後會收到操做系統的通知**
* 特色:
* 一個有效請求對應一個線程
* 當IO就緒後會收到操做系統的通知,線程沒必要等待。因此AIO可以勝任那些重量級,讀寫過程長的任務。
* 應用場景:AIO方式使用於鏈接數目多且鏈接比較長(重操做)的架構,好比相冊服務器,充分調用OS參與併發操做,編程比較複雜,JDK7開始支持
#### 參考文章
[BIO與NIO、AIO的區別(這個容易理解)](https://blog.csdn.net/skiof007/article/details/52873421)
[深刻分析 Java I/O 的工做機制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/)
[Java NIO 系列教程](http://ifeve.com/java-nio-all/)
[Java NIO原理圖文分析及代碼實現](http://weixiaolu.iteye.com/blog/1479656)
### 多線程基礎
#### 1. Java中實現多線程的兩種方式
兩種方式:
1. 實現Runnable接口,而後添加到Thread中,經過Thread的對象開啓線程。(實際開發比較經常使用)
2. 繼承Thread類,複寫run方法,建立對象而後開啓線程。
聯繫與區別:
* 相同點:無論是用Thread仍是用Runnable,都必須建立Thread對象產生線程,而後調用Thread對象的start方法來開啓線程。
* 不一樣點1:Thread它有一個缺點,java當中是單繼承的模式,它不一樣於C++,因此說爲了彌補這一缺陷,咱們java當中經過實現Runnable接口來彌補次缺點,同時用接口這個方式要比繼承Thread更靈活。
* 不一樣點2:繼承Thread,若是想執行多個任務,就必須產生多個相應的線程。可是實現Runnable不一樣,只須要建立一個這個類的實例,而後用這個實例對象產生多個線程,你就實現了整個資源的共享。
#### 2. Thread的start方法和run方法的區別
* start方法:它是開啓一個線程的方法,這個時候你不須要等待run方法體中執行完畢,你就能夠繼續執行其它代碼。而且經過start方法後,線程變成了可運行狀態,而不是執行狀態,何時運行線程代碼,這就須要操做系統本身決定。
* run方法:不會有多線程效果,就是通常的方法調用。run方法結束了,這個線程也就結束了。
#### 3. synchronized關鍵字
基本用法
線程間的同步就是使用synchronized來實現的。synchronized關鍵字的基本用法以下:
* 鎖住某個對象
* 鎖住某個類及其全部對象
```java
//修飾代碼塊(給對象加鎖),修飾一個代碼塊,被修飾的代碼塊稱爲同步語句塊,其做用的範圍是大括號{}括起來的代碼,做用的對象是調用這個代碼塊的對象。
synchronized (this/obj) {
}
//修飾一個方法,被修飾的方法稱爲同步方法,其做用的範圍是整個方法,做用的對象是調用這個方法的對象。等價於synchronized (this)
public synchronized void test() {
}
//修飾一個靜態的方法,其做用的範圍是整個靜態方法,做用的對象是這個類的全部對象。
public synchronized static void test(){
}
//修飾一個類,其做用的範圍是synchronized後面括號括起來的部分,做用的對象是這個類的全部對象。
synchronized(ClassName.class) {
}
```
synchronized原理:
synchronized獲取和釋放都有一個監聽器,若是兩個線程都是用同一個監聽器(即相同鎖),這個監聽器就能夠強制在在同一時間只有一個線程處理這個代碼塊。
#### 4. volatile關鍵字
volatile只能在線程內存和主內存之間同步一個變量的值。與不使用volatile、使用synchronized的對好比下:
```java
//線程內存和主內存都有一份變量的值,線程不安全
int i1; int geti1() { return i1; }
//volatile類型的變量不容許線程從主內存中將變量的值拷貝到本身的存儲空間。volatile類型的變量的值在全部線程同步。
//因爲線程存取或更改本身的數據拷貝有更高的效率,因此volatile類型變量在性能上有所消耗。
volatile int i2; int geti2() { return i2; }
//synchronized除了代碼塊同步,synchronized還能使內存同步。
//synchronized則同步在線程內存和主內存之間的全部變量的值。
int i3; synchronized int geti3() { return i3; }
```
#### 5. volatile和synchronized區別
* volatile只能在線程內存和主內存之間同步一個變量值,而synchronized能夠再線程內存和主內存之間同步全部的值,並經過鎖管理全部的變量。可是synchronized更消耗內存。
* volatile只能使用在變量上、synchronized則可使用在對象、類、方法上面等。
#### 6. synchronized和lock區別
* 用法上:synchronized是須要在同步中加入這個控制,lock須要指定起始位置和終止位置。
* 性能上:synchronized是脫離咱們Java虛擬機執行的,lock是咱們java本身寫的代碼,因此synchronized相比lock性能差,由於synchronized是一個重量級操做,有些耗性能。
* 採用機制方面:synchronized採用的是cpu的悲觀鎖機制,也就是線程獲取的是獨佔的鎖,意味着其餘線程只能依靠阻塞等線程釋放鎖。而咱們的lock是cpu的樂觀鎖機制,也就是每個不加鎖,而是假設沒有衝突的狀況下去完成某項操做。若是有衝突、失敗,他就重試直到操做成功位置。
#### 7. sleep和wait區別
* 在等待時wait會釋放鎖;而sleep一直持有鎖,不會改變線程持有鎖的狀況。
* Wait一般被用於線程間交互,sleep一般被用於暫停執行。
#### 8. wait和notify機制區別
wait是定義在咱們object大類當中,須要在同步代碼塊中來調用,調用完以後他會釋放鎖,並進入鎖對象的等待中,他須要其餘線程調用notify這個方法釋放鎖以後,他才能從新去競爭鎖。
#### 參考文章
[關於volatile和synchronized](https://blog.csdn.net/majorboy/article/details/475811)
[Java中Synchronized的用法](https://blog.csdn.net/luoweifu/article/details/46613015)
### 線程池
#### 1. 線程池的好處
1. 下降資源的消耗,由於能夠重複利用咱們已經建立好的線程,下降不斷建立和銷燬線程所帶來的資源消耗。
2. 提升響應速度,當任務達到必定的數量時,任務不須要等到線程建立就當即執行(由於有建立好的能夠循環利用)。
3. 提升線程的可管理性,畢竟線程仍是比較稀缺的資源,尤爲是手機當中,若是你無限制的建立線程,不只僅會消耗系統資源,同時還會下降系統的穩定性,使用線程池能夠進行統一的分配,能夠合理的的利用線程池,也提升了線程池的管理性。
#### 2. 線程池的參數
```java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
```
傳遞參數講解:
* corePoolSize:線程池的大小
* maximumPoolSize:最大線程池大小
* keepAliveTime:線程池中超過corePoolSize數目的空閒線程最大存活時間;能夠allowCoreThreadTimeOut(true)使得核心線程有效時間
* unit:keepAliveTime時間單位
* workQueue:阻塞任務隊列
* threadFactory:新建線程工廠
* RejectedExecutionHandler:當提交任務數超過maximumPoolSize+workQueue之和時,任務會交給RejectedExecutionHandler來處理(保護策略)
重點講解:
其中比較容易讓人誤解的是:corePoolSize,maximumPoolSize,workQueue之間關係。
1. 當線程池小於corePoolSize時,新提交任務將建立一個新線程執行任務,即便此時線程池中存在空閒線程。
2. 當線程池達到corePoolSize時,新提交任務將被放入workQueue中,等待線程池中任務調度執行
3. 當workQueue已滿,且maximumPoolSize>corePoolSize時,新提交任務會建立新線程執行任務
4. 當提交任務數超過maximumPoolSize時,新提交任務由RejectedExecutionHandler處理
5. 當線程池中超過corePoolSize線程,空閒時間達到keepAliveTime時,關閉空閒線程
6. 當設置allowCoreThreadTimeOut(true)時,線程池中corePoolSize線程空閒時間達到keepAliveTime也將關閉
7. 最後調用.execute()提交任務,不過.execute()沒有返回值,因此不能判斷這個任務是否被線程池執行成功,這是隱患之處。
#### 3. 線程池的工做流程
1. 首先線程池判斷,基本線程池是否已滿若是線程池已滿,進入下個流程。若是沒有滿,咱們就會建立一個工做(子線程)線程來執行該任務。
2. 若是線程池工做隊列滿了,若是沒有滿,咱們就會將提交的任務存儲到該工做隊列中,來進行相應的策略處理;若是工做隊列滿了,進入下一個流程。
3. 最後線程池判斷整個線程池是否已滿。若是整個線程池已經滿了,就會交給咱們的RejectedExecutionHandler處理,能夠拋出異常也能夠忽略這個問題。若是沒有滿就會建立一個新的工做線程。
### Android異常體系
#### 1. Android異常體系

* 常見的Android崩潰有兩類,一類是Java Exception異常,一類是Native Signal異常。咱們將圍繞這兩類異常進行。對於不少基於Unity、Cocos平臺的遊戲,還會有C#、JavaScript、Lua等的異常,這裏不作討論。
* Throwable類是全部Java異常和錯誤的父類,有兩個子類Error(錯誤)和Exception(異常)。
* Error是程序沒法處理的錯誤,虛擬機通常會選擇線程終止。這種錯誤沒法恢復或不可能捕獲,將致使應用程序中斷,一般應用程序沒法處理這些錯誤,所以應用程序不該該捕獲Error對象,也無須在其throws子句中聲明該方法拋出任何Error或其子類。
* Exception是程序自己能夠處理的異常,這種異常分兩大類運行時異常和非運行時異常。程序中應當儘量去處理這些異常。
* 運行時異常都是RuntimeException類及其子類異常,這些異常是編譯器不檢查的異常,程序中能夠選擇捕獲處理,也能夠不處理。這些異常通常是由程序邏輯錯誤引發的,程序應該從邏輯角度儘量避免這類異常的發生。
* 非運行時異常是RuntimeException之外的異常,類型上都屬於Exception類及其子類。從程序語法角度講是必須進行處理的異常,若是不處理,程序就不能編譯經過。
#### 2. 列舉常見的異常
* 常見的Error:StackOverflowError、OutOfMemoryError、ThreadDeath、ClassFormatError、AbstractMethodError、AssertionError
* 常見的RuntimeException:NullPointerException、ClassCastException、IllegalArgumentException、ArithmeticException、IndexOutOfBoundsException、SecurityException、NumberFormatException
* 常見的非RuntimeException:IOException、FileNotFoundException、、通常用戶自定義的異常
#### 3. 異常處理機制關鍵字的運用
異常處理機制的運用:
* try···catch語句
* finally語句:任何狀況下都必須執行的代碼,保持程序的健壯性
* throws子句:聲明可能會出現的異常
* throw語句:拋出異常
throw與throws關鍵字的區別?
* throw關鍵字是用於方法體內部,用來拋出一個Throwable類型的異常,須要調用者進行捕獲處理。
* throws關鍵字用於方法體外部的方法聲明部分,用來聲明方法可能會拋出某些異常,表示本方法沒法處理本異常。
final、finally和finalize關鍵字的區別?
* final修飾符(關鍵字)
* 被final修飾的類,就意味着不能再派生出新的子類,不能做爲父類而被子類繼承。所以一個類不能既被abstract聲明,又被final聲明。
* 將變量或方法聲明爲final,能夠保證他們在使用的過程當中不被修改。被聲明爲final的變量必須在聲明時給出變量的初始值,而在之後的引用中只能讀取。被final聲明的方法也一樣只能使用,不能被覆寫。
* finally是在異常處理時提供finally塊來執行任何清除操做。無論有沒有異常被拋出、捕獲,finally塊都會被執行,保持了程序的健壯性。
* finalize是Object類中的方法。Java技術容許使用finalize方法在垃圾收集器將對象從內存中清除出去以前作必要的清理工做。這個方法是由垃圾收集器在肯定這個對象沒有被引用時,被垃圾收集器清楚以前對這個對象調用的。
#### 4. 異常處理機制的原理:
* Java虛擬機用方法調用棧(method invocation stack)來跟蹤每一個線程中一系列的方法調用過程。該堆棧保存了每一個調用方法的本地信息(好比方法的局部變量)。
* 每一個線程都有一個獨立的方法調用棧。對於Java應用程序的主線程,堆棧底部是程序的入口方法main()。
* 當一個新方法被調用時,Java虛擬機把描述該方法的棧結構置入棧頂,位於棧頂的方法爲正在執行的方法。
* 當一個方法正常執行完畢,Java虛擬機會從調用棧中彈出該方法的棧結構,而後繼續處理前一個方法。
* 若是在執行方法的過程當中拋出異常,則Java虛擬機必須找到能捕獲該異常的catch代碼塊。它首先查看當前方法是否存在這樣的catch代碼塊,若是存在,那麼就執行該catch代碼塊;不然,Java虛擬機會從調用棧中彈出該方法的棧結構,繼續到前一個方法中查找合適的catch代碼塊。在回溯過程當中,若是Java虛擬機在某個方法中找到了處理該異常的代碼塊,則該方法的棧結構將成爲棧頂元素,程序流程將轉到該方法的異常處理代碼部分繼續執行。
* 當Java虛擬機追溯到調用棧的底部的方法時,若是仍然沒有找處處理該異常的代碼塊,按如下步驟處理:
1. 調用異常對象的printStackTrace()方法,打印來自方法調用棧的異常信息。
2. 若是該線程不是主線程,那麼終止這個線程,其餘線程繼續正常運行。若是該線程是主線程(即方法調用棧的底部爲main()方法),那麼整個應用程序被終2。
#### 5. 特殊的異常處理流程
* finally語句塊不被執行的特殊狀況:
* try語句以前就已經返回了
* 在finally執行以前若是經過System.exit退出了程序,finally語句塊不被執行:
```java
public static void test1() {
try {
//System.exit(0);
int i = 1/0;
} catch (Exception e) {
System.exit(0);
}finally {
System.out.println("finally");//不被打印
}
}
```
* 當全部的非守護線程停止時,守護線程被kill掉,守護線程中未被執行的finally代碼塊是不會執行的。
* try中有return,finally會被執行:try中經過return語句返回,先執行return語句的表達式計算結果,而後執行finally語句塊,最後才返回。
* finally不能經過變量賦值的方式改變返回結果:return語句已經將表達式計算完成而且將結果賦值給了一個不知名的臨時變量,finally語句塊中即便改變了return中相關表達式的值,可是沒有經過return改變臨時返回變量的值,可是對最終的返回結果沒有任何影響。
```java
public static int test2() {
int a = 0;
try {
return a = 1;
} catch (Exception e) {
}finally {
a = 2;
System.out.println("finally");//return表達式計算以後,結果返回以前,這裏被打印
}
return 0;
}
//程序最終返回1
```
* 建議不要在finally代碼塊中使用return語句,覺得它會致使如下兩種潛在的嚴重錯誤。
* finally中的return會覆蓋try或catch代碼塊的return語句,形成程序的不安全。
```java
public static int test3() {
int a = 0;
try {
return a = 1;
} catch (Exception e) {
}finally {
a = 2;
System.out.println("finally");
return a;
}
}
//程序返回2
```
* 丟失異常:若是catch代碼塊中有throw語句拋出異常,因爲先執行了finally代碼塊,又由於finally代碼塊中有return語句,因此方法退棧,catch代碼塊中的異常就沒有被捕獲處理。
```java
public static void test4() {
try {
int a = 1/0;
} catch (Exception e) {
System.out.println("catch");
int b = 1 / 0;
}finally {
System.out.println("finally");
return;
}
}
```
#### 6. Android平臺的崩潰捕獲機制及實現
使用UncaughtExceptionHandler捕獲Uncaught異常:
* 沒有捕獲住的異常,即Uncaught異常,都會致使應用程序崩潰。那麼面對崩潰,咱們是否能夠作些什麼呢?好比程序退出前,彈出個性化對話框,而不是默認的強制關閉對話框,或者彈出一個提示框安慰一下用戶,甚至重啓應用程序等。其實Java提供了一個接口給咱們,能夠完成這些,這就是UncaughtExceptionHandler。
```java
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
//...
}
});
```
對Native代碼的崩潰,能夠經過調用sigaction()註冊信號處理函數來完成捕獲:
* 熟悉Linux開發的人都知道,so庫通常經過gcc/g++編譯,崩潰時會產生信號異常。Android底層是Linux系統,因此so庫崩潰時也會產生信號異常。經過調用sigaction()註冊信號處理函數能夠捕獲Android Native崩潰。
#### 參考文章
[java學習筆記《面向對象編程》——異常處理](https://blog.csdn.net/dnxyhwx/article/details/6975087)
[開發中的異常和錯誤總結](https://blog.csdn.net/zhouxingxing1992/article/details/70236540)
[Java中final、finally和finalize的區別](https://blog.csdn.net/cyl101816/article/details/67640843)
[finally代碼塊不被執行的狀況總結](https://www.cnblogs.com/fudashi/p/6498205.html)
[Android平臺的崩潰捕獲機制及實現](https://blog.csdn.net/tangxiaoyin/article/details/80121547)
### 註解
#### 1. 註解相關的基本概念
什麼是註解?
* 概念:註解就是Java提供了一種元程序中的元素關係任何信息和任何元數據(metadata)的途徑和方法。註解是一個接口,程序能夠經過反射來獲取指定程序元素的Annotation對象,而後經過Annotation對象來獲取註解裏面的元數據。
* 基本做用:註解是JDK5.0及之後版本引入的。它能夠用於建立文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查等。
* 基本原則:Annotation不能影響程序代碼的執行,不管增長、刪除Annotation,代碼都始終如一的執行。另外,儘管一些annotation經過java的反射api方法在運行時被訪問,而java語言解釋器在工做時忽略了這些annotation。
什麼是metadata(元數據)?
* 元數據是描述數據的數據,以標籤的形式存在於Java代碼中。
* 經過元數據能夠編寫文檔、代碼分析、編譯檢查
* 元數據描述的信息是類型安全的,即元數據內部的字段都是有明確類型的。
* 元數據須要編譯器以外的工具額外的處理來生成其它的程序部件。
* 元數據能夠只存在於Java源代碼級別,也能夠存在於編譯以後的Class文件內部。
#### 2. 註解的分類
根據註解使用方法和用途,咱們能夠將Annotation分爲三類:
* JDK內置系統註解
* @Override:用於修飾此方法覆蓋了父類的方法
* @Deprecated:用於修飾已通過時的方法
* @SuppressWarnings:用於通知Java編譯器禁止特定的編譯警告
* 元註解
* @Target:用於描述註解的使用範圍。Annotation可被用於packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。
* @Retention:表示須要在什麼級別保存該註釋信息,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效)。取值以下:
* SOURCE:在源文件中有效,僅出如今源代碼中,而被編譯器丟棄
* CLASS:在class文件中有效,可能會被虛擬機忽略
* RUNTIME:在運行時有效,class被裝載時將被讀取(請注意並不影響class的執行,由於Annotation與class在使用上是被分離的)
* @Documented:用於描述其它類型的Annotation應該被做爲被標註的程序成員的公共API,所以能夠被例如javadoc此類的工具文檔化。
* @Inherited:元註解是一個標記註解,@Inherited闡述了某個被標註的類型是被繼承的(註解能夠傳遞到子類)。
* 自定義註解
#### 3. 註解處理器
註解如何被處理?
* 經過註解處理器來獲取和處理註解(核心原理是反射機制)
* 先經過反射API獲取class的元素(域、方法等),而後經過反射API中與註解相關的4個核心API來獲取、處理註解:
* boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,不然返回false.
* <T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定類型的註解,若是該類型註解不存在,則返回null。
* Annotation[] getAnnotations():返回該程序元素上存在的全部註解。
* Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的全部註解。與此接口中的其餘方法不一樣,該方法將忽略繼承的註解(@Inherited)。
#### 4. Android Support Annotation
* 空類型安全註解:@Nullable、@NonNull
* 資源類型註解:主要用於標記某個整型參數是某某資源的ID
* 類型定義註解:@IntDef,主要用於取代枚舉,保證了調用函數的時候必須傳入指定參數,如果非法在編譯時就會報異常。=
* 線程註解:@MainThread(@UiThread)、@WorkerThread、@BinderThread
* 值範圍註解:當函數參數的取值在必定範圍時,可使用註解來防止調用者傳入錯誤的參數
* 權限註解:爲了在編譯時及時發現權限的缺失,可使用@RequiresPermission註解。
* 重寫函數註解:若是API容許重寫某個函數的時候,能夠加註解@CallSuper來提示開發者如果重寫不調用super就會報錯。
* 混淆註解:@keep是用來標記在Proguard混淆過程當中不須要混淆的類或者方法。
#### 5. 參考文章
[深刻理解Java:註解(Annotation)基本概念](http://www.cnblogs.com/peida/archive/2013/04/23/3036035.html)
[深刻理解Java:註解(Annotation)自定義註解入門](http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html)
[深刻理解Java:註解(Annotation)--註解處理器](http://www.cnblogs.com/peida/archive/2013/04/26/3038503.html)
[Android進階系列之Support Annotation Library使用詳解](https://blog.csdn.net/sw5131899/article/details/53842362)
### 類加載器
#### 1. 類加載器是什麼?
Java程序並非一個原生的可執行文件,而是由許多獨立的class文件組成,每個class文件對應一個Java類。這些類文件並不是當即所有裝入內存的,而是根據程序須要**動態**裝入內存。
**類加載器是用來動態加載class文件到內存當中的。**
#### 2. Java原生類加載器的分類

從上圖咱們就能夠看出類加載器之間的父子關係(**注意不是類的繼承關係**)和管轄範圍。
* BootStrap ClassLoader是最頂層的類加載器,站在虛擬機的角度來講屬於啓動類加載器,它是由C++編寫而成,而且已經內嵌到JVM中了,主要用來讀取Java的核心類庫JRE/lib/rt.jar
* Extension ClassLoader是是用來讀取Java的擴展類庫,讀取JRE/lib/ext/*.jar
* App ClassLoader是用來讀取CLASSPATH指定的全部jar包或目錄的類文件
* Custom ClassLoader是用戶自定義編寫的,能夠本身增長一些例如字節碼加密解密等功能,它用來讀取指定類文件
#### 3. Android類加載器的分類

Android中的ClassLoader的總體架構**繼承關係**如上圖所示。
* BootClassLoader:與Java中的Bootstrap ClassLoader相似,主要加載Android Framework中的字節碼文件。
* BaseDexClassLoader是PathClassLoader以及DexClassLoader的父類,PathClassLoader以及DexClassLoader的邏輯都在這個父類中實現。
* PathClassLoader:與Java中的App ClassLoader相似,主要加載已經安裝到系統中的APK中的字節碼文件。
* DexClassLoader:與Java中的Customer ClassLoader相似,主要加載自定義路徑下的APK或者JAR中的字節碼文件(Android中主要是指dex文件,即classes.dex)。經過DexClassLoader能夠實現插件化。
Android與Java原生Java類加載器最大的不一樣是什麼?
* Java原生類加載器是加載class文件
* Android類加載器是加載dex文件
#### 4. 雙親委託機制
ClassLoader的主要特性是雙親委託機制:
1. 即加載一個類的時候,先判斷已經存在的類是否被加載過,若是沒有,先去委託父親、祖宗類加載器去加載。
2. 若是連父親、祖宗全部類加載器都沒有加載到該類的話,那麼最終由本身加載。
3. 最終若是這個類都沒有合適的CLassLoader加載,那麼就會拋出ClassNotFoundException異常。
雙親委託機制的優勢:
* 實現類加載的共享功能,提高類加載的效率。
* 實現類加載的隔離功能,提高系統的安全性。好比,經過這種方式,系統的String類只能由系統的ClassLoader加載。
#### 5. 類加載過程

1. 加載:虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,並且在Java堆中也建立一個java.lang.Class類的對象,這樣即可以經過該對象訪問方法區中的這些數據。
2. 驗證:爲了確保Class文件中的字節流包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。不一樣的虛擬機對類驗證的實現可能會有所不一樣,但大體都會完成如下四個階段的驗證:文件格式的驗證、元數據的驗證、字節碼驗證和符號引用驗證。
3. 準備:爲類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中分配。
4. 解析:虛擬機將常量池中的符號引用替換爲直接引用的過程。
5. 初始化:執行初始化程序,把靜態變量初始化爲指定的值,執行static塊、構造器等等。
#### 6. 參考文章
[類加載](https://www.jianshu.com/p/37cad7a901b1)
[JVM 類加載機制詳解](http://www.importnew.com/25295.html)
[【深刻Java虛擬機】之四:類加載機制](https://blog.csdn.net/ns_code/article/details/17881581)
### 虛擬機的內存管理
#### 1. 虛擬機內存劃分
下圖爲虛擬機的總體結構:

* 靜態存儲區(方法區):主要存放**靜態數據、全局靜態數據和常量**。這塊內存在程序編譯時就已經分配好,而且在**程序整個運行期間都存在**。
* 棧區:當方法被執行時,**方法體內的局部變量,其中包括基礎數據類型、對象或者數組的引用**都在棧上建立,並在方法執行結束時這些局部變量所持有的內存將會自動被釋放。由於棧內存分配運算內置於處理器的指令集中,**效率很高,可是分配的內存容量有限**。
* 堆區:又稱**動態內存分配**,一般就是指在**程序運行時直接new出來的內存**,也就是對象或者數組的實例。這部份內存在不使用時將會**由Java垃圾回收器來負責回收**。
* 本地方法棧:專門爲native方法服務的,例如C、C++方法。
#### 2. 堆區和棧區的區別
* 棧:存放局部變量的基本數據類型和引用。生命週期隨方法而結束。內存空間有限,存取速度快。
* 堆:存放成員變量(包括基本數據類型,引用和引用的對象實體)、局部變量引用指向的對象。運行時動態分配的內存,由Java垃圾回收器來自動管理,存取速度比棧慢。
* 堆是不連續的內存區域,堆空間比較靈活也特別大;棧是一塊連續的內存區域,大小是由操做系統決定的。
* 堆的管理很麻煩,頻繁地new/remove會形成大量的內存碎片,這樣就會慢慢致使效率低下;棧是一種先進後出的數據結構,進出徹底不會產生碎片,運行效率高且穩定。
#### 3. 一些基本概念
* 引用變量:局部變量中的引用變量在棧中分配,引用變量的取值等於數組或者對象在堆內存中的首地址,能夠經過引用變量來訪問堆中的對象或者數組。(類的成員變量的引用變量存放在堆)
* 棧溢出(StackOverflow):當方法棧的深度大於JVM最大深度的時候,就會棧溢出。例如寫一個沒有退出的遞歸就會致使棧溢出(StackOverflow)。
* 內存泄漏:長生命週期的對象持有短生命週期的對象的引用頗有可能發生內存泄漏。
* 內存溢出(OOM):當新生代、老生代都滿了的話,就會致使內存溢出(OOM)。
* 新、老生代的動態調整:服務端開發須要掌握的一門技術,好比作即時通訊服務端,Message臨時對象比較多,那麼適當增長新生代,便於內存分配,加快對象的建立。作大型服務類程序,不須要頻繁建立,就能夠擴大老生代,達到對象常駐效果,保證服務穩定性。
#### 4. 面試常見例題
```java
public class Sample {
//由於類被new出來以後是存放在堆中的,全部成員變量所有存儲於堆中(包括基本數據類型,引用和引用的對象實體)
//由於它們屬於類,類對象終究是要被new出來使用的
int i1 = 0;
Sample s1 = new Sample();
public void method() {
//局部變量和引用變量都是存在於棧中,但引用變量指向的對象是存在於堆中
//由於它們屬於方法中的變量,生命週期隨方法而結束
int i2 = 1;
Sample s2 = new Sample();
}
public static void main(String[] args) {
//局部變量和引用變量都是存在於棧中,但引用變量指向的對象是存在於堆中
Sample s3 = new Sample();
}
}
```
#### 5. 參考文章
[內存泄漏與內存溢出總結](https://blog.csdn.net/u012792686/article/details/69666498)
[JVM內存管理機制和垃圾回收機制](https://blog.csdn.net/u011225629/article/details/49000311)
### 垃圾收集與回收算法
#### 1. 垃圾收集算法
* 引用計數法:被引用1次,計數器加1,沒有被引用的時候,則回收。可是引用計數法沒法解決對象以前相互引用的問題,所以已經廢棄。
* 可達性算法(根搜索算法):經過GC ROOT對象開始搜索,不可達的對象則回收。這時候能夠提到引用的類型,主要用得最多就是強引用和弱引用。當存在強引用的時候,內存不足寧願拋出OOM也不會回收。可是是弱引用的話,就有可能會被回收,這樣就防止了內存泄漏。
#### 2. 垃圾回收算法
分代的垃圾回收策略:
分代的垃圾回收策略是基於這樣一個事實:不一樣的對象的生命週期是不同的。所以,不一樣生命週期的對象能夠採起不一樣的回收算法,以便提升回收效率。
* 新生代
* 老生代
* 持久代
常見的垃圾回收算法有:
* 複製算法:搜索、掃描沒有引用的對象。開闢新的內存空間,將存活的對象複製到新的內存,舊的內存直接清除。因爲須要屢次交換內存空間,所以在對象數量比較少的時候效率比較高。適用於新生代。
* 標記算法,舊生代與新生代不一樣,對象存活的時間比較長,比較穩定,所以採用標記(Mark)算法來進行回收。
* 標記-清除算法:搜索、發現沒有引用的對象,直接回收,可是會致使內存碎片過多。
* 標記-整理算法:在標記-清除算法的基礎上,清除掉不存活的對象以後,把後面的存活對象搬移過來。使得內存連續,解決了內存碎片的問題。
#### 3. 觸發GC的條件是什麼?
* 當應用程序空閒時(即沒有應用線程在運行時),GC低優先級的守護線程會被調用。當應用忙時,GC線程就不會被調用,但如下條件兩個除外。
* 手動調用System.gc()方法,可是並不會致使GC立刻執行,反而會增長了虛擬機的負擔,所以不推薦直接使用。
* Java堆內存不足時(當年輕代或者老年代滿了),Java虛擬機沒法爲新的對象分配內存空間,虛擬機會再多行嘗試進行GC,若是仍是不能知足新對象的分配就會觸發OOM。
#### 4. 如何下降GC壓力?
頻繁的觸發GC操做致使線程暫停、內存抖動,會使得安卓系統在16ms內沒法完成繪製,形成界面卡頓等現象。經過下面的方法能夠下降GC壓力:
* 不要顯示的調用System.gc()
* 儘可能減小臨時對象的使用,可使用享元設計模式
* 對象不用的時候最好顯式置空
* 儘可能使用StringBuilder、StringBuffer,不使用String累加字符串(String的特性有關)
* 能使用基本數據類型就不要使用裝箱類
* 儘可能減小靜態對象變量的使用
* GC在回收對象以前調用finalize方法,不建議在該方法中作繁重的非內存釋放工做
#### 參考文章
[JVM內存管理機制和垃圾回收機制](https://blog.csdn.net/u011225629/article/details/49000311)
[java垃圾回收 - 爲何要進行垃圾回收](https://www.cnblogs.com/zedosu/p/6514457.html)
[java finalize方法總結、GC執行finalize的過程](https://blog.csdn.net/pi9nc/article/details/12374049)
[深刻理解 Java 垃圾回收機制](https://www.cnblogs.com/andy-zcx/p/5522836.html)
### Android虛擬機
#### 1. Dalvik VM(DVM)與JVM有什麼不一樣?
爲了更加適合移動端,Android基於JVM創造了DVM:
* JVM執行的是class文件,DVM執行的是dex文件。dex格式是專爲Dalvik應用設計的一種壓縮格式,適合於內存和處理器速度有限的系統。
* .dex文件相對於.class文件來講去掉了冗餘信息,較少了硬件的I/O操做次數,提升了類的查找速度。(另外,odex文件是dex文件的進一步優化)
* 能夠同時存在多個DVM,每一個進程就是一個獨立的虛擬機。獨立的進程能夠防止在虛擬機崩潰的時候全部程序都被關閉。
* JVM是基於棧的,DVM是基於寄存器的,基於寄存器的Dalvik實現雖然犧牲了一些平臺無關性,可是尋址速度更加快,代碼執行效率更高。
* 類加載系統與JVM區別比較大。
* 有一個特殊的虛擬機進程Zygote,他是虛擬機實例的孵化器。它在系統啓動的時候就會啓動,它會完成虛擬機的初始化,庫的加載,預製類庫和初始化的操做。若是系統須要一個新的虛擬機實例,它會迅速複製自身,以最快的數據提供給系統。對於一些只讀的系統庫,全部虛擬機實例都和Zygote 共享一塊內存區域。
#### 2. ART與DVM有什麼不一樣?
ART虛擬機是DVM的改進版本:
* DVM運行的時候,採用JIT來將字節碼轉換成機器碼,效率比較低,但讓應用安裝比較快,並且更容易在不一樣硬件和架構上運行。
* ART採用了AOT預編譯技術,APP安裝的時候就完成了字節碼預編譯爲機器碼,移除解釋代碼這一過程後,執行速度更加快,效率高,改善電池續航。
* 可是ART會佔用更多的安裝時間以及存儲空間,這是典型的以空間換區時間的策略。
#### 3. 參考文章
[虛擬機](https://www.jianshu.com/p/e00971e07e14)
[Android開發——JVM、Dalvik以及ART的區別](https://blog.csdn.net/seu_calvin/article/details/52354964)
### 反射
#### 1. 反射的理解
* 動態獲取信息:在運行狀態中,對於任意一個類,均可以知道這個類的全部方法和屬性,包括構造方法。
* 動態訪問方法和屬性:在運行狀態中,對於任意一個對象,均可以訪問它的任意一個方法和屬性。
#### 2. Android中反射機制的運用
* 四大組件的構造
* XML文件中控件的構造
* 經過反射調用Framework中的隱藏API
* 插件化開發中經過反射調用DexClassLoader加載的APK中的代碼
* 反射獲取類中的運行時註解
* SDK開發中,SDK的jar文件經過反射調用主項目中的資源和id
* ORM數據庫框架經過反射來實現
* ……
#### 3. 反射的優缺點
* 優勢:能夠動態的建立對象和實現一些動態功能,最大限度發揮了java的靈活性。
* 缺點:Android開發中大量使用反射對性能有影響。使用反射基本上一種解釋操做,對反射的性能損失最大的出如今getType()和getMethod()等操做上面,由於是經過Native方法對字節碼的相關字符串遍歷程序集尋找的,沒有直接調用的快。
#### 4. 反射的性能問題
提升反射的性能:
* 善用反射API:儘可能不要getMethods()後再遍歷篩選,而直接用getMethod(methodName)來根據方法名獲取方法。
* 緩存結果:若是須要大量調用反射,請考慮緩存字節碼、或者緩存一些經過反射獲取的常量結果。
* 提早反射:在APP啓動階段使用反射,緩存相關結果到內存當中,避免運行的時候再調用反射API。
* 使用開源庫:使用一些成熟的高性能反射庫,例如ReflectASM(使用字節碼生成的方式實現了更爲高效的反射機制)、jOOR(封裝了Java的反射API,而且實現了緩存)。
關於反射的性能問題的總結:
* 反射大概比直接調用慢50~100倍,可是須要你在執行100萬遍的時候纔會有所感受。
* 若是你只是偶爾調用一下反射,請忘記反射帶來的性能影響。若是你須要大量調用反射,請考慮緩存。
* 你的編程的思想和能力纔是限制你程序性能的最主要的因素。
#### 5. 參考文章
[Android 反射調用資源和id]([https://blog.csdn.net/u013045971/article/details/42462985])
[java面試題--java反射機制](https://blog.csdn.net/snn1410/article/details/44978457)
[java面試題:如何提升反射效率?](https://segmentfault.com/q/1010000003004720)
[反射爲何會形成性能丟失](https://q.cnblogs.com/q/86360/)
[反射是否真的會讓你的程序性能下降?](https://www.cnblogs.com/marvin/archive/2014/12/05/ShallWeUseReflect.html)
#### Java四大引用
#### 1. Java四大引用分別是什麼?
* 強引用(StrongReference)
* 生命週期:若是一個對象具備強引用,即便是內存空間不足時,垃圾回收器毫不會回收它來解決內存不足問題,虛擬機寧願拋出OOM錯誤,程序異常終止。顯式地設置爲null,或超出對象的生命週期範圍,則gc認爲該對象不存在引用,這時就能夠回收這個對象。
* 使用場景:對象的通常保存,一般都是使用強引用。
* 軟引用(SoftReference)
* 生命週期:一個對象只具備軟引用,則內存空間足夠,垃圾回收器就不會回收它;若是內存空間不足了,就會回收這些對象的內存。(只要垃圾回收器沒有回收它,該對象就能夠被程序使用。)
* 使用場景:能夠結合ReferenceQueue使用;適合作緩存,好比圖片緩存,解決OOM問題。(可是Android2.3以後虛擬機更加傾向於回收軟引用的對象,所以更加推薦使用LRUCache作緩存)
* 弱引用(WeakReference)
* 生命週期:無論當前內存空間足夠與否,垃圾回收GC運行的適合會被回收。
* 使用場景:能夠結合ReferenceQueue使用;在使用Handler、WebView、AsyncTask的適合,須要持有Context或者Activity的適合。爲了解決內存泄漏問題,須要將類設爲靜態類,可是因爲靜態內部類沒法持有外部類的引用,所以推薦弱引用的方法來持有。
* 虛引用(PhantomReference)
* 生命週期:有效期徹底隨機於GC的回收,在任何一個不肯定的時間內,均可能會被回收
* 使用場景:能夠結合ReferenceQueue使用;主要用於跟蹤對象被垃圾回收期回收的過程,好比LeakCanary;內存的精準控制。
注意要點:
* 強、軟、弱、虛引用的回收級別一個比一個弱。
* 軟、弱、虛引用均可以結合ReferenceQueue使用,若是軟引用所引用的對象被垃圾回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
* 軟、弱引用相對來講用得比較多,虛引用隨時都會被回收。相對用得比較少。
* 開發時,爲了防止內存溢出,處理一些比較佔用內存大而且生命週期長的對象的時候,能夠儘可能使用軟引用和弱引用。
* 在Android2.3之後,軟引用比LRU算法更加任性,回收量是比較大的,沒法控制回收哪些對象。所以推薦使用LRU算法,例如LruCache。
#### 2. 參考文章
[Java/Android中的強引用、軟引用、弱引用、虛引用](https://www.jianshu.com/p/017009abf0cf)
[Android 性能優化之旅1--基本概念](https://www.jianshu.com/p/921dc3d54918)
[Java 7之基礎 - 強引用、弱引用、軟引用、虛引用](https://blog.csdn.net/mazhimazh/article/details/19752475)
### 泛型
#### 1. 什麼是泛型?泛型的特色有什麼?
泛型是JDK1.5的新特性,泛型的本質是參數化類型,也就是說所操做的數據類型被指定爲一個參數。泛型提供了編譯期的類型安全,防止了類型強制轉換的錯誤的發生。
泛型的特色以下:
* 類型安全,提供編譯期間的類型檢查
* 代碼可讀性高
* 經過類型擦除JDK版本的先後兼容
* 泛化代碼,代碼能夠更多的重複利用
* 性能較高,泛型能夠爲Java編譯器和虛擬機帶來更多的類型信息,爲進一步優化提供條件
#### 2. 泛型的工做原理及其特色
* 類型檢查:編譯器在生成字節碼以前,編譯器提供類型檢查功能
* 類型推斷:編譯器判斷泛型方法的實際類型參數的過程稱爲類型推斷
* 類型擦除:編譯器在編譯生成字節碼時擦除了全部類型相關的信息,即轉爲原始類型,因此在運行時不存在任何類型相關的信息
類型擦除的特色:
* 兼容性:類型擦除確保了程序能和JDK5以前的版本開發二進制類庫進行兼容
* 原始類型替換:類型擦除以後,沒法在運行時訪問到類型參數(反射出來的結果爲原始類型),由於編譯器已經把泛型類型轉換成了原始類型。所以:經過反射的方式能夠實現關於泛型的非法操做
* 泛型不支持數組:類型擦除以後,在運行時沒法知道確切的類型信息,所以不能建立相應類型的數組。(Effective Java一書中建議使用List來代替數組,由於List能夠提供編譯期的類型安全保證,而數組卻不能保證(數組的使用必須保證)。)
#### 3. 泛型的通配符
* 限定通配符:對類型進行了限制。泛型類型必須用限定內的類型來進行初始化,不然會致使編譯錯誤
* ```<? extends T>```:它經過確保類型**必須是T或者T的子類**來設定類型的**上界**
* ```<? super T>```:它經過確保類型**必須是T或者T的父類**來設定類型的**下界**
* 非限定通配符:能夠用任意類型來替代
* ```<?>```:能夠用任意類型來替代
#### 4. 常見面試題
Java中參數類型```List<Object>```和原始類型```List```之間的區別?
* 編譯檢查的區別,是否編譯器類型安全:在編譯時編譯器不會對原始類型進行類型安全檢查,卻會對帶參數的類型進行檢查,經過使用```Object```做爲類型參數,能夠告知編譯器該方法能夠接受任意類型的對象
```java
//編譯器沒有警告
List<Object> l1 = new ArrayList<>();
l1.add("abc");
//編譯器有uncheck警告
List l2 = new ArrayList();
l2.add("abc");
```
* 原始類型能夠接受任意參數化類型的傳值:能夠把任何帶參數的類型傳遞給原始類型```List```
```java
List<Object> l1 = new ArrayList<>();
List l2 = new ArrayList();
l2 = l1;
```
* 參數化的類型之間的傳值,不考慮類型參數的繼承關係:不能把```List<String>```傳遞給接受```List<Object>```的方法,由於會產生編譯錯誤。例如:
```java
//編譯器報錯
List<Object> l1 = new ArrayList<String>();
```
Java中非限定通配符定義的參數類型```List<?>```和參數類型```List<Object>```之間的區別是什麼?
* ```List<?>```是一個未知類型的```List```,而```List<Object>```實際上是任意類型的```List```
* 能夠把```List<String>```, ```List<Integer>```賦值給```List<?>```,卻不能把```List<String>```賦值給```List<Object>```(忽略繼承關係)。
#### 5. 參考文章
[JAVA面試-基礎增強與鞏固:反射、註解、泛型等](https://www.jianshu.com/p/aaf8594e02eb)
[泛型常見面試題](https://www.cnblogs.com/huajiezh/p/6411123.html)
### JNI
#### 1. JNI原理
* Java層:經過```System.loadLibrary("hello")```方法加載so動態庫,最終會對應一個```NativeLibrary```,其中最核心的是so庫的句柄handle
* C/C++層:經過兩個核心函數```dlopen、dlsym```來進行so庫的加載、函數表查找。虛擬機會解析C/C++的頭文件、解析而且保存裏面的符號表。其中,```dlopen```返回so的句柄handle,```dlsym```能夠根據so的句柄handle與方法名獲取對應方法的函數指針
* 調用過程:JNI方法調用的時候,虛擬機底層會調用```NativeLibrary```的```find```方法來尋找so中的對應函數,最終調用```dlsym```函數,根據句柄與方法名獲取handle中的某個方法的函數指針
* 最終經過函數指針調用函數的時候,也會分配函數棧等內存,而後進行調用,這裏就是通常的函數調用過程了
* 示例代碼以下:
```c++
//定義hello.c
int add(int a,int b){return a+b;}
//將定義hello.c編譯成共享庫libhello.so
gcc -shared hello.c -o libhello.so
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
//這裏爲了演示方便去掉錯誤檢查相關的代碼
int main(int argc,char*argv[]){
void * handle;
int (*func)(int,int);
char *error;
int a,b;
//加載libhello.so庫,而且查找到對應的函數add
handle = dlopen("libhello.so",RTLD_LAZY);//RTLD_LAZY表示懶加載-須要調用的時候才加載
func = dlsym(handle,"add");
//調用、輸出結果
printf("%d",(*func)(1,2));
//關閉句柄
dlclose(handle);
}
```
* ```NativeLibrary```與```dlopen、dlsym、dlclose```的對應以下:
```java
public abstract class ClassLoader {
//NativeLibrary實際上是ClassLoader的一個靜態內部類,NativeLibrary能夠當作一個Bean對應,對應着一個so動態連接庫
static class NativeLibrary {
//so的文件句柄
long handle;
//so的名字
String name;
//so是否已經加載
boolean loaded;
/**
* so加載的方法
* 對應handle = dlopen("xxx.so",RTLD_LAZY)
*/
native void load(String name, boolean isBuiltin);
/**
* JNI方法調用的時候,根據name查找so中的方法
* 對應func = dlsym(handle,"xxx")
*/
native long find(String name);
/**
* so卸載載的方法
* 對應dlclose(handle);
*/
native void unload(String name, boolean isBuiltin);
}
}
```
JAVA的觀察者模式
Java中觀察者模式中主要是Observerable類(被觀察者),和Observer接口(觀察者)。下面是個簡單的demo
public class MyObserverable extends Observable{
public class MyObserver implements Observer{ private String name; public MyObserver(String name) { this.name=name; }
public static void main(String[] args) { MyObserver myObserver_1=new MyObserver("observer_1"); MyObserver myObserver_2=new MyObserver("observer_2"); MyObserverable myObserverable=new MyObserverable();