Java常見面試題以及答案匯總大全(2025最新版)
一、Java基礎(chǔ)語法與核心特性
1. Java的核心特性有哪些?
答案:
- 跨平臺(tái)性(Write Once, Run Anywhere):通過JVM(Java虛擬機(jī))實(shí)現(xiàn),字節(jié)碼文件可在任意支持JVM的操作系統(tǒng)運(yùn)行;
- 面向?qū)ο螅∣OP):封裝、繼承、多態(tài)三大核心特性;
- 安全性:支持沙箱機(jī)制、字節(jié)碼校驗(yàn)、權(quán)限控制(如文件IO權(quán)限);
- 健壯性:自動(dòng)垃圾回收(GC)避免內(nèi)存泄漏,強(qiáng)類型檢查、異常處理機(jī)制減少運(yùn)行時(shí)錯(cuò)誤;
- 分布式:支持RMI(遠(yuǎn)程方法調(diào)用)、HTTP協(xié)議,便于開發(fā)分布式應(yīng)用;
- 多線程:內(nèi)置多線程API,支持并發(fā)編程。
2. 基本數(shù)據(jù)類型與包裝類的區(qū)別?
答案:
| 維度 | 基本數(shù)據(jù)類型(如int、float) | 包裝類(如Integer、Float) |
|---|---|---|
| 本質(zhì) | 原始值,無對象屬性 | 引用類型,繼承Object類 |
| 默認(rèn)值 | 有(如int默認(rèn)0,boolean默認(rèn)false) | 無,默認(rèn)null |
| 適用場景 | 簡單運(yùn)算、局部變量,效率高 | 集合框架(如List)、泛型、需要null值的場景 |
| 緩存機(jī)制 | 無 | 部分包裝類(Integer[-128~127]、Byte、Short等)有常量池緩存 |
關(guān)鍵考點(diǎn):
- 自動(dòng)裝箱/拆箱:Java 5+特性,編譯器自動(dòng)完成基本類型與包裝類的轉(zhuǎn)換(如
int i = new Integer(10)→拆箱,Integer j = 10→裝箱); - 緩存陷阱:
Integer a = 127; Integer b = 127;→a == b為true(復(fù)用緩存);Integer c = 128; Integer d = 128;→c == d為false(新建對象),需用equals()比較值。
3. String、StringBuffer、StringBuilder的區(qū)別?
答案:
核心差異在于可變性和線程安全:
- String:不可變(底層是final修飾的char數(shù)組/JDK9+ byte數(shù)組),每次修改都會(huì)創(chuàng)建新對象,效率低;
- StringBuffer:可變,線程安全(方法加synchronized鎖),適用于多線程環(huán)境的字符串拼接;
- StringBuilder:可變,線程不安全,效率高于StringBuffer,適用于單線程環(huán)境的字符串拼接。
底層原理:
String的不可變性源于private final char value[](JDK8),final修飾數(shù)組引用不可變,且數(shù)組無暴露修改接口;StringBuffer和StringBuilder繼承AbstractStringBuilder,底層是可變char數(shù)組,擴(kuò)容機(jī)制為:默認(rèn)初始容量16,當(dāng)長度超過容量時(shí),新容量=原容量×2+2,不足則直接擴(kuò)容到所需長度。
4. final關(guān)鍵字的三種用法?
答案:
- 修飾類:類不可被繼承(如String、Math),子類無法擴(kuò)展其功能;
- 修飾方法:方法不可被重寫,可防止子類修改父類核心邏輯;
- 修飾變量:變量不可被重新賦值(基本類型:值不可變;引用類型:引用地址不可變,但對象內(nèi)容可修改)。
易錯(cuò)點(diǎn):final int[] arr = {1,2,3}; arr[0] = 4; 合法(數(shù)組內(nèi)容可變);arr = new int[5]; 非法(引用地址不可變)。
5. 接口(Interface)與抽象類(Abstract Class)的區(qū)別?
答案:
| 維度 | 接口(Interface) | 抽象類(Abstract Class) |
|---|---|---|
| 繼承方式 | 多實(shí)現(xiàn)(一個(gè)類可實(shí)現(xiàn)多個(gè)接口) | 單繼承(一個(gè)類只能繼承一個(gè)抽象類) |
| 成員變量 | 只能是public static final常量 | 可包含普通變量、靜態(tài)變量、常量 |
| 成員方法 | JDK8前:只能是抽象方法;JDK8+:支持default/static方法;JDK9+:支持private方法 | 可包含抽象方法、普通方法、靜態(tài)方法 |
| 構(gòu)造方法 | 無 | 有(不能實(shí)例化,供子類調(diào)用) |
| 設(shè)計(jì)目的 | 定義行為規(guī)范,解耦(如List接口) | 定義類的模板,復(fù)用代碼(如HttpServlet) |
應(yīng)用場景:
- 接口:不同類需統(tǒng)一行為但實(shí)現(xiàn)不同(如
Runnable接口); - 抽象類:同類組件共享核心邏輯(如
AbstractList封裝List的公共方法)。
6. Java異常體系的核心結(jié)構(gòu)?
答案:
- 頂層父類:
Throwable,包含兩個(gè)核心子類:Error:嚴(yán)重錯(cuò)誤(如OutOfMemoryError、StackOverflowError),程序無法恢復(fù),無需捕獲;Exception:可處理的異常,分為:- 受檢異常(Checked Exception):編譯時(shí)必須捕獲(如IOException、SQLException);
- 非受檢異常(Unchecked Exception):運(yùn)行時(shí)異常(如NullPointerException、ArrayIndexOutOfBoundsException),繼承自
RuntimeException,無需強(qiáng)制捕獲。
異常處理關(guān)鍵字:
try:包裹可能拋出異常的代碼;catch:捕獲并處理異常(可多個(gè)catch,按異常子類→父類順序);finally:無論是否拋出異常,都會(huì)執(zhí)行(常用于關(guān)閉資源,如流、數(shù)據(jù)庫連接);throw:手動(dòng)拋出異常(如throw new IllegalArgumentException("參數(shù)非法"));throws:聲明方法可能拋出的異常,告知調(diào)用者。
最佳實(shí)踐:
- 避免捕獲
Throwable(包含Error,無法處理); - 不要忽略異常(空catch塊);
- 優(yōu)先使用try-with-resources自動(dòng)關(guān)閉資源(JDK7+,支持實(shí)現(xiàn)
AutoCloseable接口的類)。
二、Java集合框架
1. 集合框架的核心接口與繼承關(guān)系?
答案:
Java集合框架核心分為兩大體系(均位于java.util包):
- 單列集合(
Collection):存儲(chǔ)單個(gè)元素,核心子接口:List:有序、可重復(fù)(如ArrayList、LinkedList、Vector);Set:無序、不可重復(fù)(如HashSet、TreeSet、LinkedHashSet);
- 雙列集合(
Map):存儲(chǔ)鍵值對(key-value),核心實(shí)現(xiàn)類:HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap。
關(guān)鍵特性:
List:支持索引訪問,可通過get(int index)獲取元素;Set:基于equals()和hashCode()保證元素唯一性;Map:key不可重復(fù)(重復(fù)會(huì)覆蓋value),value可重復(fù);JDK8+中Map提供forEach()、computeIfAbsent()等便捷方法。
2. ArrayList與LinkedList的區(qū)別?
答案:
| 維度 | ArrayList(數(shù)組實(shí)現(xiàn)) | LinkedList(雙向鏈表實(shí)現(xiàn)) |
|---|---|---|
| 底層結(jié)構(gòu) | 動(dòng)態(tài)數(shù)組(Object[]) | 雙向鏈表(每個(gè)節(jié)點(diǎn)存儲(chǔ)prev、next、value) |
| 訪問效率 | 隨機(jī)訪問快(O(1)),通過索引直接定位 | 隨機(jī)訪問慢(O(n)),需遍歷鏈表 |
| 增刪效率 | 尾部增刪快(O(1)),中間增刪慢(需移動(dòng)數(shù)組元素,O(n)) | 中間增刪快(O(1),只需修改節(jié)點(diǎn)指針),尾部增刪需遍歷到末尾(O(n),可通過last指針優(yōu)化) |
| 內(nèi)存占用 | 連續(xù)內(nèi)存,占用少(無額外指針開銷) | 非連續(xù)內(nèi)存,每個(gè)節(jié)點(diǎn)有額外指針開銷 |
| 線程安全 | 不安全 | 不安全 |
應(yīng)用場景:
- ArrayList:頻繁查詢、少量增刪(如數(shù)據(jù)展示列表);
- LinkedList:頻繁中間增刪、隊(duì)列/棧實(shí)現(xiàn)(如消息隊(duì)列)。
3. HashMap的底層實(shí)現(xiàn)原理(JDK1.7 vs JDK1.8)?
答案:
HashMap是基于“哈希表”的Map實(shí)現(xiàn),核心是“數(shù)組+鏈表/紅黑樹”的結(jié)構(gòu),目的是平衡查詢和增刪效率。
JDK1.7實(shí)現(xiàn):
- 底層:數(shù)組(Entry[])+ 單向鏈表;
- 存儲(chǔ)流程:
- 計(jì)算key的hashCode() → 經(jīng)過哈希擾動(dòng)(
hashCode() ^ (hashCode() >>> 16))得到哈希值; - 哈希值 & 數(shù)組長度-1 → 定位數(shù)組索引(保證索引在數(shù)組范圍內(nèi));
- 若索引位置無元素,直接存儲(chǔ);若有元素(哈希沖突),采用“頭插法”插入鏈表。
- 計(jì)算key的hashCode() → 經(jīng)過哈希擾動(dòng)(
JDK1.8優(yōu)化:
- 底層:數(shù)組(Node[])+ 單向鏈表 + 紅黑樹(鏈表長度≥8且數(shù)組長度≥64時(shí),鏈表轉(zhuǎn)為紅黑樹;鏈表長度≤6時(shí),紅黑樹轉(zhuǎn)回鏈表);
- 存儲(chǔ)流程:哈希擾動(dòng)邏輯不變,哈希沖突時(shí)采用“尾插法”插入鏈表(避免JDK1.7頭插法導(dǎo)致的鏈表循環(huán)問題);
- 其他優(yōu)化:
- 擴(kuò)容機(jī)制:默認(rèn)初始容量16,負(fù)載因子0.75,當(dāng)元素個(gè)數(shù)≥容量×負(fù)載因子時(shí),觸發(fā)擴(kuò)容(新容量=原容量×2);
- 支持null key和null value(null key的hash值為0,存儲(chǔ)在數(shù)組索引0位置)。
哈希沖突解決:
- 哈希擾動(dòng):減少哈希值的高位忽略問題,提升哈希分布均勻性;
- 鏈地址法:沖突元素以鏈表/紅黑樹形式存儲(chǔ)在同一索引位置。
線程安全問題:
HashMap線程不安全,多線程環(huán)境下可能出現(xiàn):
- JDK1.7:擴(kuò)容時(shí)頭插法導(dǎo)致鏈表循環(huán);
- JDK1.8:put操作可能覆蓋數(shù)據(jù)。
解決方案:使用ConcurrentHashMap或Collections.synchronizedMap(new HashMap<>())。
4. ConcurrentHashMap的線程安全實(shí)現(xiàn)(JDK1.7 vs JDK1.8)?
答案:
ConcurrentHashMap是HashMap的線程安全版本,核心差異在于鎖機(jī)制:
JDK1.7實(shí)現(xiàn):
- 底層:Segment數(shù)組 + HashEntry數(shù)組 + 鏈表;
- 鎖機(jī)制:分段鎖(Segment繼承ReentrantLock),每個(gè)Segment對應(yīng)一把鎖,僅鎖定當(dāng)前Segment,支持多線程并發(fā)訪問不同Segment,提高并發(fā)度(默認(rèn)Segment數(shù)量16,支持16個(gè)線程并發(fā))。
JDK1.8優(yōu)化:
- 底層:Node數(shù)組 + 鏈表 + 紅黑樹(與HashMap結(jié)構(gòu)一致);
- 鎖機(jī)制:放棄分段鎖,采用“CAS + synchronized”實(shí)現(xiàn)線程安全:
- 無沖突時(shí):CAS操作原子性插入元素;
- 有沖突時(shí):synchronized鎖定當(dāng)前鏈表/紅黑樹的頭節(jié)點(diǎn),僅鎖定沖突節(jié)點(diǎn)所在的鏈表/樹,并發(fā)度更高;
- 其他優(yōu)化:支持
computeIfAbsent()、forEach()等原子操作,性能優(yōu)于JDK1.7。
5. HashSet的實(shí)現(xiàn)原理?
答案:
HashSet底層依賴HashMap實(shí)現(xiàn),核心邏輯:
- HashSet的構(gòu)造方法會(huì)創(chuàng)建一個(gè)HashMap實(shí)例;
- 存儲(chǔ)元素時(shí):
add(E e)→ 調(diào)用HashMap的put(e, PRESENT),其中PRESENT是一個(gè)靜態(tài)空Object(僅占位,不存儲(chǔ)實(shí)際值); - 元素唯一性:依賴HashMap的key不可重復(fù)特性(通過
equals()和hashCode()判斷); - 特性:無序、不可重復(fù)、線程不安全,查詢/增刪效率O(1)(無哈希沖突時(shí))。
關(guān)鍵考點(diǎn):
自定義對象作為HashSet元素時(shí),必須重寫equals()和hashCode(),否則無法保證唯一性(默認(rèn)使用Object類的方法,比較對象地址)。重寫原則:
- 兩個(gè)對象
equals()返回true →hashCode()必須相等; - 兩個(gè)對象
hashCode()相等 →equals()不一定返回true(哈希沖突)。
三、Java多線程與并發(fā)
1. Java創(chuàng)建線程的三種方式?
答案:
- 方式1:繼承
Thread類,重寫run()方法(線程執(zhí)行邏輯),調(diào)用start()方法啟動(dòng)線程(底層調(diào)用start0()native方法創(chuàng)建操作系統(tǒng)線程);class MyThread extends Thread { @Override public void run() { System.out.println("Thread running"); } } // 啟動(dòng) new MyThread().start(); - 方式2:實(shí)現(xiàn)
Runnable接口,重寫run()方法,將實(shí)例傳入Thread類啟動(dòng);class MyRunnable implements Runnable { @Override public void run() { System.out.println("Runnable running"); } } // 啟動(dòng) new Thread(new MyRunnable()).start(); - 方式3:實(shí)現(xiàn)
Callable接口,重寫call()方法(支持返回值和拋出異常),通過FutureTask包裝后傳入Thread啟動(dòng);class MyCallable implements Callable<String> { @Override public String call() throws Exception { return "Callable result"; } } // 啟動(dòng) FutureTask<String> future = new FutureTask<>(new MyCallable()); new Thread(future).start(); String result = future.get(); // 獲取返回值(阻塞直到線程完成)
對比:
- 繼承Thread:無法繼承其他類(Java單繼承),代碼簡單;
- 實(shí)現(xiàn)Runnable/Callable:可繼承其他類,支持多線程共享資源,Callable支持返回值和異常處理。
2. 線程的生命周期與狀態(tài)轉(zhuǎn)換?
答案:
Java線程有6種狀態(tài)(定義在Thread.State枚舉中),狀態(tài)轉(zhuǎn)換如下:
NEW:線程創(chuàng)建后未啟動(dòng)(未調(diào)用start());RUNNABLE:線程啟動(dòng)后,處于可運(yùn)行狀態(tài)(包含操作系統(tǒng)的“運(yùn)行中”和“就緒”);BLOCKED:線程等待同步鎖(如synchronized未獲取鎖時(shí));WAITING:線程無限期等待(如調(diào)用Object.wait()、Thread.join()、LockSupport.park(),需其他線程喚醒);TIMED_WAITING:線程限時(shí)等待(如調(diào)用Thread.sleep(ms)、Object.wait(ms)、Thread.join(ms),超時(shí)自動(dòng)喚醒);TERMINATED:線程執(zhí)行完成或異常終止。
核心轉(zhuǎn)換路徑:NEW → RUNNABLE(start()) → TERMINATED(執(zhí)行完成);RUNNABLE → BLOCKED(競爭鎖失?。?→ RUNNABLE(獲取鎖);RUNNABLE → WAITING/TIMED_WAITING(調(diào)用等待方法) → RUNNABLE(被喚醒/超時(shí))。
3. synchronized與Lock的區(qū)別?
答案:
| 維度 | synchronized(內(nèi)置鎖) | Lock(顯式鎖,如ReentrantLock) |
|---|---|---|
| 鎖實(shí)現(xiàn) | JVM層面實(shí)現(xiàn)(C++代碼) | JDK層面實(shí)現(xiàn)(Java代碼) |
| 鎖獲取與釋放 | 自動(dòng)獲?。ㄟM(jìn)入同步塊)、自動(dòng)釋放(退出同步塊/異常) | 手動(dòng)獲?。╨ock())、手動(dòng)釋放(unlock(),需在finally中執(zhí)行) |
| 鎖類型 | 可重入鎖、非公平鎖(默認(rèn)),JDK6+支持偏向鎖/輕量級鎖/重量級鎖升級 | 可重入鎖,支持公平鎖/非公平鎖(構(gòu)造函數(shù)指定) |
| 功能擴(kuò)展 | 無(僅支持基礎(chǔ)同步) | 支持中斷鎖(lockInterruptibly())、超時(shí)鎖(tryLock(ms))、條件變量(Condition)、讀寫鎖(ReentrantReadWriteLock) |
| 性能 | JDK6+優(yōu)化后,性能接近Lock | 高并發(fā)場景下性能更優(yōu),靈活度高 |
應(yīng)用場景:
- synchronized:簡單同步場景(如單例模式、簡單方法同步),代碼簡潔,無需手動(dòng)管理鎖;
- Lock:復(fù)雜并發(fā)場景(如超時(shí)獲取鎖、中斷鎖、讀寫分離),如緩存系統(tǒng)、分布式鎖實(shí)現(xiàn)。
4. volatile關(guān)鍵字的作用?
答案:
volatile是Java提供的輕量級同步機(jī)制,核心作用有兩個(gè):
- 保證可見性:一個(gè)線程修改volatile變量后,其他線程能立即看到最新值(禁止CPU緩存,變量讀寫直接操作主內(nèi)存);
- 禁止指令重排序:編譯器和CPU會(huì)對指令重排序優(yōu)化,volatile通過內(nèi)存屏障(Memory Barrier)阻止重排序(如DCL單例模式中,volatile修飾實(shí)例變量防止指令重排導(dǎo)致的空指針)。
局限性:
- 不保證原子性:如
volatile int i = 0; i++;非原子操作(包含讀取、加1、寫入三步),多線程下可能出現(xiàn)計(jì)數(shù)錯(cuò)誤,需配合synchronized或AtomicInteger使用; - 不能替代鎖:僅適用于“單寫多讀”或“狀態(tài)標(biāo)記”場景(如
volatile boolean flag = false;控制線程啟停)。
經(jīng)典應(yīng)用:雙重校驗(yàn)鎖(DCL)單例模式:
public class Singleton {
// volatile禁止指令重排,防止instance未初始化完成就被其他線程獲取
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次校驗(yàn)(無鎖,提高效率)
synchronized (Singleton.class) { // 加鎖
if (instance == null) { // 第二次校驗(yàn)(防止多線程并發(fā)創(chuàng)建)
instance = new Singleton(); // 禁止重排:分配內(nèi)存→初始化→賦值
}
}
}
return instance;
}
}5. 線程池的核心參數(shù)與工作原理?
答案:
Java線程池核心類是ThreadPoolExecutor,基于“池化思想”減少線程創(chuàng)建/銷毀開銷,提高并發(fā)效率。
核心參數(shù)(構(gòu)造方法):
public ThreadPoolExecutor(
int corePoolSize, // 核心線程數(shù)(常駐線程,即使空閑也不銷毀)
int maximumPoolSize, // 最大線程數(shù)(核心線程+臨時(shí)線程的總上限)
long keepAliveTime, // 臨時(shí)線程空閑時(shí)間(超過則銷毀)
TimeUnit unit, // keepAliveTime的時(shí)間單位
BlockingQueue<Runnable> workQueue, // 任務(wù)阻塞隊(duì)列(核心線程滿時(shí),任務(wù)入隊(duì))
ThreadFactory threadFactory, // 線程創(chuàng)建工廠(自定義線程名稱、優(yōu)先級等)
RejectedExecutionHandler handler // 拒絕策略(隊(duì)列和最大線程數(shù)都滿時(shí),處理新任務(wù))
)工作原理:
- 提交任務(wù)時(shí),若核心線程數(shù)未滿,創(chuàng)建核心線程執(zhí)行任務(wù);
- 核心線程滿時(shí),任務(wù)加入阻塞隊(duì)列;
- 隊(duì)列滿時(shí),若未達(dá)到最大線程數(shù),創(chuàng)建臨時(shí)線程執(zhí)行任務(wù);
- 臨時(shí)線程空閑時(shí)間超過keepAliveTime,銷毀臨時(shí)線程;
- 隊(duì)列和最大線程數(shù)都滿時(shí),執(zhí)行拒絕策略。
常見拒絕策略:
AbortPolicy(默認(rèn)):直接拋出RejectedExecutionException;CallerRunsPolicy:由提交任務(wù)的線程(調(diào)用者)執(zhí)行任務(wù);DiscardPolicy:直接丟棄新任務(wù),無異常;DiscardOldestPolicy:丟棄隊(duì)列中最舊的任務(wù),加入新任務(wù)。
常見線程池(Executors工具類):
Executors.newFixedThreadPool(n):固定核心線程數(shù)和最大線程數(shù)(n),隊(duì)列無界(LinkedBlockingQueue);Executors.newCachedThreadPool():核心線程數(shù)0,最大線程數(shù)Integer.MAX_VALUE,臨時(shí)線程空閑60秒銷毀,隊(duì)列同步移交(SynchronousQueue);Executors.newSingleThreadExecutor():核心線程數(shù)1,最大線程數(shù)1,隊(duì)列無界,保證任務(wù)串行執(zhí)行;Executors.newScheduledThreadPool(n):核心線程數(shù)n,支持定時(shí)/延遲執(zhí)行任務(wù)(ScheduledFutureTask)。
注意:阿里巴巴Java開發(fā)手冊禁止使用Executors創(chuàng)建線程池,原因是:
newFixedThreadPool/newSingleThreadExecutor:隊(duì)列無界,可能導(dǎo)致OOM;newCachedThreadPool:最大線程數(shù)無界,可能創(chuàng)建大量線程導(dǎo)致CPU/內(nèi)存耗盡。
推薦直接使用ThreadPoolExecutor構(gòu)造方法,指定合理參數(shù)(如核心線程數(shù)=CPU核心數(shù)±1,隊(duì)列使用有界隊(duì)列)。
6. ThreadLocal的原理與內(nèi)存泄漏問題?
答案:
ThreadLocal是線程本地存儲(chǔ)工具,允許每個(gè)線程擁有獨(dú)立的變量副本,避免多線程共享變量的并發(fā)問題。
原理:
- 底層結(jié)構(gòu):每個(gè)Thread對象持有一個(gè)
ThreadLocalMap(ThreadLocal的內(nèi)部類),ThreadLocalMap的key是ThreadLocal實(shí)例(弱引用),value是線程本地變量副本; - 核心方法:
set(T value):獲取當(dāng)前線程的ThreadLocalMap,將(當(dāng)前ThreadLocal實(shí)例,value)存入;get():獲取當(dāng)前線程的ThreadLocalMap,根據(jù)當(dāng)前ThreadLocal實(shí)例獲取value,無則調(diào)用initialValue()初始化;remove():刪除當(dāng)前線程的ThreadLocalMap中對應(yīng)的鍵值對。
內(nèi)存泄漏問題:
- 原因:ThreadLocalMap的key是弱引用(
WeakReference<ThreadLocal<?>>),當(dāng)ThreadLocal實(shí)例被回收(如外部引用置null),key會(huì)變成null,而value是強(qiáng)引用,若線程未結(jié)束(如線程池核心線程),value無法被GC回收,導(dǎo)致內(nèi)存泄漏; - 解決方案:
- 用完ThreadLocal后調(diào)用
remove()方法刪除value; - 避免使用靜態(tài)ThreadLocal(生命周期長,易導(dǎo)致內(nèi)存泄漏);
- 線程池場景下,確保任務(wù)執(zhí)行完成后清理ThreadLocal變量。
- 用完ThreadLocal后調(diào)用
應(yīng)用場景:
- 存儲(chǔ)線程上下文信息(如用戶登錄信息、數(shù)據(jù)庫連接、事務(wù)對象);
- 避免方法參數(shù)傳遞(如Spring的
RequestContextHolder底層使用ThreadLocal存儲(chǔ)HttpServletRequest)。
四、JVM核心原理
1. JVM內(nèi)存模型(運(yùn)行時(shí)數(shù)據(jù)區(qū))?
答案:
JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)分為5個(gè)部分(基于JDK8):
- 程序計(jì)數(shù)器(Program Counter Register):
- 作用:存儲(chǔ)當(dāng)前線程執(zhí)行的字節(jié)碼指令地址(行號(hào)),線程切換時(shí)恢復(fù)執(zhí)行位置;
- 特點(diǎn):線程私有(每個(gè)線程一個(gè)),無OOM風(fēng)險(xiǎn)(唯一不會(huì)拋出OutOfMemoryError的區(qū)域)。
- 虛擬機(jī)棧(VM Stack):
- 作用:存儲(chǔ)線程執(zhí)行方法時(shí)的棧幀(包含局部變量表、操作數(shù)棧、方法出口等);
- 特點(diǎn):線程私有,棧幀入棧(方法調(diào)用)和出棧(方法返回)對應(yīng)方法執(zhí)行生命周期;
- 異常:棧深度超過JVM限制→
StackOverflowError(如遞歸調(diào)用無終止);棧擴(kuò)展時(shí)內(nèi)存不足→OutOfMemoryError。
- 本地方法棧(Native Method Stack):
- 作用:與虛擬機(jī)棧類似,僅支持Native方法(如Thread.start0())的執(zhí)行;
- 特點(diǎn):線程私有,可能拋出
StackOverflowError和OutOfMemoryError。
- 堆(Heap):
- 作用:存儲(chǔ)對象實(shí)例和數(shù)組,是JVM內(nèi)存最大的區(qū)域,也是GC的主要區(qū)域;
- 特點(diǎn):線程共享,可通過
-Xms(初始堆大小)、-Xmx(最大堆大小)配置; - 分區(qū)(邏輯劃分):
- 年輕代(Young Generation):分為Eden區(qū)、Survivor0(S0)區(qū)、Survivor1(S1)區(qū),比例默認(rèn)8:1:1;
- 老年代(Old Generation):存儲(chǔ)存活時(shí)間長的對象(年輕代對象多次GC后存活則進(jìn)入老年代);
- 元空間(Metaspace,JDK8+):替代永久代,存儲(chǔ)類元信息(類名、方法信息、字段信息),使用本地內(nèi)存,默認(rèn)無大小限制(可通過
-XX:MetaspaceSize、-XX:MaxMetaspaceSize配置)。
- 異常:堆內(nèi)存不足→
OutOfMemoryError: Java heap space;元空間不足→OutOfMemoryError: Metaspace。
- 方法區(qū)(Method Area):
- 作用:存儲(chǔ)類元信息、常量池(String常量池JDK7+移至堆)、靜態(tài)變量、即時(shí)編譯后的代碼;
- 特點(diǎn):線程共享,JDK8前為永久代(PermGen),JDK8后被元空間替代。
2. 垃圾回收(GC)的核心原理?
答案:
GC是JVM自動(dòng)回收堆中無用對象(無引用的對象)的過程,核心目標(biāo)是釋放內(nèi)存,避免內(nèi)存泄漏。
1. 垃圾判定算法:
- 引用計(jì)數(shù)法:給對象添加引用計(jì)數(shù)器,引用+1,引用失效-1,計(jì)數(shù)器為0則判定為垃圾;缺點(diǎn):無法解決循環(huán)引用(如A引用B,B引用A,計(jì)數(shù)器均為1,無法回收);
- 可達(dá)性分析算法(JVM采用):以“GC Roots”為起點(diǎn),遍歷對象引用鏈,不可達(dá)的對象判定為垃圾;
- GC Roots包括:虛擬機(jī)棧局部變量表中的引用、本地方法棧中的引用、方法區(qū)靜態(tài)變量引用、常量池引用、活躍線程的引用。
2. 常見GC算法:
標(biāo)記-清除算法(Mark-Sweep):
- 步驟:標(biāo)記垃圾對象→清除垃圾對象;
- 優(yōu)點(diǎn):簡單高效;
- 缺點(diǎn):產(chǎn)生內(nèi)存碎片,后續(xù)大對象分配可能失敗。
復(fù)制算法(Copying):
- 步驟:將內(nèi)存分為兩塊(如Eden和S0/S1),標(biāo)記存活對象→復(fù)制到另一塊內(nèi)存,清除原內(nèi)存;
- 優(yōu)點(diǎn):無內(nèi)存碎片,分配效率高;
- 缺點(diǎn):內(nèi)存利用率低(僅50%),適合年輕代(存活對象少)。
標(biāo)記-整理算法(Mark-Compact):
- 步驟:標(biāo)記存活對象→將存活對象向內(nèi)存一端移動(dòng)→清除另一端垃圾;
- 優(yōu)點(diǎn):無內(nèi)存碎片,內(nèi)存利用率高;
- 缺點(diǎn):移動(dòng)對象成本高,適合老年代(存活對象多)。
分代收集算法(JVM采用):
- 原理:根據(jù)對象存活時(shí)間劃分代(年輕代、老年代),不同代采用不同GC算法;
- 年輕代:存活對象少,采用復(fù)制算法;
- 老年代:存活對象多,采用標(biāo)記-清除或標(biāo)記-整理算法。
3. 常見GC收集器:
- Serial收集器:單線程GC,年輕代采用復(fù)制算法,老年代采用標(biāo)記-整理算法,適合單CPU環(huán)境(如客戶端應(yīng)用);
- Parallel Scavenge收集器:多線程GC,年輕代復(fù)制算法,追求高吞吐量(吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+GC時(shí)間)),適合服務(wù)器應(yīng)用;
- ParNew收集器:Parallel Scavenge的多線程版本,支持與CMS收集器配合;
- CMS收集器(Concurrent Mark Sweep):老年代GC,基于標(biāo)記-清除算法,并發(fā)收集(與用戶線程同時(shí)執(zhí)行),追求低延遲,適合響應(yīng)時(shí)間敏感的應(yīng)用(如Web應(yīng)用);缺點(diǎn):產(chǎn)生內(nèi)存碎片、并發(fā)開銷大;
- G1收集器(Garbage-First):JDK9+默認(rèn)GC,基于標(biāo)記-整理算法,將堆劃分為多個(gè)大小相等的Region,優(yōu)先回收垃圾多的Region,兼顧吞吐量和低延遲,支持大堆內(nèi)存(如數(shù)十GB);
- ZGC/Shenandoah收集器:新一代低延遲GC,暫停時(shí)間控制在毫秒級以下,支持TB級堆內(nèi)存。
3. 類加載機(jī)制與雙親委派模型?
答案:
類加載是將.class字節(jié)碼文件加載到JVM內(nèi)存,生成Class對象的過程,核心分為5個(gè)階段:
1. 類加載流程:
- 加載(Loading):通過類加載器讀取.class文件,生成二進(jìn)制字節(jié)流,在堆中創(chuàng)建Class對象;
- 驗(yàn)證(Verification):校驗(yàn)字節(jié)碼合法性(如文件格式、語法、語義、符號(hào)引用驗(yàn)證),防止惡意字節(jié)碼;
- 準(zhǔn)備(Preparation):為類靜態(tài)變量分配內(nèi)存并設(shè)置默認(rèn)值(如int默認(rèn)0,boolean默認(rèn)false),不包含實(shí)例變量;
- 解析(Resolution):將符號(hào)引用(如類名、方法名)轉(zhuǎn)換為直接引用(內(nèi)存地址);
- 初始化(Initialization):執(zhí)行類構(gòu)造器
<clinit>()方法(靜態(tài)變量賦值+靜態(tài)代碼塊執(zhí)行),初始化順序:父類→子類,靜態(tài)變量→靜態(tài)代碼塊。
2. 類加載器分類:
- 啟動(dòng)類加載器(Bootstrap ClassLoader):C++實(shí)現(xiàn),加載JDK核心類庫(如
rt.jar),無父加載器; - 擴(kuò)展類加載器(Extension ClassLoader):Java實(shí)現(xiàn),加載
jre/lib/ext目錄下的類庫; - 應(yīng)用程序類加載器(Application ClassLoader):Java實(shí)現(xiàn),加載應(yīng)用classpath下的類庫(默認(rèn)類加載器);
- 自定義類加載器(Custom ClassLoader):繼承
ClassLoader類,重寫findClass()方法,用于加載自定義路徑的類(如熱部署、加密類)。
3. 雙親委派模型:
核心規(guī)則:類加載器加載類時(shí),先委托父加載器加載,父加載器無法加載(找不到類)時(shí),才由自身加載;
流程:應(yīng)用程序類加載器→擴(kuò)展類加載器→啟動(dòng)類加載器(頂層),若啟動(dòng)類加載器無法加載,反向逐級嘗試加載;
作用:
- 避免類重復(fù)加載(如
java.lang.String僅由啟動(dòng)類加載器加載一次); - 保護(hù)核心類庫(防止自定義
java.lang.String類替換核心類)。
- 避免類重復(fù)加載(如
破壞雙親委派模型的場景:
- 熱部署(如OSGi框架):需要不同模塊加載同一類的不同版本;
- JNDI、SPI機(jī)制(如JDBC驅(qū)動(dòng)加載):核心類由啟動(dòng)類加載器加載,需加載應(yīng)用classpath下的驅(qū)動(dòng)類,通過線程上下文類加載器(Thread Context ClassLoader)實(shí)現(xiàn)。
五、Spring核心框架
1. Spring IoC的原理與實(shí)現(xiàn)?
答案:
IoC(Inversion of Control,控制反轉(zhuǎn))是Spring的核心思想,指將對象的創(chuàng)建、依賴注入(DI)的控制權(quán)從應(yīng)用程序轉(zhuǎn)移到Spring容器,實(shí)現(xiàn)解耦。
核心概念:
- IoC容器:Spring的核心組件(如
ApplicationContext、BeanFactory),負(fù)責(zé)管理Bean的生命周期(創(chuàng)建、初始化、銷毀)和依賴關(guān)系; - Bean:IoC容器管理的對象(如Service、Dao層對象);
- 依賴注入(DI):IoC的具體實(shí)現(xiàn),容器在創(chuàng)建Bean時(shí)自動(dòng)注入其依賴的其他Bean(無需手動(dòng)
new對象)。
依賴注入的三種方式:
- 構(gòu)造方法注入:通過Bean的構(gòu)造方法傳入依賴,推薦使用(強(qiáng)制依賴,避免空指針);
@Service public class UserService { private final UserDao userDao; // 構(gòu)造方法注入(@Autowired可省略,Spring 4.3+支持) public UserService(UserDao userDao) { this.userDao = userDao; } } - Setter方法注入:通過Setter方法注入依賴,適用于可選依賴;
@Service public class UserService { private UserDao userDao; @Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; } } - 字段注入:直接在字段上添加
@Autowired注解,代碼簡潔,但不推薦(無法通過構(gòu)造方法校驗(yàn)依賴,不利于單元測試);@Service public class UserService { @Autowired private UserDao userDao; }
IoC容器初始化流程:
- 加載配置文件(如XML、注解
@Configuration); - 解析配置,掃描Bean定義(如
@Component、@Service、@Repository),注冊到BeanDefinitionRegistry; - 實(shí)例化Bean(默認(rèn)單例,懶加載除外);
- 依賴注入(DI):通過BeanPostProcessor(后置處理器)自動(dòng)注入依賴;
- 初始化Bean:執(zhí)行
@PostConstruct注解方法、InitializingBean接口的afterPropertiesSet()方法、自定義init-method; - Bean就緒,供應(yīng)用程序調(diào)用;
- 容器關(guān)閉時(shí),銷毀Bean:執(zhí)行
@PreDestroy注解方法、DisposableBean接口的destroy()方法、自定義destroy-method。
2. Spring AOP的原理與應(yīng)用?
答案:
AOP(Aspect-Oriented Programming,面向切面編程)是Spring的核心特性,通過“橫切”機(jī)制,將日志、事務(wù)、權(quán)限等通用功能(切面)與業(yè)務(wù)邏輯解耦,實(shí)現(xiàn)代碼復(fù)用。
核心概念:
- 切面(Aspect):封裝通用功能的類(如日志切面、事務(wù)切面),包含通知和切入點(diǎn);
- 通知(Advice):切面的具體邏輯(如日志打印、事務(wù)提交),分為5種類型:
@Before:目標(biāo)方法執(zhí)行前執(zhí)行;@After:目標(biāo)方法執(zhí)行后執(zhí)行(無論是否異常);@AfterReturning:目標(biāo)方法正常返回后執(zhí)行;@AfterThrowing:目標(biāo)方法拋出異常后執(zhí)行;@Around:環(huán)繞目標(biāo)方法執(zhí)行(可控制目標(biāo)方法的執(zhí)行與否,如事務(wù)的開始和提交);
- 切入點(diǎn)(Pointcut):定義切面作用的目標(biāo)方法(如“所有Service層的方法”),通過表達(dá)式(如execution表達(dá)式)指定;
- 連接點(diǎn)(JoinPoint):目標(biāo)方法的執(zhí)行點(diǎn)(如方法調(diào)用、異常拋出),是切入點(diǎn)的具體實(shí)例;
- 織入(Weaving):將切面邏輯融入目標(biāo)方法的過程,Spring AOP默認(rèn)采用動(dòng)態(tài)代理織入。
實(shí)現(xiàn)原理:
Spring AOP基于動(dòng)態(tài)代理實(shí)現(xiàn),分為兩種代理方式:
- JDK動(dòng)態(tài)代理:
- 適用場景:目標(biāo)類實(shí)現(xiàn)接口;
- 原理:通過
java.lang.reflect.Proxy類動(dòng)態(tài)生成代理類,代理類實(shí)現(xiàn)目標(biāo)接口,并重寫目標(biāo)方法,在方法中織入切面邏輯; - 缺點(diǎn):僅支持接口代理,無法代理無接口的類。
- CGLIB動(dòng)態(tài)代理:
- 適用場景:目標(biāo)類未實(shí)現(xiàn)接口;
- 原理:通過CGLIB(Code Generation Library)字節(jié)碼生成框架,動(dòng)態(tài)生成目標(biāo)類的子類,重寫目標(biāo)方法,織入切面邏輯;
- 優(yōu)點(diǎn):支持任意類代理(無需接口),性能優(yōu)于JDK動(dòng)態(tài)代理(創(chuàng)建代理類開銷大,但執(zhí)行效率高)。
應(yīng)用場景:
- 日志記錄:記錄方法調(diào)用參數(shù)、返回值、執(zhí)行時(shí)間;
- 事務(wù)管理:控制事務(wù)的開始、提交、回滾(Spring聲明式事務(wù)基于AOP實(shí)現(xiàn));
- 權(quán)限校驗(yàn):方法執(zhí)行前校驗(yàn)用戶權(quán)限;
- 異常處理:統(tǒng)一捕獲目標(biāo)方法的異常并處理。
示例:日志切面
// 切面類
@Aspect
@Component
public class LogAspect {
// 切入點(diǎn):所有com.example.service包下的public方法
@Pointcut("execution(public * com.example.service..*(..))")
public void servicePointcut() {}
// 環(huán)繞通知
@Around("servicePointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 目標(biāo)方法執(zhí)行前:打印請求參數(shù)
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("方法" + methodName + "調(diào)用,參數(shù):" + Arrays.toString(args));
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 執(zhí)行目標(biāo)方法
// 目標(biāo)方法執(zhí)行后:打印返回值和執(zhí)行時(shí)間
long cost = System.currentTimeMillis() - start;
System.out.println("方法" + methodName + "返回值:" + result + ",執(zhí)行時(shí)間:" + cost + "ms");
return result;
}
}3. Spring事務(wù)管理的原理?
答案:
Spring事務(wù)管理核心是“聲明式事務(wù)”(基于AOP)和“編程式事務(wù)”(手動(dòng)編碼),其中聲明式事務(wù)是主流用法。
1. 事務(wù)的ACID特性:
- 原子性(Atomicity):事務(wù)是不可分割的最小單位,要么全部執(zhí)行,要么全部回滾;
- 一致性(Consistency):事務(wù)執(zhí)行前后,數(shù)據(jù)完整性保持一致(如轉(zhuǎn)賬前后總金額不變);
- 隔離性(Isolation):多個(gè)事務(wù)并發(fā)執(zhí)行時(shí),事務(wù)之間相互隔離,互不影響;
- 持久性(Durability):事務(wù)提交后,數(shù)據(jù)修改永久生效(寫入磁盤)。
2. 事務(wù)隔離級別(Spring支持):
DEFAULT:默認(rèn)隔離級別(依賴數(shù)據(jù)庫,如MySQL默認(rèn)REPEATABLE READ);READ_UNCOMMITTED:讀未提交,最低隔離級別,可能出現(xiàn)臟讀、不可重復(fù)讀、幻讀;READ_COMMITTED:讀已提交,避免臟讀,可能出現(xiàn)不可重復(fù)讀、幻讀(如Oracle默認(rèn));REPEATABLE_READ:可重復(fù)讀,避免臟讀、不可重復(fù)讀,可能出現(xiàn)幻讀(如MySQL默認(rèn));SERIALIZABLE:串行化,最高隔離級別,避免所有并發(fā)問題,性能最低。
3. 事務(wù)傳播行為(核心,解決事務(wù)嵌套問題):
Spring定義了7種傳播行為,常用的有:
REQUIRED(默認(rèn)):如果當(dāng)前存在事務(wù),加入事務(wù);如果沒有事務(wù),創(chuàng)建新事務(wù);REQUIRES_NEW:無論當(dāng)前是否存在事務(wù),都創(chuàng)建新事務(wù)(新事務(wù)與原事務(wù)相互獨(dú)立,原事務(wù)暫停);SUPPORTS:如果當(dāng)前存在事務(wù),加入事務(wù);如果沒有事務(wù),以非事務(wù)方式執(zhí)行;NOT_SUPPORTED:以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),暫停原事務(wù);NEVER:以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),拋出異常。
4. 聲明式事務(wù)實(shí)現(xiàn)(基于AOP):
- 配置方式:通過
@EnableTransactionManagement注解啟用事務(wù)管理(Spring Boot自動(dòng)啟用); - 核心注解:
@Transactional(標(biāo)注在類或方法上,類級別的注解對所有方法生效); - 原理:
@Transactional注解被解析為切面,切入點(diǎn)是標(biāo)注該注解的方法;- 通知邏輯:通過AOP動(dòng)態(tài)代理,在目標(biāo)方法執(zhí)行前開啟事務(wù),執(zhí)行后提交事務(wù),異常時(shí)回滾事務(wù);
- 事務(wù)管理器:Spring通過
PlatformTransactionManager接口適配不同數(shù)據(jù)庫(如DataSourceTransactionManager適配JDBC,HibernateTransactionManager適配Hibernate)。
5. 事務(wù)失效的常見場景:
- 方法非public修飾(
@Transactional僅對public方法生效); - 事務(wù)方法內(nèi)部調(diào)用(如A方法調(diào)用本類的B方法,B方法的
@Transactional失效,因?yàn)槲唇?jīng)過代理類); - 異常類型不匹配(默認(rèn)僅捕獲
RuntimeException和Error, checked異常需通過rollbackFor指定); - 手動(dòng)捕獲異常未拋出(如
try-catch異常但未throw,事務(wù)無法感知異常,不會(huì)回滾); - 傳播行為配置錯(cuò)誤(如
NOT_SUPPORTED、NEVER); - 數(shù)據(jù)源未配置事務(wù)管理器(
PlatformTransactionManager未被Spring管理)。
六、數(shù)據(jù)庫與MyBatis
1. JDBC的核心操作步驟?
答案:
JDBC(Java Database Connectivity)是Java訪問數(shù)據(jù)庫的標(biāo)準(zhǔn)API,核心步驟如下:
- 加載數(shù)據(jù)庫驅(qū)動(dòng)(JDK8+無需手動(dòng)加載,DriverManager自動(dòng)掃描);
- 建立數(shù)據(jù)庫連接(通過
DriverManager.getConnection(url, username, password)); - 創(chuàng)建Statement/PreparedStatement對象(執(zhí)行SQL語句);
- 執(zhí)行SQL(
executeQuery()查詢,executeUpdate()增刪改); - 處理結(jié)果集(查詢時(shí)通過ResultSet遍歷結(jié)果);
- 關(guān)閉資源(ResultSet、Statement、Connection,需在finally中關(guān)閉,避免資源泄漏)。
示例代碼:
public void queryUser() {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 1. 加載驅(qū)動(dòng)(MySQL 8.0+驅(qū)動(dòng)類:com.mysql.cj.jdbc.Driver)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立連接
String url = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
conn = DriverManager.getConnection(url, "root", "123456");
// 3. 創(chuàng)建PreparedStatement(預(yù)編譯SQL,防止SQL注入)
String sql = "SELECT id, name FROM user WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1); // 設(shè)置參數(shù)
// 4. 執(zhí)行查詢
rs = pstmt.executeQuery();
// 5. 處理結(jié)果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println("id: " + id + ", name: " + name);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 6. 關(guān)閉資源(逆序關(guān)閉)
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}關(guān)鍵考點(diǎn):
- PreparedStatement vs Statement:PreparedStatement支持預(yù)編譯SQL、參數(shù)化查詢,防止SQL注入,性能更優(yōu);Statement不支持參數(shù)化,存在SQL注入風(fēng)險(xiǎn);
- SQL注入:如
SELECT * FROM user WHERE name = '" + name + "'",若name傳入' OR '1'='1,則SQL變?yōu)?code>SELECT * FROM user WHERE name = '' OR '1'='1',查詢所有用戶;PreparedStatement通過參數(shù)綁定避免該問題。
2. MyBatis的核心組件與工作原理?
答案:
MyBatis是持久層框架,簡化JDBC操作,通過XML或注解配置SQL語句,無需手動(dòng)編寫JDBC代碼。
核心組件:
SqlSessionFactory:MyBatis核心工廠類,通過SqlSessionFactoryBuilder讀取配置文件(mybatis-config.xml)創(chuàng)建,線程安全;SqlSession:會(huì)話對象,封裝數(shù)據(jù)庫連接和事務(wù)管理,線程不安全(每次請求創(chuàng)建新實(shí)例);Mapper接口:自定義DAO接口,MyBatis通過動(dòng)態(tài)代理生成實(shí)現(xiàn)類,關(guān)聯(lián)XML/注解中的SQL;Mapper.xml:配置SQL語句、參數(shù)映射、結(jié)果集映射;Configuration:MyBatis全局配置對象,存儲(chǔ)核心配置(如數(shù)據(jù)源、事務(wù)管理器、Mapper注冊)。
工作原理:
- 加載配置文件:
SqlSessionFactoryBuilder讀取mybatis-config.xml和Mapper.xml,解析配置信息(數(shù)據(jù)源、SQL語句、映射規(guī)則); - 創(chuàng)建SqlSessionFactory:通過解析后的配置信息創(chuàng)建SqlSessionFactory(單例模式);
- 創(chuàng)建SqlSession:SqlSessionFactory調(diào)用
openSession()創(chuàng)建SqlSession,默認(rèn)不自動(dòng)提交事務(wù); - 獲取Mapper代理對象:SqlSession調(diào)用
getMapper(Mapper接口.class),通過動(dòng)態(tài)代理生成Mapper接口的實(shí)現(xiàn)類; - 執(zhí)行SQL:調(diào)用Mapper接口方法,MyBatis根據(jù)方法名匹配Mapper.xml中的SQL語句,執(zhí)行JDBC操作;
- 處理結(jié)果集:MyBatis自動(dòng)將ResultSet映射為Java對象(根據(jù)resultType/resultMap配置);
- 提交/回滾事務(wù):執(zhí)行完成后,SqlSession調(diào)用
commit()提交事務(wù)或rollback()回滾事務(wù); - 關(guān)閉SqlSession:釋放資源。
核心配置(mybatis-config.xml):
<configuration>
<!-- 環(huán)境配置(數(shù)據(jù)源、事務(wù)管理器) -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/> <!-- 事務(wù)管理類型:JDBC/MANAGED -->
<dataSource type="POOLED"> <!-- 數(shù)據(jù)源類型:POOLED(連接池)/UNPOOLED/JNDI -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 注冊Mapper.xml -->
<mappers>
<mapper resource="com/example/mapper/UserMapper.xml"/>
</mappers>
</configuration>Mapper.xml示例:
<mapper namespace="com.example.mapper.UserMapper">
<!-- 結(jié)果集映射(數(shù)據(jù)庫字段→Java對象屬性) -->
<resultMap id="UserResultMap" type="com.example.entity.User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
</resultMap>
<!-- 查詢用戶 -->
<select id="selectUserById" parameterType="int" resultMap="UserResultMap">
SELECT id, name, age FROM user WHERE id = #{id}
</select>
<!-- 新增用戶 -->
<insert id="insertUser" parameterType="com.example.entity.User">
INSERT INTO user (name, age) VALUES (#{name}, #{age})
</insert>
</mapper>3. MyBatis的一級緩存與二級緩存?
答案:
MyBatis提供緩存機(jī)制,減少數(shù)據(jù)庫查詢次數(shù),提升性能,分為一級緩存和二級緩存。
一級緩存(SqlSession級別,默認(rèn)開啟):
- 作用范圍:同一個(gè)SqlSession內(nèi),多次執(zhí)行相同的SQL查詢(參數(shù)相同),僅第一次查詢數(shù)據(jù)庫,后續(xù)從緩存獲取結(jié)果;
- 實(shí)現(xiàn)原理:SqlSession內(nèi)部維護(hù)一個(gè)HashMap,key為緩存鍵(由SQL語句、參數(shù)、RowBounds、環(huán)境等組成),value為查詢結(jié)果;
- 失效場景:
- 執(zhí)行insert/update/delete操作(會(huì)清空一級緩存);
- 調(diào)用
SqlSession.clearCache()手動(dòng)清空; - SqlSession關(guān)閉或提交事務(wù)。
二級緩存(Mapper級別,默認(rèn)關(guān)閉):
- 作用范圍:同一個(gè)Mapper接口(命名空間)下,多個(gè)SqlSession共享緩存;
- 啟用方式:
- 在mybatis-config.xml中開啟全局緩存(默認(rèn)開啟,可省略):
<setting name="cacheEnabled" value="true"/>; - 在Mapper.xml中添加
<cache/>標(biāo)簽(啟用當(dāng)前Mapper的二級緩存);
- 在mybatis-config.xml中開啟全局緩存(默認(rèn)開啟,可省略):
- 實(shí)現(xiàn)原理:每個(gè)Mapper接口對應(yīng)一個(gè)Cache對象,SqlSession查詢后將結(jié)果存入二級緩存,其他SqlSession查詢相同SQL時(shí),先從二級緩存獲??;
- 注意事項(xiàng):
- 緩存的對象必須實(shí)現(xiàn)
Serializable接口(二級緩存可能序列化存儲(chǔ)); - 執(zhí)行insert/update/delete操作會(huì)清空當(dāng)前Mapper的二級緩存;
- 可通過
useCache="false"(查詢語句)或flushCache="true"(增刪改語句)控制緩存行為。
- 緩存的對象必須實(shí)現(xiàn)
緩存查詢順序:二級緩存 → 一級緩存 → 數(shù)據(jù)庫。
七、設(shè)計(jì)模式與性能優(yōu)化
1. 單例模式的幾種實(shí)現(xiàn)方式與線程安全?
答案:
單例模式確保一個(gè)類僅有一個(gè)實(shí)例,并提供全局訪問點(diǎn),常用實(shí)現(xiàn)方式如下:
1. 餓漢式(線程安全,非懶加載):
public class Singleton {
// 類加載時(shí)初始化實(shí)例(餓漢式)
private static final Singleton instance = new Singleton();
// 私有構(gòu)造方法,禁止外部實(shí)例化
private Singleton() {}
// 提供全局訪問方法
public static Singleton getInstance() {
return instance;
}
}- 優(yōu)點(diǎn):簡單高效,類加載時(shí)初始化,天然線程安全;
- 缺點(diǎn):非懶加載,類加載時(shí)即創(chuàng)建實(shí)例,若實(shí)例未被使用,浪費(fèi)內(nèi)存。
2. 懶漢式(線程不安全→線程安全優(yōu)化):
- 基礎(chǔ)版(線程不安全,多線程下可能創(chuàng)建多個(gè)實(shí)例):
public class Singleton { private static Singleton instance; private Singleton() {} // 線程不安全:多線程同時(shí)進(jìn)入if條件,創(chuàng)建多個(gè)實(shí)例 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } - 優(yōu)化版(雙重校驗(yàn)鎖DCL,線程安全,懶加載):
public class Singleton { // volatile禁止指令重排 private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 第一次校驗(yàn)(無鎖,提高效率) synchronized (Singleton.class) { // 加鎖 if (instance == null) { // 第二次校驗(yàn)(防止多線程并發(fā)創(chuàng)建) instance = new Singleton(); } } } return instance; } } - 優(yōu)點(diǎn):懶加載(使用時(shí)才創(chuàng)建實(shí)例),線程安全,性能優(yōu);
- 關(guān)鍵:volatile關(guān)鍵字必須加,防止
instance = new Singleton()指令重排(分配內(nèi)存→初始化→賦值),導(dǎo)致其他線程獲取到未初始化的實(shí)例。
3. 靜態(tài)內(nèi)部類式(線程安全,懶加載,推薦):
public class Singleton {
private Singleton() {}
// 靜態(tài)內(nèi)部類,類加載時(shí)不初始化
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
// 調(diào)用getInstance()時(shí),才加載SingletonHolder,初始化實(shí)例
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}- 優(yōu)點(diǎn):懶加載(靜態(tài)內(nèi)部類按需加載),線程安全(類加載機(jī)制保證),代碼簡潔,無鎖開銷;
- 原理:靜態(tài)內(nèi)部類的加載時(shí)機(jī)是在第一次被引用時(shí),類加載過程是線程安全的,確保實(shí)例僅創(chuàng)建一次。
4. 枚舉式(線程安全,防反射/序列化,最佳實(shí)踐):
public enum Singleton {
INSTANCE;
// 枚舉類的方法
public void doSomething() {
System.out.println("Singleton enum");
}
}- 優(yōu)點(diǎn):
- 線程安全:枚舉類的實(shí)例在類加載時(shí)創(chuàng)建,天然線程安全;
- 防反射:枚舉類的構(gòu)造方法被編譯器私有化,無法通過反射創(chuàng)建實(shí)例;
- 防序列化:枚舉類默認(rèn)實(shí)現(xiàn)Serializable,序列化時(shí)不會(huì)創(chuàng)建新實(shí)例;
- 缺點(diǎn):非懶加載,類加載時(shí)即創(chuàng)建實(shí)例。
2. Java性能優(yōu)化的常見手段?
答案:
Java性能優(yōu)化需從“代碼層面、JVM層面、數(shù)據(jù)庫層面、架構(gòu)層面”多維度入手,核心目標(biāo)是提升響應(yīng)速度、降低資源消耗。
1. 代碼層面優(yōu)化:
- 集合使用優(yōu)化:
- ArrayList初始化時(shí)指定初始容量(避免頻繁擴(kuò)容);
- 頻繁增刪用LinkedList,頻繁查詢用ArrayList;
- 避免在循環(huán)中使用
ArrayList.add(index, element)(O(n)復(fù)雜度);
- 字符串優(yōu)化:
- 頻繁拼接用StringBuilder(單線程)/StringBuffer(多線程),避免String拼接(創(chuàng)建大量臨時(shí)對象);
- 常量字符串用
String.intern()復(fù)用常量池對象;
- 循環(huán)優(yōu)化:
- 減少循環(huán)內(nèi)的對象創(chuàng)建(如
for (int i = 0; i < list.size(); i++)→ 先獲取size:int size = list.size(); for (int i = 0; i < size; i++)); - 避免循環(huán)內(nèi)的復(fù)雜計(jì)算(如方法調(diào)用、表達(dá)式計(jì)算);
- 減少循環(huán)內(nèi)的對象創(chuàng)建(如
- 避免空指針:使用
Objects.requireNonNull()、Optional類,減少null判斷; - 資源管理:使用try-with-resources自動(dòng)關(guān)閉流、數(shù)據(jù)庫連接,避免資源泄漏。
2. JVM層面優(yōu)化:
- 堆內(nèi)存配置:合理設(shè)置
-Xms(初始堆)和-Xmx(最大堆),建議兩者相等(避免頻繁擴(kuò)容),堆大小一般為物理內(nèi)存的1/2~1/3; - 年輕代優(yōu)化:調(diào)整
-XX:NewRatio(年輕代與老年代比例,默認(rèn)2:1)、-XX:SurvivorRatio(Eden與S0/S1比例,默認(rèn)8:1),根據(jù)應(yīng)用對象存活時(shí)間調(diào)整; - GC收集器選擇:
- 高吞吐量場景:使用Parallel Scavenge收集器;
- 低延遲場景:使用G1/ZGC收集器;
- 逃逸分析:啟用
-XX:+DoEscapeAnalysis(默認(rèn)啟用),JVM自動(dòng)分析對象是否逃逸,未逃逸的對象可分配在棧上(減少GC壓力)。
3. 數(shù)據(jù)庫層面優(yōu)化:
- 索引優(yōu)化:給查詢頻繁的字段建立索引(如WHERE、JOIN、ORDER BY字段),避免過度索引(影響增刪效率);
- SQL優(yōu)化:
- 避免SELECT *,只查詢需要的字段;
- 避免WHERE子句中使用函數(shù)(如
WHERE DATE(create_time) = '2025-01-01',導(dǎo)致索引失效); - 避免JOIN過多表(建議不超過3張表),大表JOIN用分頁;
- 批量操作替代循環(huán)單條操作(如MyBatis的
batchInsert);
- 連接池優(yōu)化:合理設(shè)置數(shù)據(jù)庫連接池大小(如HikariCP的
maximum-pool-size,建議為CPU核心數(shù)×2+1),避免連接泄漏。
4. 架構(gòu)層面優(yōu)化:
- 緩存引入:使用Redis、Ehcache等緩存熱點(diǎn)數(shù)據(jù)(如用戶信息、配置數(shù)據(jù)),減少數(shù)據(jù)庫查詢;
- 異步處理:將耗時(shí)操作(如日志打印、郵件發(fā)送)異步化(使用線程池、消息隊(duì)列),提升響應(yīng)速度;
- 負(fù)載均衡:通過Nginx、LVS等實(shí)現(xiàn)多服務(wù)器負(fù)載均衡,分散請求壓力;
- 分布式部署:將應(yīng)用拆分為微服務(wù),按業(yè)務(wù)模塊部署,提高并發(fā)處理能力。
八、總結(jié)
本文覆蓋Java面試核心考點(diǎn),從基礎(chǔ)語法、集合框架、多線程、JVM、Spring、數(shù)據(jù)庫到設(shè)計(jì)模式與性能優(yōu)化,每個(gè)模塊均包含高頻面試題、詳細(xì)答案及核心考點(diǎn)解析,兼顧理論深度與實(shí)戰(zhàn)應(yīng)用。
面試時(shí),除了記憶答案,更要理解底層原理(如HashMap的哈希沖突解決、Spring AOP的動(dòng)態(tài)代理、JVM的GC機(jī)制),并結(jié)合項(xiàng)目經(jīng)驗(yàn)說明實(shí)際應(yīng)用場景(如線程池在項(xiàng)目中的配置、事務(wù)失效的排查過程)。建議重點(diǎn)掌握多線程并發(fā)、JVM、Spring核心原理等高級考點(diǎn),這些是區(qū)分初級與中高級開發(fā)者的關(guān)鍵。
到此這篇關(guān)于Java常見面試題以及答案匯總大全的文章就介紹到這了,更多相關(guān)Java常見面試題及答案內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
到此這篇關(guān)于Java常見面試題以及答案匯總大全的文章就介紹到這了,更多相關(guān)Java常見面試題及答案內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java應(yīng)用程序如何自定義log4j配置文件的位置
這篇文章主要介紹了java應(yīng)用程序如何自定義log4j配置文件的位置,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
java線程池prestartCoreThread prestartAllCoreThreads的預(yù)熱源碼解讀
這篇文章主要介紹了java線程池prestartCoreThread prestartAllCoreThreads的預(yù)熱源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
JAVA驗(yàn)證身份證號(hào)碼有效性的實(shí)例代碼
很多項(xiàng)目業(yè)務(wù)都會(huì)設(shè)計(jì)到人員信息,那么身份證號(hào)就是必不可少的校驗(yàn)項(xiàng),下面這篇文章主要給大家介紹了關(guān)于JAVA驗(yàn)證身份證號(hào)碼有效性的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
Java在讀取文件內(nèi)容的時(shí)候,如何判斷出空白行的操作
這篇文章主要介紹了Java在讀取文件內(nèi)容的時(shí)候,如何判斷出空白行的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
自定義application.yml配置項(xiàng)方式
這篇文章主要介紹了自定義application.yml配置項(xiàng)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
使用@TableField(updateStrategy=FieldStrategy.IGNORED)遇到的坑記錄
這篇文章主要介紹了使用@TableField(updateStrategy=FieldStrategy.IGNORED)遇到的坑及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
使用spring aop 統(tǒng)一捕獲異常和寫日志的示例demo
本文通過一個(gè)小demo給大家介紹spring AOP 實(shí)現(xiàn)的異常捕獲和日志的方法技巧,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-08-08

