戰(zhàn)路徑)
簡(jiǎn)介這份Java高頻面試題PDF面向準(zhǔn)備Java后端崗位面試的求職者與初中級(jí)開發(fā)者聚焦Spring生態(tài)與數(shù)據(jù)庫調(diào)優(yōu)兩大高頻考點(diǎn)。內(nèi)容系統(tǒng)梳理了Spring Bean生命周期實(shí)例化、依賴注入、初始化、銷毀、Spring MVC完整執(zhí)行流程、Spring Boot自動(dòng)裝配原理以及SQL執(zhí)行計(jì)劃解讀、索引失效場(chǎng)景與優(yōu)化技巧、MySQL常見索引類型和B樹底層結(jié)構(gòu)并延伸至線程池種類與核心參數(shù)、拒絕策略等并發(fā)知識(shí)點(diǎn)。資源包共1個(gè)PDF文件約396KB輕量便攜適合碎片化復(fù)習(xí)與面試前速查。目前已有180人學(xué)習(xí)瀏覽。讀者可借此快速串聯(lián)Spring框架核心機(jī)制與SQL優(yōu)化思路對(duì)照?qǐng)?zhí)行計(jì)劃字段與索引失效清單查漏補(bǔ)缺形成結(jié)構(gòu)化的面試答題框架提升技術(shù)表達(dá)與實(shí)戰(zhàn)應(yīng)對(duì)能力。1. 從一份 Java 高頻面試題 PDF 說起為什么背完答案還是掛面試間里最尷尬的場(chǎng)景不是被問到完全沒聽過的名詞而是題目你見過、答案你背過面試官追問一句“為什么”就卡殼。很多人手里都有一份流傳的 Java 高頻面試題 PDF幾百道題、分類清晰、答案現(xiàn)成看起來是捷徑實(shí)際用起來卻像黑匣子——輸入題目、輸出答案中間的理解過程被跳過了。這份 PDF 真正該被當(dāng)成什么不是背誦手冊(cè)而是一張知識(shí)地圖的索引。它告訴你哪些點(diǎn)會(huì)被反復(fù)考但每個(gè)點(diǎn)背后的原理、邊界、版本差異得自己補(bǔ)上。這篇文章面向正在準(zhǔn)備 Java 后端面試的開發(fā)者尤其是工作一到三年、想從“會(huì)寫業(yè)務(wù)代碼”跨到“能講清底層機(jī)制”的人。我會(huì)把這份高頻題 PDF 拆成可復(fù)現(xiàn)的學(xué)習(xí)路徑哪些題必須手寫代碼驗(yàn)證、哪些參數(shù)要親手調(diào)一遍、哪些坑是面試官最愛追問的。目標(biāo)很直接——讓你下次被追問時(shí)能順著原理往下講三層而不是背完第一層就沒了。2. 把高頻題 PDF 拆成可執(zhí)行的知識(shí)模塊先分類再動(dòng)手拿到一份 Java 高頻面試題 PDF第一反應(yīng)不該是從第一頁開始背。我一般會(huì)先做一次結(jié)構(gòu)拆解把題目按“考察層次”重新歸類因?yàn)椴煌瑢哟蔚念}準(zhǔn)備方式完全不同。PDF 的原始分類通常是按知識(shí)點(diǎn)排的——集合、并發(fā)、JVM、Spring、數(shù)據(jù)庫——但面試官提問時(shí)是混合著來的所以你得自己建一套索引。2.1 按考察層次給題目打標(biāo)簽記憶層、原理層、設(shè)計(jì)層記憶層的題答案是事實(shí)性的比如“HashMap 默認(rèn)容量是多少”“ArrayList 和 LinkedList 的區(qū)別”。這類題占比不小但單獨(dú)背它們性價(jià)比最低因?yàn)槊嬖嚬俸苌僦粏柺聦?shí)一定會(huì)往原理層追。原理層的題問的是“為什么這樣設(shè)計(jì)”比如“HashMap 為什么用紅黑樹而不是 AVL”“ConcurrentHashMap 為什么在 JDK 8 改成 CAS synchronized”。設(shè)計(jì)層的題最開放比如“讓你設(shè)計(jì)一個(gè)本地緩存你會(huì)考慮哪些點(diǎn)”它考的是你把前面兩層串起來的能力。我的做法是給 PDF 里每道題標(biāo)一個(gè)層級(jí)然后統(tǒng)計(jì)比例。如果原理層和設(shè)計(jì)層的題加起來不到一半說明這份 PDF 偏基礎(chǔ)你得自己補(bǔ)深度如果超過七成那它更適合當(dāng)復(fù)習(xí)提綱而不是入門材料。這個(gè)判斷決定了你后面花多少時(shí)間在動(dòng)手驗(yàn)證上。2.2 用最小代碼驗(yàn)證記憶層結(jié)論以 HashMap 擴(kuò)容為例記憶層的結(jié)論最容易記錯(cuò)也最容易被追問翻車。與其死記不如寫一段最小代碼跑一遍。下面這段代碼用來觀察 HashMap 的擴(kuò)容時(shí)機(jī)和容量變化注意我加了反射讀取內(nèi)部閾值這是理解擴(kuò)容邏輯的關(guān)鍵。import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class HashMapProbe { public static void main(String[] args) throws Exception { MapString, Integer map new HashMap(); // 反射拿到 table 和 threshold 字段觀察擴(kuò)容 Field tableField HashMap.class.getDeclaredField(table); tableField.setAccessible(true); Field thresholdField HashMap.class.getDeclaredField(threshold); thresholdField.setAccessible(true); for (int i 0; i 20; i) { map.put(key i, i); Object[] table (Object[]) tableField.get(map); int threshold (int) thresholdField.get(map); int capacity table null ? 0 : table.length; System.out.printf(size%d, capacity%d, threshold%d%n, map.size(), capacity, threshold); } } }這段代碼的邏輯說明HashMap 在第一次 put 時(shí)才初始化 table默認(rèn)容量 16、負(fù)載因子 0.75所以閾值是 12。當(dāng) size 超過閾值時(shí)觸發(fā)擴(kuò)容容量翻倍閾值也翻倍。運(yùn)行后你會(huì)看到 capacity 從 0 跳到 16再到 32threshold 從 0 到 12 再到 24。參數(shù)說明負(fù)載因子 0.75 是空間和時(shí)間的折中調(diào)大減少擴(kuò)容但增加哈希沖突調(diào)小相反初始容量如果提前知道數(shù)據(jù)量應(yīng)該設(shè)為預(yù)期元素?cái)?shù) / 0.75 1避免多次擴(kuò)容。這個(gè)結(jié)論背下來只要一句話但親手跑一遍面試官問“為什么是 0.75”時(shí)你就能從泊松分布和沖突概率的角度答而不是說“書上寫的”。2.3 給并發(fā)題建一張參數(shù)對(duì)照表synchronized 與 ReentrantLock并發(fā)是高頻題里最容易答得含糊的部分。我習(xí)慣把關(guān)鍵類做成對(duì)照表面試前掃一遍比背大段文字有效。下面這張表覆蓋了最常被對(duì)比的兩個(gè)鎖參數(shù)和邊界都列清楚。對(duì)比項(xiàng)synchronizedReentrantLock鎖獲取方式JVM 內(nèi)置自動(dòng)釋放顯式 lock/unlock需 finally 釋放可中斷不支持lockInterruptibly 支持超時(shí)獲取不支持tryLock(timeout) 支持公平鎖非公平可選公平/非公平條件變量wait/notify 單一可創(chuàng)建多個(gè) Condition鎖升級(jí)偏向鎖→輕量級(jí)→重量級(jí)無升級(jí)概念基于 AQS這張表不是讓你背而是讓你在回答時(shí)能快速定位差異點(diǎn)。比如面試官問“什么時(shí)候用 ReentrantLock”你就能從可中斷、超時(shí)、多條件三個(gè)場(chǎng)景切入而不是籠統(tǒng)說“更靈活”。參數(shù)上要特別注意ReentrantLock 的公平鎖會(huì)降低吞吐非公平鎖可能造成線程饑餓默認(rèn)非公平是有性能考慮的。這些邊界在 PDF 的答案里往往只有一句話但面試官追問時(shí)就是分水嶺。3. 用 JVM 和并發(fā)兩條線串起 PDF 里的硬骨頭高頻題 PDF 里真正拉開差距的是 JVM 和并發(fā)這兩塊。它們題目多、追問深、光靠背結(jié)論必翻車。我的策略是各選一條主線把零散的題掛上去形成能自己推演的鏈條。3.1 JVM 內(nèi)存結(jié)構(gòu)從一道“對(duì)象在哪分配”追到逃逸分析PDF 里常有一道題“對(duì)象一定分配在堆上嗎”標(biāo)準(zhǔn)答案是不一定可能棧上分配。但面試官接著會(huì)問“什么情況下棧上分配”這就到了逃逸分析。要講清楚這條鏈得先理清 JVM 內(nèi)存結(jié)構(gòu)程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧、堆、方法區(qū)JDK 8 后是元空間。對(duì)象分配的主戰(zhàn)場(chǎng)是堆但 JIT 編譯器在逃逸分析后可能做標(biāo)量替換把對(duì)象拆成基本類型放在棧上。驗(yàn)證這件事需要?jiǎng)邮终{(diào)參數(shù)。下面這組 JVM 參數(shù)用來觀察逃逸分析的效果注意-XX:PrintEscapeAnalysis在部分版本需要 debug 版 JVM生產(chǎn)環(huán)境一般用-XX:DoEscapeAnalysis開啟即可。# 開啟逃逸分析關(guān)閉標(biāo)量替換做對(duì)比 java -XX:DoEscapeAnalysis -XX:-EliminateAllocations -Xmx256m -jar demo.jar # 開啟逃逸分析并允許標(biāo)量替換 java -XX:DoEscapeAnalysis -XX:EliminateAllocations -Xmx256m -jar demo.jar邏輯說明DoEscapeAnalysis控制是否做逃逸分析EliminateAllocations控制是否允許標(biāo)量替換。兩個(gè)都開時(shí)未逃逸的對(duì)象可能被拆散分配在棧上減少堆壓力。參數(shù)說明-Xmx256m限制堆大小方便觀察 GC 頻率變化如果對(duì)象確實(shí)逃逸了比如被返回或存入全局集合逃逸分析不會(huì)生效這時(shí)調(diào)參也沒用。常見誤用是以為開了逃逸分析所有對(duì)象就上棧實(shí)際上它只對(duì)未逃逸的局部對(duì)象生效而且受 JIT 編譯閾值影響方法得被調(diào)用足夠多次才會(huì)觸發(fā)優(yōu)化。面試時(shí)能講清“逃逸分析是 JIT 的優(yōu)化手段不是 JVM 規(guī)范強(qiáng)制”就已經(jīng)超過大多數(shù)候選人了。3.2 并發(fā)工具類用 CountDownLatch 和 CyclicBarrier 做對(duì)照實(shí)驗(yàn)并發(fā)題里 CountDownLatch 和 CyclicBarrier 經(jīng)常被放在一起問很多人背了“一個(gè)減一個(gè)加”就完事但面試官會(huì)追問使用場(chǎng)景和異常處理。我一般寫兩個(gè)最小 Demo 跑一遍把行為差異刻進(jìn)記憶。import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; public class LatchVsBarrier { public static void main(String[] args) throws Exception { // CountDownLatch主線程等 3 個(gè)子任務(wù)完成 CountDownLatch latch new CountDownLatch(3); for (int i 0; i 3; i) { new Thread(() - { try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println(Thread.currentThread().getName() done); latch.countDown(); }).start(); } latch.await(); System.out.println(all tasks finished); // CyclicBarrier3 個(gè)線程互相等待到齊后繼續(xù) CyclicBarrier barrier new CyclicBarrier(3, () - System.out.println(barrier action executed)); for (int i 0; i 3; i) { new Thread(() - { try { System.out.println(Thread.currentThread().getName() waiting); barrier.await(); System.out.println(Thread.currentThread().getName() passed); } catch (Exception e) { Thread.currentThread().interrupt(); } }).start(); } } }邏輯說明CountDownLatch 是一次性的計(jì)數(shù)減到零后所有 await 的線程被釋放主線程繼續(xù)CyclicBarrier 是可循環(huán)的每個(gè)線程 await 后等其他人到齊然后一起繼續(xù)還能指定到齊后執(zhí)行的動(dòng)作。參數(shù)說明CountDownLatch 的構(gòu)造參數(shù)是計(jì)數(shù)初始值必須和任務(wù)數(shù)一致調(diào)大了會(huì)永久阻塞CyclicBarrier 的構(gòu)造參數(shù)是參與線程數(shù)如果某個(gè)線程在 await 前拋異常其他線程會(huì)收到 BrokenBarrierException這是排查時(shí)最常忽略的點(diǎn)。常見誤用是把 CyclicBarrier 當(dāng) CountDownLatch 用結(jié)果發(fā)現(xiàn)它不能“一個(gè)線程等多個(gè)”因?yàn)樗腔ハ嗟取C嬖嚂r(shí)能說出“CyclicBarrier 的 await 會(huì)拋 BrokenBarrierException需要處理”就是加分項(xiàng)。3.3 線程池參數(shù)把 PDF 里的七個(gè)參數(shù)落到一次壓測(cè)里線程池的七個(gè)參數(shù)幾乎每份 PDF 都有但背下來和會(huì)調(diào)是兩回事。核心線程數(shù)、最大線程數(shù)、空閑時(shí)間、時(shí)間單位、阻塞隊(duì)列、線程工廠、拒絕策略這七個(gè)里最容易翻車的是隊(duì)列和拒絕策略的組合。我一般用一段代碼模擬任務(wù)提交觀察不同隊(duì)列下的行為。import java.util.concurrent.*; public class PoolProbe { public static void main(String[] args) { // 核心 2最大 4隊(duì)列容量 2觀察拒絕時(shí)機(jī) ThreadPoolExecutor pool new ThreadPoolExecutor( 2, 4, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); for (int i 0; i 10; i) { final int id i; try { pool.execute(() - { System.out.println(task id running on Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } catch (RejectedExecutionException e) { System.out.println(task id rejected); } } pool.shutdown(); } }邏輯說明任務(wù)提交時(shí)先看核心線程是否滿沒滿就新建核心線程核心滿了進(jìn)隊(duì)列隊(duì)列滿了看最大線程是否滿沒滿就新建非核心線程都滿了觸發(fā)拒絕策略。參數(shù)說明ArrayBlockingQueue(2)容量為 2所以前 2 個(gè)任務(wù)占核心線程第 3、4 個(gè)進(jìn)隊(duì)列第 5、6 個(gè)占最大線程第 7 個(gè)開始被拒絕。如果把隊(duì)列換成LinkedBlockingQueue無界隊(duì)列最大線程數(shù)就永遠(yuǎn)不會(huì)被觸發(fā)任務(wù)會(huì)一直堆積直到 OOM這是生產(chǎn)環(huán)境最常見的坑。拒絕策略里 AbortPolicy 拋異常CallerRunsPolicy 讓提交線程自己跑DiscardPolicy 靜默丟棄DiscardOldestPolicy 丟最老的。面試時(shí)能說清“無界隊(duì)列會(huì)讓 maximumPoolSize 失效”比背七個(gè)參數(shù)名字有用得多。4. 避坑與排查高頻題準(zhǔn)備里最容易翻車的五件事準(zhǔn)備 Java 面試的過程中有些坑幾乎每個(gè)人都會(huì)踩而且踩了之后還以為是題目太難。下面這五條是我自己和身邊人反復(fù)驗(yàn)證過的按“現(xiàn)象→原因→解決”寫清楚。4.1 現(xiàn)象答案背得滾瓜爛熟一追問就斷片原因記憶層和原理層混在一起背沒有建立因果鏈。PDF 的答案往往是結(jié)論式的比如“HashMap 線程不安全”但沒告訴你為什么不安全、什么場(chǎng)景下會(huì)出問題。解決每背一個(gè)結(jié)論強(qiáng)制自己?jiǎn)柸齻€(gè)“為什么”然后寫最小代碼驗(yàn)證。比如“線程不安全”就去跑多線程 put觀察死循環(huán)或數(shù)據(jù)丟失親手復(fù)現(xiàn)一次比背十遍都牢。4.2 現(xiàn)象JVM 參數(shù)題答得頭頭是道實(shí)際調(diào)優(yōu)時(shí)不敢下手原因參數(shù)是背的沒在真實(shí)或模擬環(huán)境里調(diào)過。比如-Xmn設(shè)多大、-XX:SurvivorRatio怎么算光看公式記不住。解決本地起一個(gè) Spring Boot 小服務(wù)用-Xmx256m -Xmn128m跑一遍再用jstat -gc觀察 Eden、Survivor、Old 區(qū)的變化把參數(shù)和 GC 日志對(duì)應(yīng)起來。參數(shù)說明jstat -gc pid 1000每秒打印一次重點(diǎn)看 YGC 頻率和 FGC 次數(shù)如果 FGC 頻繁且每次回收不多多半是內(nèi)存泄漏或老年代太小。4.3 現(xiàn)象并發(fā)題一寫代碼就出 bug面試手寫環(huán)節(jié)翻車原因平時(shí)只看不寫對(duì) API 的異常和邊界不熟。比如wait()必須在循環(huán)里調(diào)用、CountDownLatch.await()要處理中斷。解決把 PDF 里所有涉及手寫的題列出來每道題在 IDE 里敲一遍跑通再關(guān)掉答案重寫。重點(diǎn)練生產(chǎn)者消費(fèi)者、單例雙重檢查、線程交替打印這三類它們覆蓋了鎖、等待通知、內(nèi)存可見性的核心。4.4 現(xiàn)象Spring 題答成配置背誦被問原理就轉(zhuǎn)移話題原因把 Spring 當(dāng)黑盒用沒理解 IoC 和 AOP 的實(shí)現(xiàn)。解決寫一個(gè)最小的 IoC 容器用反射和注解實(shí)現(xiàn) bean 的掃描、實(shí)例化、注入幾十行代碼就能跑通。AOP 則用 JDK 動(dòng)態(tài)代理和 CGLIB 各寫一個(gè) Demo對(duì)比兩者的區(qū)別。參數(shù)說明JDK 動(dòng)態(tài)代理要求目標(biāo)類實(shí)現(xiàn)接口CGLIB 通過繼承實(shí)現(xiàn)所以 final 類無法被 CGLIB 代理這是面試常問的邊界。4.5 現(xiàn)象數(shù)據(jù)庫題只答索引原理不會(huì)看執(zhí)行計(jì)劃原因索引結(jié)構(gòu)背得熟但沒在真實(shí)查詢里用過EXPLAIN。解決建一張百萬級(jí)的模擬表對(duì)同一個(gè)查詢分別加索引和不加索引用EXPLAIN看 type、key、rows 的變化。重點(diǎn)理解 type 從 ALL 到 ref 到 const 的差別以及最左前綴原則在聯(lián)合索引里的體現(xiàn)。參數(shù)說明EXPLAIN的 rows 是預(yù)估掃描行數(shù)不是精確值但數(shù)量級(jí)能反映索引是否生效如果 type 是 ALL 且 rows 很大說明走了全表掃描需要檢查索引列和查詢條件是否匹配。5. 從背題到能講用費(fèi)曼技巧把 PDF 榨干最后一章說一個(gè)我反復(fù)用、也確實(shí)有效的技巧把 PDF 里的每道高頻題當(dāng)成你要講給一個(gè)剛?cè)胄械耐侣?。費(fèi)曼技巧的核心不是復(fù)述而是用最少的術(shù)語把因果講通講不通的地方就是你的知識(shí)缺口。具體做法是挑一道題比如“ThreadLocal 為什么會(huì)內(nèi)存泄漏”先合上 PDF用白板或空白文檔寫下你的解釋然后對(duì)照三個(gè)標(biāo)準(zhǔn)檢查有沒有說清 ThreadLocalMap 的 key 是弱引用、value 是強(qiáng)引用有沒有說清為什么 key 弱引用還會(huì)泄漏有沒有說清remove()在什么時(shí)機(jī)調(diào)用。任何一條卡住就回去補(bǔ)補(bǔ)完再講一遍。我一般會(huì)把這種講解錄成三分鐘的語音回聽時(shí)專挑自己含糊的地方。含糊往往出現(xiàn)在兩個(gè)位置一是版本差異比如 JDK 8 和 JDK 17 在 GC 或集合實(shí)現(xiàn)上的變化二是邊界條件比如“大多數(shù)情況”和“一定”的區(qū)別。面試官追問的往往就是這兩處。下面這張表是我用來檢查講解質(zhì)量的清單每道題過一遍比刷十遍 PDF 有用。檢查項(xiàng)合格標(biāo)準(zhǔn)常見不合格表現(xiàn)因果鏈能說出至少三層為什么只答結(jié)論追問就重復(fù)結(jié)論版本邊界能指出 JDK 版本差異把 JDK 8 的行為當(dāng)成所有版本反例能舉出一個(gè)失效場(chǎng)景只說“一般”“通?!眳?shù)能說出關(guān)鍵參數(shù)和默認(rèn)值參數(shù)名記混或默認(rèn)值說錯(cuò)手寫能默寫出核心代碼結(jié)構(gòu)只記得 API 名字寫不出邏輯這個(gè)清單不是讓你逐條背而是講解時(shí)自然覆蓋。我自己的血淚經(jīng)驗(yàn)是早期準(zhǔn)備時(shí)把 PDF 刷了三遍結(jié)果面試被問“ConcurrentHashMap 的 size() 怎么實(shí)現(xiàn)的”直接卡住因?yàn)?PDF 只寫了“線程安全”沒寫 size 的實(shí)現(xiàn)細(xì)節(jié)。后來我改用講解法每道題都逼自己講到能回答“怎么實(shí)現(xiàn)的”這一層才真正穩(wěn)下來。還有一點(diǎn)別等到面試前一周才開始講平時(shí)每學(xué)一個(gè)模塊就講一次講完記下卡殼點(diǎn)那些就是你的復(fù)習(xí)重點(diǎn)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取