最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java常見面試題以及答案匯總大全(2025最新版)

 更新時(shí)間:2026年04月14日 08:40:23   作者:獨(dú)角鯨網(wǎng)絡(luò)安全實(shí)驗(yàn)室  
這篇文章主要介紹了Java常見面試題及答案匯總的相關(guān)資料,適合準(zhǔn)備求職、或者有跳槽想法的初/中/高級程序員,也合適想要對自己技術(shù)棧查缺補(bǔ)漏、不斷完善成長的人,需要的朋友可以參考下

一、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è)核心子類:
    1. Error:嚴(yán)重錯(cuò)誤(如OutOfMemoryError、StackOverflowError),程序無法恢復(fù),無需捕獲;
    2. 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ǔ)流程:
    1. 計(jì)算key的hashCode() → 經(jīng)過哈希擾動(dòng)(hashCode() ^ (hashCode() >>> 16))得到哈希值;
    2. 哈希值 & 數(shù)組長度-1 → 定位數(shù)組索引(保證索引在數(shù)組范圍內(nèi));
    3. 若索引位置無元素,直接存儲(chǔ);若有元素(哈希沖突),采用“頭插法”插入鏈表。

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ù)。
    解決方案:使用ConcurrentHashMapCollections.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)換路徑
NEWRUNNABLE(start()) → TERMINATED(執(zhí)行完成);
RUNNABLEBLOCKED(競爭鎖失?。?→ RUNNABLE(獲取鎖);
RUNNABLEWAITING/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è):

  1. 保證可見性:一個(gè)線程修改volatile變量后,其他線程能立即看到最新值(禁止CPU緩存,變量讀寫直接操作主內(nèi)存);
  2. 禁止指令重排序:編譯器和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ù))
)

工作原理:

  1. 提交任務(wù)時(shí),若核心線程數(shù)未滿,創(chuàng)建核心線程執(zhí)行任務(wù);
  2. 核心線程滿時(shí),任務(wù)加入阻塞隊(duì)列;
  3. 隊(duì)列滿時(shí),若未達(dá)到最大線程數(shù),創(chuàng)建臨時(shí)線程執(zhí)行任務(wù);
  4. 臨時(shí)線程空閑時(shí)間超過keepAliveTime,銷毀臨時(shí)線程;
  5. 隊(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)存泄漏;
  • 解決方案:
    1. 用完ThreadLocal后調(diào)用remove()方法刪除value;
    2. 避免使用靜態(tài)ThreadLocal(生命周期長,易導(dǎo)致內(nèi)存泄漏);
    3. 線程池場景下,確保任務(wù)執(zhí)行完成后清理ThreadLocal變量。

應(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):

    1. 程序計(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ū)域)。
    1. 虛擬機(jī)棧(VM Stack):
    • 作用:存儲(chǔ)線程執(zhí)行方法時(shí)的棧幀(包含局部變量表、操作數(shù)棧、方法出口等);
    • 特點(diǎn):線程私有,棧幀入棧(方法調(diào)用)和出棧(方法返回)對應(yīng)方法執(zhí)行生命周期;
    • 異常:棧深度超過JVM限制→StackOverflowError(如遞歸調(diào)用無終止);棧擴(kuò)展時(shí)內(nèi)存不足→OutOfMemoryError。
    1. 本地方法棧(Native Method Stack):
    • 作用:與虛擬機(jī)棧類似,僅支持Native方法(如Thread.start0())的執(zhí)行;
    • 特點(diǎn):線程私有,可能拋出StackOverflowErrorOutOfMemoryError。
    1. 堆(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
    1. 方法區(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)類加載器無法加載,反向逐級嘗試加載;

  • 作用:

    1. 避免類重復(fù)加載(如java.lang.String僅由啟動(dòng)類加載器加載一次);
    2. 保護(hù)核心類庫(防止自定義java.lang.String類替換核心類)。
  • 破壞雙親委派模型的場景:

    • 熱部署(如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容器初始化流程:

  1. 加載配置文件(如XML、注解@Configuration);
  2. 解析配置,掃描Bean定義(如@Component、@Service、@Repository),注冊到BeanDefinitionRegistry;
  3. 實(shí)例化Bean(默認(rèn)單例,懶加載除外);
  4. 依賴注入(DI):通過BeanPostProcessor(后置處理器)自動(dòng)注入依賴;
  5. 初始化Bean:執(zhí)行@PostConstruct注解方法、InitializingBean接口的afterPropertiesSet()方法、自定義init-method;
  6. Bean就緒,供應(yīng)用程序調(diào)用;
  7. 容器關(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),分為兩種代理方式:

  1. 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):僅支持接口代理,無法代理無接口的類。
  1. 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)注在類或方法上,類級別的注解對所有方法生效);
  • 原理:
    1. @Transactional注解被解析為切面,切入點(diǎn)是標(biāo)注該注解的方法;
    2. 通知邏輯:通過AOP動(dòng)態(tài)代理,在目標(biāo)方法執(zhí)行前開啟事務(wù),執(zhí)行后提交事務(wù),異常時(shí)回滾事務(wù);
    3. 事務(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)僅捕獲RuntimeExceptionError, 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,核心步驟如下:

  1. 加載數(shù)據(jù)庫驅(qū)動(dòng)(JDK8+無需手動(dòng)加載,DriverManager自動(dòng)掃描);
  2. 建立數(shù)據(jù)庫連接(通過DriverManager.getConnection(url, username, password));
  3. 創(chuàng)建Statement/PreparedStatement對象(執(zhí)行SQL語句);
  4. 執(zhí)行SQL(executeQuery()查詢,executeUpdate()增刪改);
  5. 處理結(jié)果集(查詢時(shí)通過ResultSet遍歷結(jié)果);
  6. 關(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注冊)。

工作原理:

  1. 加載配置文件:SqlSessionFactoryBuilder讀取mybatis-config.xml和Mapper.xml,解析配置信息(數(shù)據(jù)源、SQL語句、映射規(guī)則);
  2. 創(chuàng)建SqlSessionFactory:通過解析后的配置信息創(chuàng)建SqlSessionFactory(單例模式);
  3. 創(chuàng)建SqlSession:SqlSessionFactory調(diào)用openSession()創(chuàng)建SqlSession,默認(rèn)不自動(dòng)提交事務(wù);
  4. 獲取Mapper代理對象:SqlSession調(diào)用getMapper(Mapper接口.class),通過動(dòng)態(tài)代理生成Mapper接口的實(shí)現(xiàn)類;
  5. 執(zhí)行SQL:調(diào)用Mapper接口方法,MyBatis根據(jù)方法名匹配Mapper.xml中的SQL語句,執(zhí)行JDBC操作;
  6. 處理結(jié)果集:MyBatis自動(dòng)將ResultSet映射為Java對象(根據(jù)resultType/resultMap配置);
  7. 提交/回滾事務(wù):執(zhí)行完成后,SqlSession調(diào)用commit()提交事務(wù)或rollback()回滾事務(wù);
  8. 關(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共享緩存;
  • 啟用方式:
    1. 在mybatis-config.xml中開啟全局緩存(默認(rèn)開啟,可省略):<setting name="cacheEnabled" value="true"/>
    2. 在Mapper.xml中添加<cache/>標(biāo)簽(啟用當(dāng)前Mapper的二級緩存);
  • 實(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ù)據(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):
    1. 線程安全:枚舉類的實(shí)例在類加載時(shí)創(chuàng)建,天然線程安全;
    2. 防反射:枚舉類的構(gòu)造方法被編譯器私有化,無法通過反射創(chuàng)建實(shí)例;
    3. 防序列化:枚舉類默認(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ì)算);
  • 避免空指針:使用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配置文件的位置

    這篇文章主要介紹了java應(yīng)用程序如何自定義log4j配置文件的位置,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • java線程池prestartCoreThread prestartAllCoreThreads的預(yù)熱源碼解讀

    java線程池prestartCoreThread prestartAllCoreThreads的預(yù)熱源碼解讀

    這篇文章主要介紹了java線程池prestartCoreThread prestartAllCoreThreads的預(yù)熱源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • JAVA驗(yàn)證身份證號(hào)碼有效性的實(shí)例代碼

    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í)候,如何判斷出空白行的操作

    這篇文章主要介紹了Java在讀取文件內(nèi)容的時(shí)候,如何判斷出空白行的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • 淺析SpringBoot中過濾器配置與應(yīng)用場景

    淺析SpringBoot中過濾器配置與應(yīng)用場景

    過濾器是 Web 三大組件之一,也是項(xiàng)目中常用的工具,這篇文章主要介紹一下過濾器的概念及在 Spring Boot 中的常用使用方法,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-05-05
  • IDEA maven上傳速度很慢的解決辦法

    IDEA maven上傳速度很慢的解決辦法

    maven上傳的速度很慢,排除網(wǎng)絡(luò)原因,需要檢查配置,本文主要介紹了IDEA maven上傳速度很慢的解決辦法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-08-08
  • 自定義application.yml配置項(xiàng)方式

    自定義application.yml配置項(xiàng)方式

    這篇文章主要介紹了自定義application.yml配置項(xiàng)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • 使用@TableField(updateStrategy=FieldStrategy.IGNORED)遇到的坑記錄

    使用@TableField(updateStrategy=FieldStrategy.IGNORED)遇到的坑記錄

    這篇文章主要介紹了使用@TableField(updateStrategy=FieldStrategy.IGNORED)遇到的坑及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • 深入了解Java核心類庫--Math類

    深入了解Java核心類庫--Math類

    本文是小編最新給大家整理的關(guān)于Java中Math類常用方法的知識(shí),通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧,
    2021-07-07
  • 使用spring aop 統(tǒng)一捕獲異常和寫日志的示例demo

    使用spring aop 統(tǒng)一捕獲異常和寫日志的示例demo

    本文通過一個(gè)小demo給大家介紹spring AOP 實(shí)現(xiàn)的異常捕獲和日志的方法技巧,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2021-08-08

最新評論

社旗县| 三都| 淳安县| 社旗县| 蒙城县| 高碑店市| 阿图什市| 乐清市| 兴宁市| 平邑县| 板桥市| 三门峡市| 绿春县| 陇南市| 镇江市| 新民市| 和林格尔县| 合肥市| 宿松县| 望奎县| 富阳市| 东乌珠穆沁旗| 通海县| 广饶县| 台中县| 柳州市| 天柱县| 德化县| 西畴县| 固安县| 西充县| 南皮县| 琼结县| 若羌县| 永福县| 沁源县| 成武县| 盐池县| 门头沟区| 莱州市| 资兴市|