本文來源于并發(fā)編程網(wǎng)清英的一篇文章:你應(yīng)該知道的JAVA面試題,最近自己也在面試一些候選人,發(fā)現(xiàn)這篇文章中的有些點我也拿不準,因此按照自己的理解整理一份參考回答。
基礎(chǔ)題目
1. Java線程的狀態(tài)
Java線程在某個時刻只能處于以下六個狀態(tài)中的一個。
- New(新創(chuàng)建),一個線程剛剛被創(chuàng)建出來,還沒有開始運行的狀態(tài),更通俗點說:還沒有調(diào)用start方法;
- Runnable(可運行),可以在Java虛擬機中運行的狀態(tài);一個可運行的線程可能正在運行自己的代碼也可能沒有,這取決于操作系統(tǒng)提供的時間片;
- Blocked(被阻塞),當一個線程試圖獲取一個內(nèi)部的對象鎖(不是java.util.concurrent庫中的鎖),而該鎖此時正被其他線程持有,則該線程進入阻塞狀態(tài);
- Waiting(等待),當線程等待另一個線程通知調(diào)度器一個條件時,它自己進入等待狀態(tài)。在調(diào)用Object.wait方法或Thread.join方法,或者是等待java.util.concurrent庫中的Lock或Condition時,就會出現(xiàn)這種情況;
- Timed waiting(計時等待),Object.wait、Thread.join、Lock.tryLock和Condition.await等方法有超時參數(shù),還有Thread.sleep方法、LockSupport.parkNanos方法和LockSupport.parkUntil方法,這些方法會導致線程進入計時等待狀態(tài),如果超時或者出現(xiàn)通知,都會切換會可運行狀態(tài);
- Terminated(被終止),因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡。

Java線程狀態(tài).png
參考資料:
- Java Platform SE 8文檔
- Java核心技術(shù) 卷I—P634
2. 進程與線程的區(qū)別,進程間如何通訊,線程間如何通訊?
在并發(fā)編程領(lǐng)域,有進程和線程兩個概念,在Java語言中說起并發(fā)編程,常常是指多線程,但是了解進程的概念也非常重要:
- 進程是操作系統(tǒng)的資源調(diào)度實體,有自己的內(nèi)存地址空間和運行環(huán)境;
- 線程一般被稱為輕量級的進程,線程和進程一樣,也有自己的運行環(huán)境,但是創(chuàng)建一個線程要需要的資源比創(chuàng)建一個進程要少。線程存在于進程之中——每個進程至少有一個線程。一個進程下的多個線程之間可以共享進程的資源,包括內(nèi)存空間和打開的文件。
- 進程跟程序(programs)、應(yīng)用(applications)具備相同的含義,進程間通訊依靠IPC資源,例如管道(pipes)、套接字(sockets)等;
- 線程間通訊依靠JVM提供的API,例如wait方法、notify方法和notifyAll方法,線程間還可以通過共享的主內(nèi)存來進行值的傳遞;
參考資料:
3. HashMap的數(shù)據(jù)結(jié)構(gòu)是什么?如何實現(xiàn)的?和HashTable、ConcurrentHashMap的區(qū)別?
- 在Java 8中,HashMap的數(shù)據(jù)結(jié)構(gòu)是由Node<K,V>作為元素組成的數(shù)組:(1)如果有多個值hash到同一個桶中,則組織成一個鏈表,而且,當這個鏈表的節(jié)點個數(shù)超過某個值(TREEIFY_THRESHOLD參數(shù)指定)時,則將這個鏈表重構(gòu)為一個二叉樹;(2)如果發(fā)現(xiàn)map中的元素個數(shù)超過了threshold,則進行空間擴容——二倍空間。
- HashMap和HashTable的數(shù)據(jù)結(jié)構(gòu)和操作基本相同,區(qū)別是前者是非線程安全,并且HashMap接受value為null。
- ConcurrentHashMap和HashTable一樣,都是線程安全的,但是區(qū)別是:HashTable每次操作都會鎖住整個表結(jié)構(gòu)——導致一次只能有一個線程訪問HashTable對象,而ConcurrentHashMap不會,只會鎖住某個節(jié)點,只有在涉及到size的操作時才會鎖整個表結(jié)構(gòu)。
- 參考資料:《Java并發(fā)編程實戰(zhàn)》
4. Cookie和Session的區(qū)別
HTTP是無狀態(tài)協(xié)議,但是在實際應(yīng)用中有跟蹤客戶端狀態(tài)的需求,Cookie和Session是兩種不同的實現(xiàn)方案。
- Cookie保存在客戶端,Session保存在服務(wù)端
- Cookie沒有Session安全,侵入者可以通過分析客戶端的cookie信息侵入網(wǎng)站;
- 使用Session存儲重要信息,使用Cookie存儲不那么重要的信息;
- 使用Session方案時,常常需要依賴Cookie傳遞SID的值,如果客戶端禁用了Cookie,則轉(zhuǎn)而采取URL重寫技術(shù)(但是這種技術(shù)有安全風險);
- 參考資料:What is the difference between Sessions and Cookies in PHP?
5. 索引有什么用?如何建索引?
- 索引的作用:索引是一種數(shù)據(jù)結(jié)構(gòu),用于加快mysql獲取數(shù)據(jù)的速度;
- 如何建索引?在使用InnoDB引擎的前提下討論:(1)最左前綴原理:分析業(yè)務(wù)中的查詢條件,區(qū)分度高的字段放在前面,盡量減少一條SQL的影響行數(shù);(2)A+B可以代替A,A+B+C可以代替A+B,如果查詢是A+C則只能使用到A列索引;
- 關(guān)于InnoDB的認識:InnoDB使用B+Tree作為存儲數(shù)據(jù)結(jié)構(gòu),屬于聚簇索引,每個輔助索引最后都會指向主鍵的值,每次查詢兩次;(4)由于聚簇索引的特性,建議在使用InnoDB引擎的時候,使用自增ID作為主鍵,不要使用隨機的業(yè)務(wù)列作為主鍵。
- 參考資料
6. ArrayList是如何實現(xiàn)的,ArrayList和LinkedList的區(qū)別?ArrayList如何實現(xiàn)擴容?
- 可變數(shù)組實現(xiàn)了List接口的所有操作,功能上跟Vector相同,區(qū)別是Vector是線程安全的;
- 區(qū)別:LinkedList實現(xiàn)了List和Deque接口,一般稱為雙向鏈表;LinkedList在插入和刪除數(shù)據(jù)時效率更高,ArrayList在查找某個index的數(shù)據(jù)時效率更高;LinkedList比ArrayList需要更多的內(nèi)存;
- 關(guān)于可變數(shù)組的擴容策略,可以查看源碼,不同的JDK實現(xiàn)不太一樣,我這里使用JDK 8:首先嘗試擴容為原來大小的1.5倍,如果newCapacity還不夠大,則再擴大為minCapacity值;如果newCapacity比數(shù)組的規(guī)定最大容量還大,則根據(jù)minCapacity的值進行定奪,參見hugeCapacity方法。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
- 在代碼中,如果預先知道需要增加大量元素,則可以提前對當前的可變數(shù)組調(diào)用ensureCapacity方法,可以避免多次遞增的內(nèi)存重新分配;
- 參考資料:
- Java 8 DOC-LinkedList
- Java 8 DOC-ArrayList
- JDK 1.8中ArrayList的源碼
7. equals、hashcode等Object類中一些方法的討論?
- 覆寫equals方法的時候,也必須覆寫hashcode方法;
- 編寫equals方法后,檢查是否符合:對稱性、傳遞性、一致性、自反性和非空性
- 參考資料
8. 面向?qū)ο?/h3>
- 三大特性
- 封裝
- 繼承
- 多態(tài)
9. JVM如何加載字節(jié)碼文件?
- 封裝
- 繼承
- 多態(tài)
虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗、轉(zhuǎn)換解析和初始化,最終形成可被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。
Java語言中類的加載、連接和初始化過程都是在程序運行期間完成的,領(lǐng)Java具備高度的靈活性。
類加載的過程:加載、連接(驗證、準備、解析)、初始化。
- 加載:通過一個類的名字獲取此類的二進制字節(jié)流(PS:不限于從文件中讀?。?;將這個字節(jié)流代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運行時結(jié)構(gòu)(由具體的虛擬機自己定義);在內(nèi)存中生成一個java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)結(jié)構(gòu)的訪問入口。
- 驗證:文件格式驗證、元數(shù)據(jù)驗證(語義分析,類與類的繼承關(guān)系等)、字節(jié)碼驗證(數(shù)據(jù)流和控制流分析)、符號引用驗證(對類自身以外的信息進行匹配校驗)
- 準備:正式為類變量分配內(nèi)存并設(shè)置初始值,這里類變量指的是被static修飾的變量。例外:如果類字段是常量,則在這里會被初始化為表達式指定的值。
- 解析:將常量池內(nèi)的符號引用替換為直接引用。符號引用:類似于OS中的邏輯地址;直接引用:類似于OS中的物理地址,直接指向目標的指針、相對偏移量或一個能間接定位到目標的句柄。
- 初始化:真正開始執(zhí)行類中定義的Java程序代碼;初始化用于執(zhí)行Java類的構(gòu)造方法。類初始化的過程是不可逆的,如果中間一步出錯,則無法執(zhí)行下一步,參見不可逆的類初始化過程。
10. GC算法
- 垃圾回收解決三個問題:哪些內(nèi)存需要回收?什么時候回收?如何回收?
- 垃圾回收關(guān)注的是堆內(nèi)存(heap);
- 常見的垃圾收集算法
- 標記-清除算法
- 復制算法
- 標記-整理算法
- 分代收集算法
11. 什么情況下回出現(xiàn)Full GC,什么情況下會出現(xiàn)Young GC
- 對象優(yōu)先在新生代Eden區(qū)中分配,如果Eden區(qū)沒有足夠的空間時,就會觸發(fā)一次young gc
- Full gc的觸發(fā)條件有多個,F(xiàn)ULL GC的時候會STOP THE WORD。
- 在執(zhí)行Young gc之前,JVM會進行空間分配擔?!绻夏甏倪B續(xù)空間小于新生代對象的總大小(或歷次晉升的平均大?。?,則觸發(fā)一次full gc。
- 顯式調(diào)用System.gc()方法時;
- 大對象直接進入老年代,從年輕代晉升上來的老對象,嘗試在老年代分配內(nèi)存時,但是老年代內(nèi)存空間不夠;
12. JVM內(nèi)存模型

JVM內(nèi)存模型
- Java虛擬機規(guī)范定義Java內(nèi)存模型,嘗試屏蔽掉各種硬件和操作系統(tǒng)的訪問差異;
- JVM內(nèi)存模型的目標:定義程序中各個變量的訪問規(guī)則,即在虛擬機中將變量存儲到內(nèi)存和從內(nèi)存取出來這樣的細節(jié);
- volatile關(guān)鍵字:當一個變量用volatile關(guān)鍵字限定后,會有兩個語義:(1)當這個變量的值被修改后,會立即刷新到主內(nèi)存中,對其他線程可見;當某個線程讀取這個變量的時候,也會重新將主內(nèi)存中的數(shù)據(jù)刷一份到工作內(nèi)存中來。但是,如果多線程操作這個變量的計算中,后一個值依賴前一個值,就還是會有并發(fā)問題,說明volatile不具備原子性;(2)禁止指令重排優(yōu)化,觀察voatile變量對應(yīng)的字節(jié)碼文件,會發(fā)現(xiàn)變量的操作指令后面加了一句
lock addl $0x0,(%esp)的操作,這個操作相當于一個內(nèi)存屏障。 - synchronized關(guān)鍵字:當一個線程對一個變量加鎖的時候,就會清空這個變量在當前工作內(nèi)存中的值,因此該關(guān)鍵字同時滿足了可見性和原子性。
- 參考資料
- 程曉明:深入理解JVM內(nèi)存模型1
- 《深入理解JVM虛擬機》
- 《Java并發(fā)編程實戰(zhàn)》
13. Java運行時數(shù)據(jù)區(qū)

Java虛擬機運行時數(shù)據(jù)區(qū)
- 程序計數(shù)器(PC):Java線程私有,類似于操作系統(tǒng)里的PC計數(shù)器,用于指定下一條需要執(zhí)行的字節(jié)碼的地址;
- Java虛擬機棧:Java線程私有,虛擬機展描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的時候,都會創(chuàng)建一個棧幀用于存儲局部變量、操作數(shù)、動態(tài)鏈接、方法出口等信息;每個方法調(diào)用都意味著一個棧幀在虛擬機棧中入棧到出棧的過程;
- 本地方法棧:和Java虛擬機棧的作用類似,區(qū)別是該該區(qū)域為JVM調(diào)用到的本地方法服務(wù);
- 堆(Heap):所有線程共享的一塊區(qū)域,垃圾收集器管理的主要區(qū)域。目前主要的垃圾回收算法都是分代收集,因此該區(qū)域還可以細分為如下區(qū)域:
- 年輕代
- Eden空間
- From Survivor空間1,F(xiàn)rom Survivor空間2,用于存儲在Young gc過程中幸存的對象;
- 老年代
- 年輕代
- 方法區(qū):各個線程共享的一個區(qū)域,用于存儲虛擬機加載的類信息、常量、靜態(tài)變量等信息;
- 運行時常量池:方法區(qū)的一部分,用于存放編譯器生成的各種字面量和符號引用;
14. 事務(wù)的實現(xiàn)原理
- 事務(wù)的特性:ACID——原子性、一致性、隔離性和持久性
- Spring中的事務(wù)管理?Spring事務(wù)管理那些事
- MySQL中的事務(wù)?事務(wù)的隔離級別和鎖,參考何登成的MySQL的加鎖處理分析
本號專注于后端技術(shù)、JVM問題排查和優(yōu)化、Java面試題、個人成長和自我管理等主題,為讀者提供一線開發(fā)者的工作和成長經(jīng)驗,期待你能在這里有所收獲。

javaadu