一、Java 語言核心特性(面向對象編程)
核心知識點梳理:
面向對象三大特性:
- 封裝:隱藏對象內部實現,通過 public 方法暴露接口(例:類的 private 字段 + get/set 方法)。
- 繼承:子類繼承父類非 private 屬性和方法,減少代碼冗余(注意:Java 單繼承,通過接口實現多繼承效果)。
- 多態:同一行為的不同表現形式(編譯時多態:方法重載;運行時多態:子類重寫父類方法,父類引用指向子類對象)。
接口與抽象類的區別:
- 接口:全抽象(JDK8 后可有 default 方法),字段默認 public static final,類可實現多個接口。
- 抽象類:可含抽象方法和具體方法,字段可非靜態,類只能繼承一個抽象類。
- 應用場景:接口用于定義規范(如 DAO 層接口),抽象類用于抽取共性實現(如 BaseService)。
其他高頻點:
- 重載(Overload)vs 重寫(Override):重載是同一類中方法名相同、參數不同;重寫是子類覆蓋父類方法,參數和返回值需一致(協變返回類型除外)。
- 關鍵字:
static
(靜態變量 / 方法屬于類,非實例)、final
(修飾類不可繼承、方法不可重寫、變量不可修改)、this
(當前實例)、super
(父類引用)。
面試常見問題及回答思路:
Q:多態的實現原理是什么?
A:核心是 “動態綁定”:編譯時編譯器根據父類引用類型檢查方法是否存在,運行時 JVM 根據實際對象類型調用對應子類的重寫方法(依賴方法表:每個類的 Class 對象中維護方法表,記錄方法的實際入口地址)。
舉例:List list = new ArrayList(); list.add(1);
?編譯時用 List 接口檢查 add 方法,運行時實際調用 ArrayList 的 add 實現。Q:為什么 Java 不支持多繼承?
A:避免 “菱形繼承” 問題(多個父類有相同方法時,子類無法確定繼承哪一個),通過接口多實現替代,接口僅定義規范,無具體實現沖突。
二、集合框架
核心知識點梳理:
整體結構:
- 接口:
Collection
(List/Set)、Map
; - 核心實現類:
- List:
ArrayList
(數組,查快改慢)、LinkedList
(雙向鏈表,增刪快查慢); - Set:
HashSet
(底層 HashMap,無序去重)、TreeSet
(紅黑樹,有序去重,需實現 Comparable); - Map:
HashMap
(數組 + 鏈表 / 紅黑樹,JDK1.8 后鏈表長度 > 8 轉紅黑樹)、ConcurrentHashMap
(線程安全,JDK1.7 分段鎖,JDK1.8 CAS+synchronized)。
- List:
- 接口:
高頻細節:
HashMap
:初始容量 16,負載因子 0.75,擴容為 2 倍;key 為 null 存放在索引 0;線程不安全(多線程 put 可能導致死循環,JDK1.8 后修復但仍有數據覆蓋問題)。HashSet
:本質是HashMap
的 key(value 為常量),所以去重依賴 key 的hashCode()
和equals()
(先比 hashCode,再比 equals)。- 線程安全集合:
Vector
(所有方法 synchronized,效率低)、Collections.synchronizedList()
(包裝類,加鎖粒度同 Vector)、CopyOnWriteArrayList
(寫時復制,讀不加鎖,適合讀多寫少)。
面試常見問題及回答思路:
Q:ArrayList 和 LinkedList 的區別?如何選型?
A:從底層結構、操作效率、內存占用三方面說:- 底層:ArrayList 是動態數組,LinkedList 是雙向鏈表;
- 效率:ArrayList 隨機訪問(get (index))快(O (1)),增刪(尤其是中間位置)慢(需移動元素,O (n));LinkedList 增刪快(O (1),找到位置后修改指針),隨機訪問慢(O (n));
- 選型:查多改少用 ArrayList,增刪多(尤其是中間)用 LinkedList;小數據量時差異不大。
Q:HashMap 的 put 流程?JDK1.7 和 1.8 有什么區別?
A:put 流程:- 計算 key 的 hash 值(hashCode () 高 16 位異或低 16 位,減少哈希沖突);
- 計算索引(hash & (容量 - 1));
- 若桶位為空,直接放新節點;若不為空,判斷 key 是否存在(hash+equals),存在則替換 value;不存在則插入鏈表 / 紅黑樹;
- 插入后若鏈表長度 > 8 且容量 >=64,轉紅黑樹;
- 若元素數 > 容量 * 負載因子,觸發擴容(2 倍)。
區別:1.7 是頭插法(擴容時可能死循環),1.8 是尾插法;1.8 新增紅黑樹優化長鏈表查詢;1.7 的 hash 計算更簡單(1.8 增加高 16 位異或)。
三、多線程編程基礎
核心知識點梳理:
線程創建方式:
- 繼承
Thread
類(重寫 run ()); - 實現
Runnable
接口(重寫 run (),可多線程共享資源); - 實現
Callable
接口(重寫 call (),有返回值,結合Future
獲取結果)。
- 繼承
線程狀態:
新建(New)→ 就緒(Runnable)→ 運行(Running)→ 阻塞(Blocked/Waiting/Timed Waiting)→ 終止(Terminated)。- 阻塞原因:synchronized 未拿到鎖(Blocked)、wait ()(Waiting)、sleep (long)/join (long)(Timed Waiting)。
同步機制:
synchronized
:可修飾方法(鎖當前對象 / 類)、代碼塊(鎖括號內對象);JDK1.6 后優化為偏向鎖→輕量級鎖→重量級鎖(鎖升級),減少性能消耗。Lock
接口:ReentrantLock
(可重入、可中斷、可超時、公平鎖 / 非公平鎖),需手動lock()
和unlock()
(建議放 finally)。- 區別:synchronized 是 JVM 層面隱式鎖,自動釋放;Lock 是 API 層面顯式鎖,靈活但需手動釋放。
線程池:
- 核心參數:
corePoolSize
(核心線程數)、maximumPoolSize
(最大線程數)、keepAliveTime
(非核心線程空閑存活時間)、workQueue
(任務隊列)。 - 常用線程池:
Executors.newFixedThreadPool()
(固定線程數)、newCachedThreadPool()
(緩存線程池,可擴容)、newSingleThreadExecutor()
(單線程);實際開發建議用ThreadPoolExecutor
手動創建,避免資源耗盡(如 FixedThreadPool 的隊列無界可能 OOM)。
- 核心參數:
面試常見問題及回答思路:
Q:synchronized 和 volatile 的區別?
A:兩者都用于線程安全,但作用不同:- volatile:修飾變量,保證可見性(一個線程修改后,其他線程立即看到)和禁止指令重排序(如單例模式雙重檢查鎖中修飾 instance),但不保證原子性(例:i++ 仍會有線程安全問題);
- synchronized:保證原子性(臨界區代碼互斥執行)、可見性(解鎖前刷新內存)、有序性(臨界區代碼單線程執行),但開銷比 volatile 大。
總結:volatile 適合修飾狀態標志(如 boolean isRunning),synchronized 適合復雜邏輯的同步。
Q:什么是死鎖?如何避免?
A:死鎖是兩個或多個線程互相持有對方需要的鎖,導致無限等待。
例:線程 1 持有鎖 A,等待鎖 B;線程 2 持有鎖 B,等待鎖 A。
避免:1. 按固定順序獲取鎖(如都先獲取 A 再獲取 B);2. 定時釋放鎖(tryLock (time));3. 減少鎖的持有時間。
四、JVM 內存結構和垃圾回收機制
核心知識點梳理:
內存結構(JDK8+):
- 線程私有:
- 程序計數器:記錄當前線程執行的字節碼行號,唯一不會 OOM 的區域;
- 虛擬機棧:存儲棧幀(局部變量表、操作數棧、返回地址等),棧深過深會 StackOverflowError(如遞歸無出口),內存不足會 OOM;
- 本地方法棧:類似虛擬機棧,為 Native 方法服務。
- 線程共享:
- 堆:存儲對象實例,GC 主要區域,分新生代(Eden+From Survivor+To Survivor)和老年代;內存不足會 OOM;
- 方法區(元空間,MetaSpace):存儲類信息、常量、靜態變量等,JDK8 前是永久代(PermGen),元空間使用本地內存,默認無上限(可通過 - XX:MaxMetaspaceSize 限制),內存不足會 OOM。
- 線程私有:
垃圾回收(GC):
- 回收區域:堆和方法區(常量、無用類)。
- 對象存活判斷:可達性分析(以 GC Roots 為起點,不可達的對象標記為可回收);GC Roots 包括:虛擬機棧中引用的對象、本地方法棧中引用的對象、靜態變量、常量等。
- 引用類型:強引用(new 的對象,不會被回收)、軟引用(OOM 前回收,適合緩存)、弱引用(GC 時必回收,如 WeakHashMap)、虛引用(回收時通知,跟蹤 GC)。
- 垃圾收集算法:
- 標記 - 清除:標記可回收對象,直接清除,會產生碎片;
- 標記 - 復制:將存活對象復制到另一塊區域,無碎片,適合新生代(Eden:From:To=8:1:1);
- 標記 - 整理:標記后將存活對象移到一端,清除另一端,適合老年代。
- 垃圾收集器:
- 新生代:SerialGC(單線程,STW 長)、Parallel Scavenge(多線程,注重吞吐量)、ParNew(多線程,配合 CMS);
- 老年代:Serial Old(單線程,配合 SerialGC)、Parallel Old(多線程,配合 Parallel Scavenge)、CMS(并發標記清除,低延遲,三步 STW:初始標記、重新標記、并發清除,有碎片)、G1(Region 分區,兼顧吞吐量和延遲,Mixed GC 回收部分老年代)。
面試常見問題及回答思路:
Q:JVM 堆內存分代?為什么這么劃分?
A:堆分新生代(Young)和老年代(Old),新生代又分 Eden、From Survivor、To Survivor。
原因:基于 “大部分對象朝生夕死” 的經驗法則(新生代對象存活時間短,回收頻繁;老年代對象存活時間長,回收少),分代后可采用不同 GC 算法優化效率:新生代用標記 - 復制(效率高,適合短生命周期對象),老年代用標記 - 整理(避免碎片,適合長生命周期對象)。Q:G1 收集器的工作原理?
A:G1 將堆劃分為多個大小相等的 Region(默認 1-32MB),每個 Region 可動態標記為 Eden、Survivor、Old。
流程:1. 初始標記(STW,標記 GC Roots 直接關聯的對象);2. 并發標記(遍歷對象引用鏈,無 STW);3. 最終標記(STW,處理并發標記遺漏的對象);4. 篩選回收(STW,根據 Region 的回收價值排序,優先回收價值高的,混合回收部分老年代 Region)。
優勢:可設置最大 STW 時間,兼顧吞吐量和延遲,適合大堆(如 8GB 以上)。
五、復習建議
- 畫思維導圖:把每個模塊的知識點串聯起來(如集合框架→線程安全集合→多線程同步),形成知識網。
- 看源碼:重點看
ArrayList
的add()
、HashMap
的put()
、Thread
的start()
等核心方法,理解底層實現。 - 刷面試題:針對每個知識點,找 5-10 道高頻題練習(如牛客網、力扣 Java 面試專題),確保能清晰表達。
- 模擬面試:找同學互相提問,訓練臨場反應,避免緊張忘詞。
核心是 “理解原理 + 能舉例子”,面試官更看重你是否真的懂,而不是背概念。比如講 HashMap 時,能說出自己項目中遇到的哈希沖突問題及解決方法,會比單純背流程更加分。
List list = new ArrayList(); 這個是小類型轉換成大類型是自動轉換是嗎,如果反過來呢
在 Java 中,List list = new ArrayList();
?涉及的是多態中的向上轉型,而非基本數據類型的自動轉換(小轉大),但邏輯上有相似之處:
ArrayList
?是?List
?接口的實現類,List
?是父類型(接口可視為 “抽象父類”),ArrayList
?是子類型。- 這種 “父類引用指向子類對象” 的寫法是自動向上轉型,無需顯式聲明,編譯器允許這種轉換(因為子類一定符合父類的接口規范)。
如果反過來寫?ArrayList arrayList = new List();
,則完全不允許,原因是:
List
?是接口,不能直接實例化(new List()
?本身語法錯誤);- 即使是類之間的轉換(如?
ArrayList
?與?LinkedList
,同屬?List
?子類型),父類型也不能直接轉換為子類型,因為父類型引用的對象可能根本不是目標子類型的實例。
若要將父類型引用轉回子類型,必須滿足兩個條件:
- 父類型引用的實際對象是目標子類型的實例;
- 必須通過顯式強制轉型聲明。
例如:
List list = new ArrayList(); // 向上轉型(自動)
ArrayList arrayList = (ArrayList) list; // 向下轉型(顯式強制,合法,因為list實際指向ArrayList)
但如果實際對象類型不匹配,強制轉型會拋出?ClassCastException
:
List list = new LinkedList();
ArrayList arrayList = (ArrayList) list; // 運行時異常:LinkedList不能轉為ArrayList