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

