版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
Java后端開發(fā)工程師高頻面試題
【精選近三年60道高頻面試題】
【題目來源:學(xué)員面試分享復(fù)盤及網(wǎng)絡(luò)真題整理】
【注:每道題含高分回答示例+避坑指南】
1.請做一個自我介紹(基本必考|重點準(zhǔn)備)
2.聊聊HashMap的底層實現(xiàn),JDK1.7和1.8有什么區(qū)別?為什么1.8要引入紅黑樹?(極高
頻|基本必考)
3.ConcurrentHashMap是如何保證線程安全的?CAS+synchronized是怎么配合的?(極
高頻|重點準(zhǔn)備)
4.請手寫一個單例模式(DCL雙重檢查鎖),并解釋為什么需要volatile關(guān)鍵字?(基本必
考|背誦即可)
5.線程池的7個核心參數(shù)分別是什么?生產(chǎn)環(huán)境如何合理配置線程池大?。浚O高頻|考察
實操)
6.什么是AQS(AbstractQueuedSynchronizer)?ReentrantLock的加鎖和釋放鎖流程是怎
樣的?(常問|需深度思考)
7.講一下JVM的內(nèi)存模型(JMM),堆和棧的區(qū)別是什么?(基本必考|背誦即可)
8.詳細介紹一下CMS和G1垃圾收集器的區(qū)別,ZGC了解嗎?(常問|重點準(zhǔn)備)
9.生產(chǎn)環(huán)境出現(xiàn)OOM(內(nèi)存溢出)或CPU飆升到100%,你的排查思路和步驟是什么?
(極高頻|考察實操)
10.類加載機制中的“雙親委派模型”是什么?如何破壞雙親委派?(常問|需深度思考)
11.MySQL索引的數(shù)據(jù)結(jié)構(gòu)為什么選擇B+樹而不是B樹或Hash?(基本必考|重點準(zhǔn)備)
12.談?wù)凪ySQL事務(wù)的隔離級別,以及MVCC(多版本并發(fā)控制)的實現(xiàn)原理。(極高頻|需
深度思考)
13.這是一條執(zhí)行很慢的SQL,你會如何進行分析和優(yōu)化?(Explain關(guān)鍵字解讀)(極高頻|
考察實操)
14.MySQL的鎖機制了解嗎?什么是間隙鎖(GapLock),如何避免死鎖?(常問|需深度
思考)
15.Redis有哪些數(shù)據(jù)類型?在你的項目中具體用在什么場景?(基本必考|網(wǎng)友分享)
16.Redis的持久化機制RDB和AOF有什么區(qū)別?生產(chǎn)環(huán)境怎么選?(常問|背誦即可)
17.什么是緩存穿透、緩存擊穿和緩存雪崩?分別有什么解決方案?(極高頻|重點準(zhǔn)備)
18.如何實現(xiàn)分布式鎖?基于Redis(Redisson)和Zookeeper實現(xiàn)的區(qū)別是什么?(重點準(zhǔn)
備|考察實操)
19.SpringBean的生命周期是怎樣的?BeanPostProcessor在其中起什么作用?(常問|背誦
即可)
20.Spring是如何解決循環(huán)依賴問題的?三級緩存的作用分別是什么?(常問|需深度思考)
21.SpringBoot的自動裝配原理是什么?@SpringBootApplication注解背后做了什么?(基
本必考|重點準(zhǔn)備)
22.動態(tài)代理JDKProxy和CGLIB有什么區(qū)別?SpringAOP默認(rèn)使用哪種?(常問|背誦即
可)
23.消息隊列(MQ)在使用中如何保證消息不丟失?(以RocketMQ或Kafka為例)(重點
準(zhǔn)備|考察實操)
24.如何保證消息消費的順序性?如果出現(xiàn)了消息積壓怎么處理?(極高頻|考察抗壓)
25.了解分布式事務(wù)嗎?講講Seata的AT模式或TCC模式的原理。(常問|需深度思考)
26.微服務(wù)架構(gòu)中,服務(wù)熔斷和降級有什么區(qū)別?Sentinel或Hystrix的原理了解嗎?(常問|網(wǎng)
友分享)
27.什么是CAP定理?BASE理論又是什么?注冊中心Eureka和Nacos在一致性上有什么區(qū)
別?(基本必考|需深度思考)
28.Netty的線程模型(Reactor模式)是怎樣的?為什么它性能高?(常問|重點準(zhǔn)備)
29.TCP的三次握手和四次揮手流程是怎樣的?為什么握手是三次而揮手是四次?(基本必
考|背誦即可)
30.HTTPS的加密流程是怎樣的?對稱加密和非對稱加密是如何配合的?(常問|需深度思
考)
31.Session、Cookie和Token(JWT)的區(qū)別是什么?分布式Session如何實現(xiàn)?(基本必
考|網(wǎng)友分享)
32.什么是布隆過濾器(BloomFilter)?它的實現(xiàn)原理和誤判率是怎么回事?(常問|需深度
思考)
33.常用設(shè)計模式有哪些?請結(jié)合你的項目代碼,舉例說明你是怎么使用的(如策略模式、模
板方法)。(極高頻|考察實操)
34.Linux常用命令有哪些?如何查找大文件、查看日志中的關(guān)鍵字、查看端口占用?(基本
必考|考察實操)
35.Git操作問題:gitrebase和gitmerge有什么區(qū)別?發(fā)生代碼沖突怎么解決?(常問|考
察實操)
36.Docker的鏡像分層原理了解嗎?Kubernetes(K8s)的Pod和Service概念解釋一下。(常
問|網(wǎng)友分享)
37.算法題:反轉(zhuǎn)鏈表/判斷鏈表是否有環(huán)。(基本必考|學(xué)員真題)
38.算法題:無重復(fù)字符的最長子串(滑動窗口)。(重點準(zhǔn)備|學(xué)員真題)
39.算法題:TopK問題(堆排序或快速選擇算法)。(常問|需深度思考)
40.算法題:二叉樹的層序遍歷/二叉樹的最大深度。(基本必考|學(xué)員真題)
41.場景設(shè)計:如何設(shè)計一個高并發(fā)的秒殺系統(tǒng)?(前端、網(wǎng)關(guān)、后端、數(shù)據(jù)庫各層怎么做)
(極高頻|需深度思考)
42.場景設(shè)計:如何設(shè)計一個全局唯一的ID生成器?(雪花算法Snowflake原理)(重點準(zhǔn)備|
考察實操)
43.場景設(shè)計:短URL生成系統(tǒng)怎么設(shè)計?長短鏈接如何映射?(常問|網(wǎng)友分享)
44.數(shù)據(jù)庫主從復(fù)制的延遲問題怎么解決?(常問|需深度思考)
45.在項目中遇到過最難的技術(shù)挑戰(zhàn)是什么?你是如何一步步解決的?(極高頻|考察軟實
力)
46.你的項目是如何做壓測的?QPS和TPS大概是多少?瓶頸在哪里?(重點準(zhǔn)備|考察實
操)
47.什么是ThreadLocal?它為什么會導(dǎo)致內(nèi)存泄漏?(常問|需深度思考)
48.Java8StreamAPI常用方法有哪些?了解Lambda表達式的閉包特性嗎?(基本必考|背
誦即可)
49.講講MyBatis中#{}和${}的區(qū)別?如何防止SQL注入?(基本必考|背誦即可)
50.Maven依賴沖突(Jar包沖突)通常怎么解決?(常問|考察實操)
51.什么是零拷貝(ZeroCopy)?Sendfile和mmap的區(qū)別是什么?(常問|需深度思考)
52.JDK19/21引入的虛擬線程(VirtualThreads)了解嗎?它和傳統(tǒng)線程的區(qū)別?(常問|
網(wǎng)友分享)
53.如果產(chǎn)品經(jīng)理提了一個不合理的需求,或者技術(shù)上實現(xiàn)成本極高,你會怎么處理?(常
問|考察軟實力)
54.你平時通過什么渠道學(xué)習(xí)新技術(shù)?最近在看什么技術(shù)書或源碼?(常問|考察軟實力)
55.面對老舊系統(tǒng)的“屎山”代碼,你是選擇重構(gòu)還是維持現(xiàn)狀?如何降低重構(gòu)風(fēng)險?(常問|
考察抗壓)
56.什么是接口冪等性?在你的項目中是如何保證接口冪等的?(重點準(zhǔn)備|考察實操)
57.CI/CD(持續(xù)集成/持續(xù)部署)流程了解嗎?你們團隊的代碼上線流程是怎樣的?(常問|
網(wǎng)友分享)
58.未來3-5年的職業(yè)規(guī)劃是什么?是想走技術(shù)專家路線還是管理路線?(基本必考|考察軟實
力)
59.為什么從上一家公司離職?(基本必考|考察軟實力)
60.我問完了,你有什么想問我的嗎?(面試收尾)
【Java后端開發(fā)工程師】高頻面試題深度解答
Q1:請做一個自我介紹
?不好的回答示例:
面試官好,我叫張三,畢業(yè)于XX大學(xué)計算機專業(yè)。我平時性格比較開朗,能吃苦耐
勞。在大學(xué)期間學(xué)過Java、MySQL和SpringBoot,也做過一些課程設(shè)計,比如圖
書管理系統(tǒng)。我對技術(shù)很有熱情,平時也喜歡逛逛技術(shù)博客。我上一家公司是在一
家外包公司做增刪改查,覺得成長不夠,所以想來咱們公司試試。希望能給我一個
機會,我一定會努力工作的。
為什么這么回答不好:
1.流水賬且無亮點:僅羅列了基礎(chǔ)信息和性格,提到的“圖書管理系統(tǒng)”是爛大街的校招項
目,無法體現(xiàn)社招求職者的專業(yè)深度。
2.技術(shù)棧描述空洞:只是泛泛地說“學(xué)過”,沒有提及掌握程度(如熟悉、精通)以及具體的
實戰(zhàn)應(yīng)用場景。
3.暴露負面動機:直接說上一家是“外包”且“成長不夠”,雖然誠實但容易被貼上技術(shù)底子薄
的標(biāo)簽,且顯得不僅沒有沉淀,反而急于逃離。
高分回答示例:
面試官好,我叫張三,擁有5年Java后端開發(fā)經(jīng)驗,目前專注于高并發(fā)系統(tǒng)的設(shè)計
與優(yōu)化。我最核心的競爭力體現(xiàn)在三個方面:
第一,扎實的技術(shù)底座與架構(gòu)能力。我精通Java并發(fā)編程與JVM調(diào)優(yōu),對Spring
Cloud微服務(wù)生態(tài)有深入的落地經(jīng)驗。在上一家公司,我主導(dǎo)了核心交易鏈路的重
構(gòu),將系統(tǒng)從單體架構(gòu)遷移至微服務(wù)架構(gòu),成功支撐了日均500萬的訂單量,系統(tǒng)
可用性從99.9%提升至99.99%。
第二,具備復(fù)雜場景下的數(shù)據(jù)庫優(yōu)化經(jīng)驗。我非常擅長MySQL的索引調(diào)優(yōu)和
Redis緩存策略設(shè)計。曾在一個高流量的營銷活動中,通過引入RedisCluster和
Lua腳本解決“超賣”問題,并結(jié)合MySQL的分庫分表方案(ShardingSphere),
解決了單表數(shù)據(jù)量過億帶來的查詢延遲,將核心接口響應(yīng)時間從500ms降低至
50ms以內(nèi)。
第三,擁有良好的工程素養(yǎng)與團隊推動力。我習(xí)慣閱讀開源源碼(如RocketMQ、
Dubbo)來解決疑難雜癥,并在團隊內(nèi)部推廣了CI/CD自動化流程和代碼Review機
制,有效降低了線上故障率。
我對貴公司的技術(shù)氛圍非常向往,特別是貴司在電商領(lǐng)域的業(yè)務(wù)挑戰(zhàn)與我的技術(shù)棧
高度匹配。希望能有機會加入團隊,用我的經(jīng)驗為業(yè)務(wù)創(chuàng)造價值。
Q2:聊聊HashMap的底層實現(xiàn),JDK1.7和1.8有什么區(qū)別?為什么1.8要引入
紅黑樹?
?不好的回答示例:
HashMap底層就是數(shù)組加鏈表。JDK1.7的時候只有數(shù)組和鏈表,插入的時候是用
頭插法。JDK1.8變成了數(shù)組加鏈表加紅黑樹,插入改成了尾插法。引入紅黑樹是
因為鏈表太長了查詢會變慢,紅黑樹查詢快一點。另外,1.8里的擴容機制好像也不
太一樣,計算哈希值的方法也優(yōu)化了。其他的細節(jié)記不太清了,但在項目里我主要
是用它來存鍵值對的。
為什么這么回答不好:
1.關(guān)鍵點缺失:雖然提到了結(jié)構(gòu)變化,但對于“為什么引入紅黑樹”的閾值(8轉(zhuǎn)樹,6退
鏈)以及時間復(fù)雜度變化(O(n)到O(logn))沒有量化說明。
2.邏輯表述不清:對擴容機制的改變(如1.7的死循環(huán)問題、1.8的位運算優(yōu)化)一筆帶
過,無法展示對底層原理的深度理解。
3.缺乏專業(yè)術(shù)語:只是簡單說“快一點”,沒有從數(shù)據(jù)結(jié)構(gòu)的角度解釋性能提升的本質(zhì),顯得
專業(yè)度不夠。
高分回答示例:
HashMap是面試中的高頻考點,我對它的源碼有過深入研究。JDK1.8相比1.7主
要有三大核心變化:
1.底層數(shù)據(jù)結(jié)構(gòu)的演進:
JDK1.7采用“數(shù)組+鏈表”結(jié)構(gòu)。在極端情況下(Hash沖突嚴(yán)重),鏈表會變得
很長,導(dǎo)致查詢時間復(fù)雜度退化為O(n)。
JDK1.8優(yōu)化為“數(shù)組+鏈表+紅黑樹”。當(dāng)鏈表長度超過8且數(shù)組長度大于64時,
鏈表會轉(zhuǎn)為紅黑樹。這利用了紅黑樹O(logn)的查詢復(fù)雜度,解決了Hash沖突導(dǎo)
致的性能退化問題。當(dāng)節(jié)點數(shù)降至6以下時,又會退化為鏈表以節(jié)省空間。
2.插入方式的改變:
JDK1.7使用頭插法,這在多線程擴容時容易導(dǎo)致鏈表成環(huán),造成死循環(huán)。
JDK1.8改為尾插法,保證了元素插入順序,徹底解決了并發(fā)擴容下的死循環(huán)問
題(盡管HashMap本身仍不是線程安全的)。
3.擴容機制的優(yōu)化:
JDK1.7擴容時需要重新計算每個元素的Hash值(Rehash),效率較低。
JDK1.8利用了數(shù)組長度是2的冪次方的特性,通過(e.hash&oldCap)的結(jié)果判
斷元素位置。如果結(jié)果為0,位置不變;如果不為0,位置為“原索引+舊數(shù)組長
度”。這種設(shè)計直接利用位運算代替了取模運算,極大提升了擴容效率。
Q3:ConcurrentHashMap是如何保證線程安全的?CAS+synchronized是
怎么配合的?
?不好的回答示例:
ConcurrentHashMap在1.7的時候是用分段鎖Segment,把大數(shù)組切成小段,每段
配一把鎖。到了1.8就不用Segment了,改成了CAS和synchronized。插入數(shù)據(jù)的
時候先用CAS嘗試一下,如果失敗了就加synchronized鎖。這樣鎖的粒度更細,性
能更好。具體怎么配合的,好像是鎖住鏈表的頭節(jié)點吧,反正比Hashtable那種全
表鎖要快很多。
為什么這么回答不好:
1.細節(jié)模糊:對1.8中具體的加鎖對象(Node節(jié)點)和CAS的使用場景(初始化數(shù)組、put
空節(jié)點)描述不清。
2.缺乏深度:沒有解釋清楚為什么1.8要放棄Segment(減少內(nèi)存開銷、提高并發(fā)度),也
沒有提到擴容時的協(xié)同機制(多線程協(xié)助擴容)。
3.語言隨意:“好像是”、“反正比...快”等詞匯暴露了知識掌握的不確定性,難以獲得面試官
信任。
高分回答示例:
ConcurrentHashMap在JDK1.8中進行了徹底的重構(gòu),放棄了1.7的Segment分段
鎖,采用了“Node數(shù)組+CAS+synchronized”來實現(xiàn)更細粒度的并發(fā)控制:
1.降低鎖粒度:
JDK1.7的鎖粒度是Segment(默認(rèn)為16個),并發(fā)度受限于Segment數(shù)量。
JDK1.8將鎖粒度細化到了每個Hash桶(Node鏈表的頭節(jié)點或紅黑樹的根節(jié)
點)。這意味著只要Hash不沖突,多線程可以完全并行操作,理論并發(fā)度達到了
數(shù)組長度。
2.CAS與synchronized的協(xié)同工作(Put流程):
CAS階段:當(dāng)向一個空的Hash桶插入數(shù)據(jù)時,系統(tǒng)不需要加鎖,而是直接利用CAS
(CompareAndSwap)操作嘗試將新節(jié)點寫入數(shù)組。如果成功則直接返回,這避免
了加鎖的開銷。
synchronized階段:如果Hash桶不為空(發(fā)生了Hash沖突),或者CAS失敗,說明
有并發(fā)爭搶。此時,線程會使用synchronized鎖住當(dāng)前桶的頭節(jié)點(Node),然后進
行鏈表追加或紅黑樹插入操作。
3.并發(fā)擴容優(yōu)化:
ConcurrentHashMap還引入了多線程協(xié)同擴容機制。當(dāng)一個線程發(fā)現(xiàn)Map正在
擴容(節(jié)點狀態(tài)為MOVED)時,它不會阻塞,而是會協(xié)助其他線程一起進行數(shù)
據(jù)遷移(transfer),進一步利用了多核CPU的能力,顯著提升了吞吐量。
Q4:請手寫一個單例模式(DCL雙重檢查鎖),并解釋為什么需要volatile關(guān)鍵
字?
?不好的回答示例:
Java
public
class
Singleton
{
private
static
Singleton
instance;
private
Singleton()
{}
public
static
Singleton
getInstance()
{
if
(instance
==
null)
{
synchronized
(Singleton.class)
{
if
(instance
==
null)
{
instance
=
new
Singleton();
}
}
}
return
instance;
}
}
這就是DCL寫法。加兩個if判斷是為了提高效率,避免每次都加鎖。volatile關(guān)鍵字
的話,好像是保證可見性的,如果不加的話,多線程環(huán)境可能會讀到空對象吧,或
者是為了防止重排序,具體細節(jié)有點忘了。
為什么這么回答不好:
1.代碼漏洞:示例代碼中漏掉了核心的volatile關(guān)鍵字,這直接導(dǎo)致了DCL失效,屬于
嚴(yán)重的編碼錯誤。
2.原理不清:對volatile的作用解釋模棱兩可,沒有準(zhǔn)確指出“指令重排序”導(dǎo)致對象半初
始化的問題。
3.缺乏專業(yè)性:對于經(jīng)典的八股文題目,無法精準(zhǔn)背誦和解釋,會給面試官留下基礎(chǔ)不扎
實的印象。
高分回答示例:
Java
public
class
Singleton
{
//
必須使用
volatile
關(guān)鍵字
private
volatile
static
Singleton
instance;
private
Singleton()
{}
public
static
Singleton
getInstance()
{
//
第一重檢查:若已實例化,則不需加鎖,提升性能
if
(instance
==
null)
{
synchronized
(Singleton.class)
{
//
第二重檢查:防止多線程并發(fā)突破第一層檢查后重復(fù)創(chuàng)建
if
(instance
==
null)
{
instance
=
new
Singleton();
}
}
}
return
instance;
}
}
為什么必須加volatile?
這主要是為了禁止指令重排序。在Java中,instance=newSingleton()這行代
碼并非原子操作,它在底層會被編譯為三步:
1.分配內(nèi)存空間(memory=allocate())。
2.初始化對象(ctorInstance(memory))。
3.將instance引用指向內(nèi)存地址(instance=memory)。
如果不加volatile,編譯器或CPU可能會發(fā)生重排序,將步驟變成1->3->2。
在多線程環(huán)境下,線程A執(zhí)行完步驟3(引用已賦值,但對象未初始化)時,線程B
搶占CPU,在第一重檢查中發(fā)現(xiàn)instance!=null,直接返回了這個半初始化狀態(tài)
的對象。這會導(dǎo)致空指針異?;驍?shù)據(jù)錯誤。volatile通過內(nèi)存屏障徹底避免了這種重
排序風(fēng)險。
Q5:線程池的7個核心參數(shù)分別是什么?生產(chǎn)環(huán)境如何合理配置線程池大小?
?不好的回答示例:
7個參數(shù)主要是核心線程數(shù)、最大線程數(shù)、過期時間、單位、任務(wù)隊列、線程工廠
和拒絕策略。配置的話,我一般看是CPU密集型還是IO密集型。如果是CPU密集
型,就設(shè)成CPU核數(shù)加1;如果是IO密集型,就設(shè)成CPU核數(shù)乘以2。這都是網(wǎng)上
說的通用公式,我在項目里一般就直接用JDK自帶的FixedThreadPool,比較方
便,還沒遇到過什么大問題。
為什么這么回答不好:
1.理論脫離實際:僅僅背誦了公式,但沒有結(jié)合實際業(yè)務(wù)場景(如接口響應(yīng)時間、流量波
動)進行分析。
2.存在隱患:提到使用FixedThreadPool,這是阿里巴巴開發(fā)手冊明確禁止的(因為無界
隊列會導(dǎo)致OOM),暴露了實戰(zhàn)經(jīng)驗的不足。
3.缺乏調(diào)優(yōu)思路:沒有考慮到動態(tài)調(diào)整或監(jiān)控,生產(chǎn)環(huán)境的配置遠比簡單公式復(fù)雜。
高分回答示例:
線程池的7個參數(shù)包括:corePoolSize(核心線程數(shù))、maximumPoolSize(最大線
程數(shù))、keepAliveTime(存活時間)、unit(時間單位)、workQueue(阻塞隊
列)、threadFactory(線程工廠)、handler(拒絕策略)。
在生產(chǎn)環(huán)境中配置線程池,我不會生搬硬套公式,而是遵循以下“壓測+動態(tài)調(diào)
整”的策略:
1.初步估算:
CPU密集型任務(wù)(如加密、計算):N+1,盡量利用CPU。
IO密集型任務(wù)(如數(shù)據(jù)庫、RPC調(diào)用):2N或N/(1-阻塞系數(shù))。由于IO操作
不占用CPU,需要更多線程來提高并發(fā)度。
2.嚴(yán)控隊列與拒絕策略:
絕對不使用Executors創(chuàng)建的線程池,因為LinkedBlockingQueue默認(rèn)是無界
的,容易導(dǎo)致OOM。我會使用ThreadPoolExecutor,配合
ArrayBlockingQueue(有界隊列),并根據(jù)業(yè)務(wù)重要性選擇CallerRunsPolicy
(主線程執(zhí)行)或AbortPolicy(拋異常)作為拒絕策略,防止系統(tǒng)崩潰。
3.動態(tài)監(jiān)控與調(diào)優(yōu):
最重要的是,我會引入動態(tài)線程池(如Hippo4j或Nacos配置中心)。在線上,
我會監(jiān)控線程池的活躍度、隊列堆積情況。如果發(fā)現(xiàn)隊列經(jīng)常滿,我會動態(tài)調(diào)大
maximumPoolSize;如果發(fā)現(xiàn)CPU利用率過高,則適當(dāng)調(diào)小。任何配置都必須
基于真實的壓測數(shù)據(jù)和線上監(jiān)控來定。
Q6:什么是AQS(AbstractQueuedSynchronizer)?ReentrantLock的加鎖
和釋放鎖流程是怎樣的?
?不好的回答示例:
AQS就是一個抽象隊列同步器,它是很多鎖的基礎(chǔ),比如ReentrantLock和
CountDownLatch都用到了它。它里面有一個state變量,還有一個FIFO隊列。加
鎖的時候就是去改那個state,改成功了就拿到鎖了,失敗了就進隊列排隊。釋放鎖
的時候就把state改回去,然后喚醒隊列里的下一個線程。原理大概就是這樣,具體
的源碼我沒太細看。
為什么這么回答不好:
1.描述過于淺顯:只是泛泛而談,沒有涉及到CAS操作、CLH隊列變體、獨占/共享模式等
核心概念。
2.流程缺失:對于ReentrantLock的“可重入性”是如何實現(xiàn)的,以及公平鎖與非公平鎖在流
程上的區(qū)別完全沒提。
3.態(tài)度問題:“源碼沒太細看”對于高級開發(fā)崗位來說是一個明顯的減分項,暗示缺乏鉆研精
神。
高分回答示例:
AQS(AbstractQueuedSynchronizer)是Java并發(fā)包(JUC)的基石,它提供了
一套通用的機制來管理同步狀態(tài)(state)和線程排隊(CLH隊列變體)。
以ReentrantLock(非公平鎖)為例,其加鎖和釋放鎖的流程如下:
1.加鎖流程(lock):
CAS搶占:線程進來后,直接通過CAS嘗試將state從0修改為1。如果成功,直接
獲取鎖,設(shè)置當(dāng)前擁有鎖的線程為自己(這是非公平鎖的特性)。
重入判斷:如果CAS失敗,判斷當(dāng)前持有鎖的線程是否是自己。如果是,state加
1,實現(xiàn)可重入。
入隊掛起:如果都不是,線程會被封裝成Node節(jié)點,放入AQS的雙向鏈表尾部。隨
后,線程會通過LockSupport.park()掛起,等待被喚醒。
2.釋放鎖流程(unlock):
修改狀態(tài):線程將state減1。如果減后state為0,說明鎖完全釋放。
喚醒后繼:鎖完全釋放后,從AQS隊列的頭部(Head)找到下一個有效的等待節(jié)點
(WaitStatus不為取消狀態(tài)),調(diào)用LockSupport.unpark()喚醒該線程,讓它去嘗試
獲取鎖。
AQS通過模板方法模式,將復(fù)雜的排隊和阻塞邏輯封裝在底層,子類只需實現(xiàn)tryA
cquire和tryRelease等方法即可,體現(xiàn)了極高的設(shè)計美學(xué)。
Q7:講一下JVM的內(nèi)存模型(JMM),堆和棧的區(qū)別是什么?
?不好的回答示例:
JVM內(nèi)存模型主要包括堆、棧、方法區(qū)、程序計數(shù)器和本地方法棧。堆是存對象
的,最大的那一塊。棧是存局部變量的,方法執(zhí)行的時候會壓棧。區(qū)別就是堆是線
程共享的,棧是線程私有的。還有就是堆會發(fā)生垃圾回收,棧一般不會。JMM的
話,好像是說主內(nèi)存和工作內(nèi)存什么的,為了保證線程安全。
為什么這么回答不好:
1.概念混淆:題目問的是JVM內(nèi)存模型(JMM,JavaMemoryModel,涉及并發(fā)、可見
性),回答者卻在講運行時數(shù)據(jù)區(qū)(RuntimeDataArea),這是兩個完全不同的概念。
2.深度不足:對堆棧的區(qū)別僅停留在“存對象”和“線程共享”層面,未涉及生命周期、空間大
小限制及拋出異常類型(OOMvsStackOverflow)。
3.表達不嚴(yán)謹(jǐn):對JMM的描述含糊不清,沒有點出原子性、可見性、有序性這三大特性。
高分回答示例:
這個問題需要區(qū)分運行時數(shù)據(jù)區(qū)和JMM(Java內(nèi)存模型)。
首先,運行時數(shù)據(jù)區(qū)包括:
1.堆(Heap):線程共享,主要存放對象實例和數(shù)組,是垃圾回收(GC)的主要區(qū)域。
2.棧(Stack):線程私有,存放局部變量表、操作數(shù)棧等。方法調(diào)用對應(yīng)棧幀入棧,結(jié)
束對應(yīng)出棧。
3.方法區(qū)、程序計數(shù)器、本地方法棧等。
堆和棧的核心區(qū)別:
物理存儲:堆內(nèi)存較大且不連續(xù),棧內(nèi)存較小且連續(xù)。
生命周期:堆與JVM進程同生共死,棧隨線程的生命周期結(jié)束而釋放。
異常類型:堆空間不足拋OutOfMemoryError,棧深度不夠拋StackOverflowError。
其次,JMM(JavaMemoryModel)是一套規(guī)范,用于屏蔽硬件差異,解決多線
程通信問題。它規(guī)定了:
1.所有變量存儲在主內(nèi)存。
2.每個線程有自己的工作內(nèi)存,保存了變量的副本。
3.線程操作變量必須在工作內(nèi)存中進行,不能直接讀寫主內(nèi)存。
這就引入了可見性(volatile)、原子性(synchronized/CAS)和有序性
(Happens-Before原則)的問題,是并發(fā)編程的理論基礎(chǔ)。
Q8:詳細介紹一下CMS和G1垃圾收集器的區(qū)別,ZGC了解嗎?
?不好的回答示例:
CMS是老年代的收集器,用的是標(biāo)記-清除算法。它的優(yōu)點是并發(fā)收集,停頓時間
短,但是會產(chǎn)生內(nèi)存碎片。G1是現(xiàn)在默認(rèn)的收集器,它把堆分成了很多小塊
(Region),不分年代了。G1可以預(yù)測停頓時間,性能更好。ZGC是最新的,號
稱停頓時間不超過10ms,但我沒在生產(chǎn)上用過。
為什么這么回答不好:
1.理解偏差:說G1“不分年代了”是錯誤的,G1依然保留了分代概念(邏輯分代,物理分
區(qū))。
2.細節(jié)缺失:沒有對比兩者的核心痛點,如CMS的“浮動垃圾”和“ConcurrentMode
Failure”,以及G1的篩選回收(MixedGC)機制。
3.ZGC描述淺薄:僅提到了停頓時間,沒有觸及ZGC的核心技術(shù)(如讀屏障、染色指
針)。
高分回答示例:
CMS和G1代表了垃圾回收器從“物理分代”向“邏輯分代、物理分區(qū)”的演進方向:
1.CMS(ConcurrentMarkSweep):
特點:追求最短停頓時間(LowPause),采用“標(biāo)記-清除”算法。
缺點:會產(chǎn)生大量內(nèi)存碎片,導(dǎo)致大對象分配困難;對CPU資源敏感;存在“浮動垃
圾”問題,若預(yù)留空間不足會觸發(fā)FullGC(SerialOld),導(dǎo)致長時間停頓。
2.G1(Garbage-First):
特點:將堆劃分為多個大小相等的Region,雖然保留了分代概念,但物理上不再隔
離。它能建立“可預(yù)測的停頓時間模型”,根據(jù)用戶設(shè)定的目標(biāo)時間,優(yōu)先回收價值
(垃圾占比)最大的Region。
優(yōu)勢:采用“標(biāo)記-整理”算法(Region之間是復(fù)制),不會產(chǎn)生內(nèi)存碎片;適合大內(nèi)存
機器(6G-8G以上)。
3.ZGC(ZGarbageCollector):
黑科技:JDK11/15引入,基于染色指針(ColoredPointers)和讀屏障(Load
Barriers)技術(shù)。
目標(biāo):在TB級堆內(nèi)存下,將STW(StopTheWorld)時間控制在10ms以內(nèi)。它實現(xiàn)
了并發(fā)整理,幾乎所有階段都是并發(fā)執(zhí)行的,是未來低延遲服務(wù)的首選。
Q9:生產(chǎn)環(huán)境出現(xiàn)OOM(內(nèi)存溢出)或CPU飆升到100%,你的排查思路和步
驟是什么?
?不好的回答示例:
如果CPU飆升,我會先用top命令看看是哪個進程占用的。如果是Java進程,我就
重啟一下服務(wù)試試,看能不能恢復(fù)。如果不行,就看看日志有沒有報錯。如果是
OOM,我就把堆內(nèi)存調(diào)大一點,比如把Xmx參數(shù)改大。實在不行就讓運維幫忙
dump一下堆內(nèi)存,然后用MAT工具分析一下,看是哪個對象太多了。
為什么這么回答不好:
1.盲目重啟:“重啟一下試試”是典型的運維大忌,會丟失案發(fā)現(xiàn)場(線程棧、堆信息),導(dǎo)
致問題無法根治。
2.簡單粗暴:“調(diào)大內(nèi)存”只是緩解癥狀,如果是代碼邏輯(如死循環(huán)、內(nèi)存泄漏)問題,調(diào)
大內(nèi)存只會推遲崩潰時間。
3.流程不規(guī)范:缺乏定位到具體“線程”和“代碼行”的步驟,排查邏輯非常松散。
高分回答示例:
面對線上OOM或CPU100%的問題,必須保持冷靜,保留現(xiàn)場并精準(zhǔn)定位。我的
排查SOP如下:
針對CPU飆升到100%:
1.定位進程:使用top命令找到占用CPU最高的進程ID(PID)。
2.定位線程:使用top-Hp<PID>查看該進程下占用CPU最高的線程ID(TID)。
3.轉(zhuǎn)換進制:將TID轉(zhuǎn)換為十六進制(printf"%x\n"TID)。
4.定位代碼:使用jstack<PID>|grep<十六進制TID>-A20打印堆棧信息。如果看
到是業(yè)務(wù)代碼,通常是死循環(huán)或復(fù)雜算法;如果是GC線程,說明發(fā)生了頻繁的FullGC。
針對OOM(內(nèi)存溢出):
1.保護現(xiàn)場:確保啟動參數(shù)配置了-XX:+HeapDumpOnOutOfMemoryError,這樣OOM時會
自動生成Dump文件。
2.離線分析:獲取Dump文件,使用MAT(MemoryAnalyzerTool)或JProfiler進行分
析。
3.查找病因:
查看Histogram,找出實例數(shù)量最多的類。
分析DominatorTree,找到占用內(nèi)存最大的對象(大對象)。
查看GCRoots引用鏈,定位是哪個靜態(tài)變量或緩存隊列一直在持有對象導(dǎo)致無法回
收(內(nèi)存泄漏)。
4.解決:修復(fù)代碼泄露邏輯,或根據(jù)業(yè)務(wù)量合理調(diào)整堆內(nèi)存大小。
Q10:類加載機制中的“雙親委派模型”是什么?如何破壞雙親委派?
?不好的回答示例:
雙親委派就是加載類的時候,先讓父加載器去加載,父加載器加載不了再自己加
載。這樣是為了安全,防止核心類被篡改。比如Object類,大家都要用,必須由啟
動類加載器加載。如果要破壞的話,好像是重寫loadClass方法吧。Tomcat好像就
破壞了,因為它要部署很多應(yīng)用,不同的應(yīng)用可能有同名的類。
為什么這么回答不好:
1.表述不準(zhǔn):“父加載器”容易讓人誤解為繼承關(guān)系,實際上是組合(Composition)關(guān)系。
2.深度不夠:對“破壞”場景的解釋不夠具體,未提到SPI(ServiceProviderInterface)機制
(如JDBCDriver)也是破壞雙親委派的典型案例。
3.邏輯單一:僅提到了Tomcat,沒有展開講Tomcat為什么要破壞(Web應(yīng)用隔離性),回
答略顯單薄。
高分回答示例:
雙親委派模型是指:當(dāng)一個類加載器收到類加載請求時,它首先不會自己去加載,
而是將請求委派給父類加載器。層層向上,直到最頂層的BootstrapClassLoader。
只有當(dāng)父加載器反饋無法完成加載時,子加載器才會嘗試自己加載。
核心作用:
1.沙箱安全:防止核心API庫(如java.lang.Object)被惡意篡改。
2.避免重復(fù)加載:保證類的全局唯一性。
如何破壞雙親委派?
主要通過重寫ClassLoader的loadClass方法(該方法包含了雙親委派的邏輯)來
實現(xiàn)。典型場景包括:
1.TomcatWeb容器:Tomcat需要解決Web應(yīng)用之間的類隔離問題(App1用Spring4,
App2用Spring5)。它自定義了WebAppClassLoader,優(yōu)先加載Web應(yīng)用目錄下
(WEB-INF/classes)的類,打破了雙親委派,只有核心類庫才委托給上級。
2.JavaSPI機制(JDBC):DriverManager是核心類(rt.jar),由啟動類加載器加載。
但它需要加載廠商提供的驅(qū)動實現(xiàn)(如com.mysql.jdbc.Driver,在classpath下)。由
于啟動類加載器看不見classpath,Java利用線程上下文類加載器
(ThreadContextClassLoader)完成了父級加載器去請求子級加載器完成類加載的動
作,這是一種反向委派。
Q11:MySQL索引的數(shù)據(jù)結(jié)構(gòu)為什么選擇B+樹而不是B樹或Hash?
?不好的回答示例:
因為B+樹查詢快啊。Hash索引只支持等值查詢,不支持范圍查詢,所以很少用。B
樹的話,它的節(jié)點里也存數(shù)據(jù),這就導(dǎo)致一個頁存不了多少索引,樹就會很高。
B+樹只有葉子節(jié)點存數(shù)據(jù),非葉子節(jié)點只存索引,樹比較矮,IO次數(shù)少,所以效率
高。而且B+樹葉子節(jié)點有鏈表,適合范圍查詢。
為什么這么回答不好:
1.回答不夠條理:雖然點到了核心,但缺乏結(jié)構(gòu)化陳述,像是在“擠牙膏”。
2.細節(jié)缺失:沒有提到MySQL頁大?。J(rèn)16KB)與B+樹高度的具體估算關(guān)系(一般3層
能存2000萬數(shù)據(jù))。
3.專業(yè)度不足:對于“局部性原理”和“磁盤預(yù)讀”等操作系統(tǒng)層面的支撐理論提及較少。
高分回答示例:
MySQLInnoDB引擎選擇B+樹作為索引結(jié)構(gòu),是基于磁盤I/O特性和查詢場景的綜
合考量:
1.與Hash對比(范圍查詢瓶頸):
Hash索引雖然等值查詢僅需O(1),但它是無序的,無法支持范圍查詢(>、<、
between)和排序操作,也不能利用最左前綴匹配,因此不適合作為通用索引。
2.與B樹對比(樹高與I/O):
存儲效率:B樹的每個節(jié)點都存儲Key和Value(數(shù)據(jù)),而MySQL頁大小固定
(16KB)。如果數(shù)據(jù)較大,一個頁能存的索引項就很少,導(dǎo)致樹很高,意味著更多的
磁盤I/O。而B+樹僅在葉子節(jié)點存數(shù)據(jù),非葉子節(jié)點只存Key,這使得單一節(jié)點能容納
更多索引,3-4層的高度就能支撐千萬級數(shù)據(jù),極大減少了I/O次數(shù)。
掃庫能力:B樹進行全表掃描需要遍歷整棵樹,效率低。B+樹的葉子節(jié)點通過雙向鏈
表連接,天然有序,非常適合范圍查詢和全表掃描(OrderBy/Limit)。
綜上,B+樹在保持低樹高的同時,完美支持了關(guān)系型數(shù)據(jù)庫最常見的范圍和排序需
求。
Q12:談?wù)凪ySQL事務(wù)的隔離級別,以及MVCC(多版本并發(fā)控制)的實現(xiàn)原
理。
?不好的回答示例:
MySQL有四個隔離級別:讀未提交、讀已提交、可重復(fù)讀、串行化。默認(rèn)是可重復(fù)
讀(RR)。
讀未提交會有臟讀;讀已提交會有不可重復(fù)讀;RR解決了不可重復(fù)讀,但是會有幻
讀。串行化最安全但最慢。
MVCC就是多版本并發(fā)控制,主要是為了提高并發(fā)。它在每行數(shù)據(jù)后面加了兩個隱
藏列,記錄創(chuàng)建版本號和刪除版本號。讀取的時候根據(jù)版本號判斷能不能讀。
為什么這么回答不好:
1.幻讀問題表述不準(zhǔn):在InnoDB的RR級別下,通過MVCC+Next-KeyLock實際上已經(jīng)基本
解決了幻讀問題,回答中說“會有幻讀”是不準(zhǔn)確的。
2.MVCC原理陳舊:“創(chuàng)建/刪除版本號”是網(wǎng)上過時的老教材說法。真正的InnoDB實現(xiàn)是基
于UndoLog鏈和ReadView。
3.缺乏深度:沒有解釋當(dāng)前讀(CurrentRead)和快照讀(SnapshotRead)的區(qū)別。
高分回答示例:
MySQL的四個隔離級別中,RepeatableRead(RR)是默認(rèn)級別。
MVCC(多版本并發(fā)控制)是InnoDB在RC和RR級別下實現(xiàn)快照讀(非阻塞讀)
的核心機制。它的實現(xiàn)依賴于三個隱式字段、UndoLog和ReadView:
1.UndoLog(回滾日志):
當(dāng)修改記錄時,MySQL不會直接覆蓋原數(shù)據(jù),而是將舊數(shù)據(jù)寫入UndoLog,并
通過DB_ROLL_PTR指針將新老數(shù)據(jù)連接成一個版本鏈。
2.ReadView(讀視圖):
ReadView是事務(wù)進行快照讀時產(chǎn)生的讀視圖,包含當(dāng)前活躍事務(wù)ID列表
(m_ids)。
在RC級別下:每次Select查詢都會生成一個新的ReadView,所以能讀到其他事務(wù)已
提交的最新修改(導(dǎo)致不可重復(fù)讀)。
在RR級別下:只有第一次Select會生成ReadView,后續(xù)查詢復(fù)用該視圖。這就保證
了在同一個事務(wù)中,多次讀取看到的數(shù)據(jù)是一致的(解決了不可重復(fù)讀)。
3.解決幻讀:
MVCC解決了“快照讀”下的幻讀;而對于“當(dāng)前讀”(如selectforupdate),
InnoDB通過Next-KeyLock(間隙鎖+行鎖)鎖定范圍,防止其他事務(wù)插入新
行,從而徹底解決了幻讀問題。
Q13:這是一條執(zhí)行很慢的SQL,你會如何進行分析和優(yōu)化?(Explain關(guān)鍵字
解讀)
?不好的回答示例:
如果SQL慢,我會先看日志,然后用Explain命令去分析。主要看type那列,如果
是ALL就是全表掃描,肯定慢,要加索引。還有看key那列,看有沒有用到索引。如
果rows太大也不行。優(yōu)化的話就是加索引,或者改寫SQL,比如不要用select*,
不要在where條件里做計算。如果還慢,就得看是不是表太大了,可能要分庫分
表。
為什么這么回答不好:
1.Explain解讀不全:漏掉了核心字段Extra,其中的Usingfilesort和Usingtempora
ry往往是性能殺手。
2.分析邏輯單一:只是簡單說“加索引”,沒有考慮到索引失效的場景(如最左前綴、類型轉(zhuǎn)
換)。
3.缺乏實操感:沒有提到開啟慢查詢?nèi)罩荆⊿lowQueryLog)的第一步動作,直接跳到了
Explain。
高分回答示例:
面對慢SQL,我的優(yōu)化SOP如下:
1.定位問題:
首先通過開啟慢查詢?nèi)罩静东@慢SQL,或使用監(jiān)控工具(如Arthas、
Prometheus)定位耗時語句。
2.Explain分析執(zhí)行計劃:
重點關(guān)注以下幾個字段:
type:訪問類型。優(yōu)選ref、range,堅決避免ALL(全表掃描)和index(全
索引掃描)。
key:實際使用的索引。如果為NULL,說明索引未生效,需檢查是否違反最左前綴原
則、使用了!=、或者在字段上進行了函數(shù)計算。
rows:掃描行數(shù)。數(shù)值越大,IO開銷越大。
Extra:額外信息。出現(xiàn)Usingfilesort(文件排序)或Usingtemporary(臨
時表)是性能大忌,必須優(yōu)化排序或分組邏輯。
3.優(yōu)化手段:
索引優(yōu)化:覆蓋索引(CoveringIndex)是神器,盡量讓查詢字段都在索引中,避免
回表。
SQL改寫:例如將select*改為具體字段;用limit分頁優(yōu)化大數(shù)據(jù)量偏移
(延遲關(guān)聯(lián));小表驅(qū)動大表。
架構(gòu)層面:如果單表數(shù)據(jù)量過千萬,物理優(yōu)化無效,我會考慮讀寫分離或分庫分表。
Q14:MySQL的鎖機制了解嗎?什么是間隙鎖(GapLock),如何避免死鎖?
?不好的回答示例:
MySQL鎖主要有表鎖和行鎖。MyISAM用表鎖,InnoDB用行鎖。行鎖有共享鎖和
排他鎖。間隙鎖就是鎖住兩條記錄中間的空隙,防止別人插入數(shù)據(jù),是為了解決幻
讀的。避免死鎖的話,就是盡量按順序加鎖,或者大事務(wù)拆成小事務(wù)。如果發(fā)生了
死鎖,MySQL會自動回滾其中一個事務(wù)。
為什么這么回答不好:
1.鎖的分類不全:漏掉了意向鎖(IntentionLock)、臨鍵鎖(Next-KeyLock)等重要概
念。
2.間隙鎖觸發(fā)條件模糊:沒說明間隙鎖是在RR隔離級別下、且未命中唯一索引(或范圍查
詢)時才會觸發(fā)。
3.死鎖解決太淺:僅提到了順序加鎖,沒有涉及具體的業(yè)務(wù)場景,比如批量更新時的ID排
序處理。
高分回答示例:
InnoDB的鎖機制非常豐富,主要包含:
1.記錄鎖(RecordLock):鎖住具體的索引行。
2.間隙鎖(GapLock):鎖住兩個索引之間的區(qū)間(開區(qū)間),主要用于解決幻讀。
3.臨鍵鎖(Next-KeyLock):記錄鎖+間隙鎖(左開右閉),是RR級別下的默認(rèn)行鎖
算法。
關(guān)于間隙鎖(GapLock):
它在RepeatableRead隔離級別下生效。當(dāng)使用范圍查詢,或者等值查詢未命中唯
一索引時,InnoDB不僅鎖住數(shù)據(jù),還會鎖住數(shù)據(jù)周邊的空隙,防止其他事務(wù)插入新
數(shù)據(jù)(Insert),從而避免幻讀。這雖然保證了數(shù)據(jù)一致性,但也降低了并發(fā)度。
如何避免死鎖:
死鎖通常是因為兩個事務(wù)以不同順序鎖定資源造成的。在業(yè)務(wù)中,我通常采取以下
策略:
1.固定順序加鎖:比如批量更新庫存,我會先將商品ID在代碼層面進行排序,然后按順序
執(zhí)行Update,打破環(huán)路等待條件。
2.大事務(wù)拆解:將長事務(wù)拆分為多個短事務(wù),減少持有鎖的時間。
3.索引優(yōu)化:確保Update語句走索引,避免因索引失效導(dǎo)致鎖升級(行鎖變表鎖)或鎖范
圍擴大,增加死鎖概率。
Q15:Redis有哪些數(shù)據(jù)類型?在你的項目中具體用在什么場景?
?不好的回答示例:
Redis有5種基本類型:String、List、Set、Hash、Zset。
String用得最多,存Session或者緩存用戶信息。List可以做消息隊列。Set可以去
重。Hash存對象。Zset可以做排行榜。
這就是我們項目里主要用的,其他的像Bitmap什么的我沒怎么用過。
為什么這么回答不好:
1.回答千篇一律:這是最標(biāo)準(zhǔn)的“背書式”回答,沒有任何亮點,無法區(qū)分求職者水平。
2.場景缺乏深度:僅說了“存對象”、“做排行榜”,沒有結(jié)合具體業(yè)務(wù)痛點(如:為什么要用
Hash存對象而不用JSONString?)。
3.高級類型缺失:對于Bitmap、HyperLogLog、Geo等高級數(shù)據(jù)結(jié)構(gòu)只字未提,錯失了展
示技術(shù)廣度的機會。
高分回答示例:
在我的項目中,除了基礎(chǔ)的KV緩存,我深入使用了Redis的多種數(shù)據(jù)結(jié)構(gòu)來解決具
體業(yè)務(wù)問題:
1.String:不僅用于常規(guī)緩存,我還利用其原子遞增(INCR)特性實現(xiàn)了分布式ID生成和
接口限流(結(jié)合過期時間)。
2.Hash:我用于存儲電商購物車數(shù)據(jù)。Key是用戶ID,F(xiàn)ield是商品ID,Value是數(shù)量。相
比JSONString,Hash能單獨修改某個商品數(shù)量,減少了序列化開銷和網(wǎng)絡(luò)傳輸量。
3.ZSet(SortedSet):用于實現(xiàn)延時隊列。將任務(wù)執(zhí)行時間戳作為Score,輪詢獲取
Score小于當(dāng)前時間的任務(wù)進行消費。同時也用于實現(xiàn)熱搜排行榜。
4.Bitmap:這是一個亮點。我用它實現(xiàn)了用戶簽到功能。一個用戶一年的簽到記錄只需不
到4KB內(nèi)存(365bit),極大地節(jié)省了存儲空間。
5.HyperLogLog:用于統(tǒng)計網(wǎng)站的UV(獨立訪客)。在允許極小誤差的情況下,它能用
12KB內(nèi)存統(tǒng)計上億級別的數(shù)據(jù),替代了傳統(tǒng)的Set去重方案。
Q16:Redis的持久化機制RDB和AOF有什么區(qū)別?生產(chǎn)環(huán)境怎么選?
?不好的回答示例:
RDB是快照,把內(nèi)存數(shù)據(jù)dump到磁盤,文件小,恢復(fù)快,但是會丟數(shù)據(jù)。AOF是
日志,記錄每條寫命令,數(shù)據(jù)全,但是文件大,恢復(fù)慢。
生產(chǎn)環(huán)境一般兩個都開?;蛘咧婚_RDB,因為AOF太慢了。如果怕丟數(shù)據(jù)就開
AOF。
為什么這么回答不好:
1.理解片面:說“AOF太慢”是不準(zhǔn)確的,AOF的fsync策略(everysec)性能損耗是可以接
受的。
2.配置策略過時:僅僅說“兩個都開”,沒有提到Redis4.0引入的混合持久化機制,這是目
前的最佳實踐。
3.缺乏恢復(fù)場景分析:沒有提到RDB在災(zāi)難恢復(fù)(備份到遠程)中的優(yōu)勢。
高分回答示例:
Redis提供RDB和AOF兩種持久化機制,它們各有優(yōu)劣:
1.RDB(快照):
原理:在指定時間間隔內(nèi)生成數(shù)據(jù)集的時間點快照(fork子進程,寫時復(fù)制COW)。
優(yōu)點:文件緊湊,適合全量備份和災(zāi)難恢復(fù);恢復(fù)速度快。
缺點:無法實時持久化,服務(wù)器宕機會丟失最后一次快照后的數(shù)據(jù)。
2.AOF(追加文件):
原理:以日志形式記錄每次寫操作,重啟時重放命令恢復(fù)數(shù)據(jù)。
優(yōu)點:數(shù)據(jù)安全性高(支持秒級甚至同步刷盤),不丟數(shù)據(jù)。
缺點:文件體積大,重放速度慢于RDB。
生產(chǎn)環(huán)境最佳實踐:
雖然官方建議同時開啟,但在Redis4.0之后,我強烈推薦使用混合持久化(RDB
+AOF)。
配置aof-use-rdb-preambleyes。在AOF重寫時,將內(nèi)存數(shù)據(jù)以RDB格式寫入
AOF文件頭部,之后的增量數(shù)據(jù)以AOF文本追加。這樣既享受了RDB的快速加載,
又擁有了AOF的數(shù)據(jù)安全性,是目前的最優(yōu)解。
Q17:什么是緩存穿透、緩存擊穿和緩存雪崩?分別有什么解決方案?
?不好的回答示例:
緩存穿透就是查不到數(shù)據(jù),一直打數(shù)據(jù)庫。解決辦法是校驗參數(shù),或者用布隆過濾
器。
緩存擊穿就是熱點Key失效了,大量請求打過來。解決辦法是永不過期,或者加
鎖。
緩存雪崩就是大量Key同時失效。解決辦法是加隨機過期時間,或者搞集群。
這三個問題很常見,背下來就行了。
為什么這么回答不好:
1.定義區(qū)分度不夠:容易把穿透和擊穿搞混,描述過于口語化。
2.解決方案單一:對于“緩存擊穿”,只提到了互斥鎖,沒提到邏輯過期;對于“穿透”,沒提
到空對象緩存。
3.缺乏實戰(zhàn)細節(jié):比如布隆過濾器的誤判問題、加鎖的具體實現(xiàn)(分布式鎖)沒有展開。
高分回答示例:
這三個是緩存系統(tǒng)的高頻風(fēng)險點,我的理解和處理方案如下:
1.緩存穿透(查無此據(jù)):
現(xiàn)象:請求根本不存在的數(shù)據(jù)(如ID=-1),緩存和數(shù)據(jù)庫都查不到,導(dǎo)致請求直透
DB。
解決:*緩存空對象:即使DB查不到,也存一個null值到Redis并設(shè)較短過期時間。
布隆過濾器(BloomFilter):在請求到達緩存前,先通過位圖判斷Key是否存
在。雖然有誤判率,但能攔截絕大多數(shù)非法請求。
2.緩存擊穿(熱點失效):
現(xiàn)象:某個極度熱點Key(如秒殺商品)突然過期,高并發(fā)請求瞬間擊穿緩存打垮
DB。
解決:
互斥鎖(Mutex):緩存失效時,只允許一個線程去查DB重建緩存,其他線程等
待。
邏輯過期:永不過期,但Value中包含時間戳。后臺線程異步刷新數(shù)據(jù),保證高可
用。
3.緩存雪崩(集體崩塌):
現(xiàn)象:大量Key在同一時刻失效,或Redis宕機。
解決:
隨機TTL:在設(shè)置過期時間時添加隨機值(如1-5分鐘),打散失效時間。
高可用架構(gòu):搭建RedisSentinel或Cluster集群,防止單點故障。
多級緩存:引入Caffeine本地緩存作為一級屏障。
Q18:如何實現(xiàn)分布式鎖?基于Redis(Redisson)和Zookeeper實現(xiàn)的區(qū)別
是什么?
?不好的回答示例:
用Redis實現(xiàn)就是setnx,如果返回1就拿到鎖了。用完del刪掉。為了防止死鎖要加
過期時間。但是這有個問題,如果業(yè)務(wù)沒執(zhí)行完鎖就過期了怎么辦?所以可以用
Redisson,它有個看門狗。
Zookeeper就是創(chuàng)建臨時節(jié)點,誰創(chuàng)建成功誰拿鎖。ZK是強一致性的,Redis是
AP的。一般用Redis多一點,因為快。
為什么這么回答不好:
1.原生實現(xiàn)簡陋:簡單的setnx+expire不是原子操作,存在隱患(雖然新版Redis支
持原子命令,但回答未提及)。
2.Redisson原理淺:對“看門狗(Watchdog)”的機制解釋不清(每10s續(xù)期)。
3.ZK對比不夠深:沒有提到ZK的“順序節(jié)點+監(jiān)聽機制”避免驚群效應(yīng)的優(yōu)勢。
高分回答示例:
在生產(chǎn)環(huán)境中,我通常直接使用Redisson框架來實現(xiàn)Redis分布式鎖,因為它完
美解決了原生setnx的痛點:
1.Redisson原理(核心亮點):
原子性:底層使用Lua腳本保證加鎖和設(shè)置過期時間的原子性。
看門狗機制(Watchdog):解決了“業(yè)務(wù)沒跑完鎖就過期”的難題。默認(rèn)每10秒檢測
一次,如果線程還持有鎖,就自動延長過期時間,直到業(yè)務(wù)結(jié)束。
可重入性:結(jié)合Hash結(jié)構(gòu)記錄線程ID和重入次數(shù),支持可重入。
2.RedisvsZookeeper對比:
性能(CPvsAP):Redis是AP模型,基于內(nèi)存,性能極高(QPS10w+),但由
主從異步復(fù)制可能導(dǎo)致鎖丟失(Master宕機時)。Zookeeper是CP模型,基于ZAB
協(xié)議保證強一致性,數(shù)據(jù)更安全,但寫性能較差。
實現(xiàn)機制:ZK利用臨時順序節(jié)點+Watcher監(jiān)聽,實現(xiàn)了阻塞等待(公平鎖),避免
了無效輪詢。
選型建議:絕大多數(shù)互聯(lián)網(wǎng)高并發(fā)場景選Redis(Redisson),因為并發(fā)性能是
瓶頸;只有對鎖的可靠性要求極高(如金融核心轉(zhuǎn)賬)才考慮Zookeeper。
Q19:SpringBean的生命周期是怎樣的?BeanPostProcessor在其中起什么
作用?
?不好的回答示例:
Bean的生命周期就是實例化、屬性賦值、初始化、銷毀。
Spring啟動的時候先掃描類,然后new一個對象,然后依賴注入,填充屬性。然后
執(zhí)行init-method方法。最后容器關(guān)閉的時候執(zhí)行destroy方法。
BeanPostProcessor就是后置處理器,可以在初始化前后做一些事情,比如AOP
就是在后置處理器里做的。
為什么這么回答不好:
1.流程粗糙:漏掉了非常關(guān)鍵的“Aware接口回調(diào)”步驟(如BeanNameAware)。
2.BPP作用不全:僅提到了AOP,沒提到它在Spring內(nèi)部擴展中的核心地位(如注解處
理)。
3.缺乏宏觀視角:沒區(qū)分“實例化(Instantiation)”和“初始化(Initialization)”這兩個極易混
淆的概念。
高分回答示例:
SpringBean的生命周期非常復(fù)雜,但我習(xí)慣將其概括為“四步走+擴展點”:
1.實例化(Instantiation):
Spring利用反射(CreateBeanInstance)在堆中申請內(nèi)存,創(chuàng)建Bean的原始對
象(此時屬性還是null)。
2.屬性賦值(PopulateBean):
進行依賴注入(DI),將@Autowired或配置的屬性填充到Bean中。
3.初始化(Initialization)——核心邏輯集中區(qū):
Aware接口回調(diào):注入BeanName、BeanFactory等容器資源。
BeanPostProcessor(Before):執(zhí)行postProcessBeforeInitialization。
初始化方法:執(zhí)行@PostConstruct、InitializingBean接口方法、xml中的init-m
ethod。
BeanPostProcessor(After):執(zhí)行postProcessAfterInitialization。AOP代理
對象通常就是在這個階段生成的(如果需要代理,這里返回的就是Proxy對象而非原始
對象)。
4.銷毀(Destruction):
容器關(guān)閉時,執(zhí)行@PreDestroy或DisposableBean接口方法。
BeanPostProcessor的作用:
它是Spring最重要的擴展點(Hook)。AOP動態(tài)代理、@Value注解解析、
@Autowired依賴注入等核心功能,本質(zhì)上都是通過實現(xiàn)不同的
BeanPostProcessor來介入Bean生命周期完成的。
Q20:Spring是如何解決循環(huán)依賴問題的?三級緩存的作用分別是什么?
?不好的回答示例:
循環(huán)依賴就是A依賴B,B依賴A。Spring用三級緩存解決的。
第一級存成品對象,第二級存半成品,第三級存工廠。
創(chuàng)建A的時候,先把A放到緩存里,然后去創(chuàng)建B,B發(fā)現(xiàn)A在緩存里,就直接拿來用
了,這樣就解決了。但是構(gòu)造器循環(huán)依賴解決不了。
為什么這么回答不好:
1.邏輯斷層:沒解釋清楚為什么需要第三級緩存?直接用兩級為什么不行?(核心在于
AOP代理對象的引用問題)。
2.對象狀態(tài)描述不清:什么是“成品”?什么是“半成品”?專業(yè)術(shù)語應(yīng)該是“完全初始化的
Bean”和“早期引用”。
3.缺乏場景限制:沒強調(diào)Spring只能解決單例(Singleton)且Setter注入的循環(huán)依賴。
高分回答示例:
Spring通過三級緩存解決了單例模式下Setter注入的循環(huán)依賴問題。其核心思想
是“提前暴露早期引用”。
三級緩存結(jié)構(gòu):
1.一級緩存(singletonObjects):存放經(jīng)歷了完整生命周期的、完全可用的單例Bean。
2.二級緩存(earlySingletonObjects):存放早期的Bean對象(屬性還未填充),用于
中途被引用。
3.三級緩存(singletonFactories):存放ObjectFactory(函數(shù)式接口),用于生成
Bean的早期引用。
解決流程(A依賴B,B依賴A):
1.A實例化后,將一個lambda表達式(工廠)存入三級緩存。
2.A填充屬性時發(fā)現(xiàn)需要B,去創(chuàng)建B。
3.B填充屬性時發(fā)現(xiàn)需要A,依次查一級、二級緩存皆無,從三級緩存中獲取A的工廠對
象,調(diào)用getObject()拿到A的早期引用(如果有AOP,此時會提前創(chuàng)建代理對象),
并放入二級緩存。
4.B完成初始化,存入一級緩存。
5.A拿到B,完成初始化,從二級緩存移動到一級緩存。
為什么需要第三級?
核心是為了解決AOP代理問題。如果直接將原始對象放入二級緩存,當(dāng)A需要被代
理時,B引用到的將是原始A而不是代理A,違背了單例原則。三級緩存的工廠機制
延遲了代理對象的創(chuàng)建,只有在發(fā)生循環(huán)依賴時才提前生成代理對象。
Q21:SpringBoot的自動裝配原理是什么?@SpringBootApplication注解背
后做了什么?
?不好的回答示例:
SpringBoot自動裝配就是不用寫xml配置了,它自己能把Bean掃描進去。@Spring
BootApplication這個注解是一個組合注解,里面有@Configuration,還有開啟自動
配置的注解。原理就是Spring啟動的時候去classpath下找jar包,看有沒有對應(yīng)的
依賴,如果有就自動配置。比如引入了Redis包,它就自動配好RedisTemplate。
具體是讀取哪個文件我忘了,好像是META-INF下面的一個什么文件。
為什么這么回答不好:
1.原理描述模糊:“找jar包”這種說法太通俗,沒有點出核心機制——SPI(ServiceProvider
Interface)和條件注解。
2.關(guān)鍵文件名缺失:沒有準(zhǔn)確說出spring.factories(或SpringBoot3.0后的AutoConfi
guration.imports),這是自動裝配的靈魂。
3.缺乏注解拆解:對@SpringBootApplication的三個核心組成部分(@SpringBootConfig
uration、@ComponentScan、@EnableAutoConfiguration)表述不全。
高分回答示例:
@SpringBootApplication是一個復(fù)合注解,其核心邏輯由三個注解支撐:@SpringBo
otConfiguration(聲明配置類)、@ComponentScan(組件掃描)和最核心的**@En
ableAutoConfiguration**。
自動裝配的原理可以概括為“SPI機制+條件注解”:
1.加載候選配置(SPI):
當(dāng)SpringBoot啟動時,@EnableAutoConfiguration會利用
SpringFactoriesLoader掃描所有Jar包下的META-INF/spring.factories文
件(SpringBoot3.0后改為META-INF/spring/....imports)。這個文件中列出
了所有可能需要自動裝配的配置類(ConfigurationClass),比如
RedisAutoConfiguration。
2.篩選配置(去偽存真):
加載進來的幾百個配置類不會全部生效,而是通過@Conditional系列注解進行
過濾。
例如RedisAutoConfiguration上標(biāo)有@ConditionalOnClass(RedisOperations.cla
ss)。
只有當(dāng)你項目中引入了spring-boot-starter-data-redis依賴,classpath中存在
RedisOperations類時,這個配置才會生效,RedisTemplate才會被注入容器。
3.結(jié)果:
這種“約定大于配置”的機制,讓我們無需手動編寫大量重復(fù)的Bean定義,真正實
現(xiàn)了開箱即用。
Q22:動態(tài)代理JDKProxy和CGLIB有什么區(qū)別?SpringAOP默認(rèn)使用哪種?
?不好的回答示例:
JDK代理是基于接口的,CGLIB是基于繼承的。JDK代理是用反射做的,CGLIB是
用字節(jié)碼技術(shù)做的。效率上來說,以前說CGLIB快,現(xiàn)在JDK快了。
SpringAOP默認(rèn)是用JDK代理。如果類沒有接口,就自動切到CGLIB。不過在
SpringBoot里好像有點不一樣,好像默認(rèn)都是CGLIB了。具體的配置參數(shù)我不記
得了。
為什么這么回答不好:
1.技術(shù)細節(jié)淺嘗輒止:沒有提到JDK代理的核心類Proxy和InvocationHandler,也沒提
到CGLIB底層的ASM框架。
2.性能對比滯后:關(guān)于“快慢”的討論缺乏JDK版本的前提,容易產(chǎn)生誤導(dǎo)。
3.默認(rèn)行為不確定:對SpringBoot2.x之后默認(rèn)策略的變更(默認(rèn)強制使用CGLIB)表述
含糊,顯得對新特性關(guān)注不足。
高分回答示例:
JDK動態(tài)代理和CGLIB是Java實現(xiàn)代理模式的兩種核心手段,主要區(qū)別如下:
1.實現(xiàn)原理:
JDKProxy:要求目標(biāo)類必須實現(xiàn)
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025-2030長江經(jīng)濟帶綠色制造行業(yè)競爭態(tài)勢發(fā)展現(xiàn)狀規(guī)劃分析研究報告
- 2025-2030針對性強效學(xué)習(xí)策略研究及認(rèn)知科學(xué)與應(yīng)用效果分析報告
- 2025-2030醬油制作行業(yè)投資分析及融資策略研究報告
- 2025-2030造船行業(yè)市場發(fā)展現(xiàn)狀分析和投資決策規(guī)劃研究報告
- 2026年1月廣東深圳市第七高級中學(xué)招聘專任教師4人備考題庫及參考答案詳解
- 2026秋招:博賽礦業(yè)題庫及答案
- 廠房出售介紹
- (2026)運用PDCA提高護理級別劃分正確率課件
- 教育培訓(xùn)機構(gòu)教師會議
- 異位癥移植方案
- 2025年海管水平定向鉆穿越方案研究
- 全國網(wǎng)絡(luò)安全行業(yè)職業(yè)技能大賽(網(wǎng)絡(luò)安全管理員)考試題及答案
- 攝影家協(xié)會作品評選打分細則
- 電子產(chǎn)品三維建模設(shè)計細則
- 2025年中國道路交通毫米波雷達市場研究報告
- 設(shè)計交付:10kV及以下配網(wǎng)工程的標(biāo)準(zhǔn)與實踐
- 大學(xué)高數(shù)基礎(chǔ)講解課件
- hop安全培訓(xùn)課件
- 固井質(zhì)量監(jiān)督制度
- 中華人民共和國職業(yè)分類大典是(專業(yè)職業(yè)分類明細)
- 2025年中考英語復(fù)習(xí)必背1600課標(biāo)詞匯(30天記背)
評論
0/150
提交評論