一、Java 基础语法 & 关键字

1. JDK、JRE 和 JVM 的区别

  • JVM(Java Virtual Machine):Java 虚拟机,负责执行字节码(.class)。提供类加载、字节码校验、内存管理(堆、方法区)、执行引擎、垃圾回收、线程调度等。是抽象的规范,具体由不同厂商实现(HotSpot、OpenJ9 等)。
  • JRE(Java Runtime Environment):运行环境,包含 JVM、核心类库(rt.jar 或 modules)、以及运行时需要的其它资源。用于运行 Java 程序。
  • JDK(Java Development Kit):开发工具包,包含 JRE + 开发工具(javacjarjavadoc、调试工具等)。用于开发 Java 程序。
    总结:JDK ⊃ JRE ⊃ JVM(JDK 包含 JRE,JRE 包含 JVM)。

2. Java 的跨平台原理是什么?

  • Java 程序先由 javac 编译成与平台无关的 字节码.class)。字节码不是机器码,而是供 JVM 执行的指令集。
  • 每个平台(Windows/Linux/macOS)有相应的 JVM 实现,JVM 把字节码翻译成该平台的机器指令(通过解释器、JIT 编译器等)。因此同一套 .class 可以在不同平台的 JVM 上运行 —— “一次编写,处处运行”(Write Once, Run Anywhere)。
  • 其它保证跨平台的要点:标准库隐藏平台差异(I/O、线程等),需避免使用与平台绑定的本地代码(JNI)。

3. Java 的基本数据类型有哪些?各自的默认值?

8 种基本类型及默认值(成员变量/数组元素;局部变量无默认值必须初始化):

  • byte:1 byte,默认值 0
  • short:2 bytes,默认值 0
  • int:4 bytes,默认值 0
  • long:8 bytes,默认值 0L
  • float:4 bytes,默认值 0.0f
  • double:8 bytes,默认值 0.0d
  • char:2 bytes(UTF-16 code unit),默认值 '\u0000'(即 0)
  • boolean:JVM 语义上用 1 bit 表示,默认值 false

另外,引用类型(对象、数组等)的默认值是 null


4. byteshortintlong 各占多少字节?

  • byte1 字节(8 位)
  • short2 字节(16 位)
  • int4 字节(32 位)
  • long8 字节(64 位)

(上述为 Java 语言规范定义,与平台无关。)


5. floatdouble 的区别?

  • 精度与存储:
    • float:32 位单精度浮点(IEEE 754),约 6~7 位有效数字。
    • double:64 位双精度浮点(IEEE 754),约 15~16 位有效数字。
  • 精度更高的 double 更常用;float 用于节省内存或与特定 API(如图形库)兼容。
  • 注意:浮点数有精度误差,不适合做精确货币计算(应使用 BigDecimal)。
  • 默认浮点字面量为 double(例如 1.2double,写成 1.2f 才是 float)。

6. char 类型占几个字节?能存汉字吗?

  • char2 字节(16 位),表示 UTF-16 的一个 code unit。
  • 能否存汉字:多数常用汉字(位于基本多文种平面 BMP)可以用单个 char 表示(例如 '你');但有些罕见汉字或 emoji 属于补充平面(Supplementary Planes),它们需要 两个 char(称为 surrogate pair) 来表示。
  • 因此,单个 char 能表示一个 UTF-16 code unit,而不是完整的 Unicode code point。处理 Unicode 字符时注意使用 int codePoint / Character 的相关 API。

7. 自动装箱(autoboxing)和拆箱(unboxing)的原理是什么?

  • 概念

    • 装箱:把基本类型自动转换成对应的包装类型(如 intInteger)。
    • 拆箱:把包装类型自动转换回基本类型(如 Integerint)。
  • 编译器行为:自动装箱/拆箱是在编译期由 Java 编译器插入对应的调用,示例:

    1
    2
    Integer a = 10;        // 编译器会转换为 Integer.valueOf(10)
    int b = a; // 编译器会转换为 a.intValue()
  • valueOf 缓存:包装类(如 Integer.valueOf(int))会对小整数(默认 -128 到 127)做缓存以重用对象,减少频繁创建对象。

  • 注意事项

    • 拆箱 null 会抛出 NullPointerException

      1
      2
      Integer x = null;
      int y = x; // NPE
    • 装箱/拆箱会有性能开销(对象创建、装箱/拆箱方法调用),在性能敏感场景尽量使用基本类型或手动优化。

    • 比较时注意:Integer a = 100; Integer b = 100; a==b 在缓存范围内可能为 true,超出范围则通常为 false


8. ==equals() 的区别?

  • ==
    • 对于基本类型:比较 (数值是否相等)。
    • 对于引用类型:比较 引用地址(是否是同一个对象)。
  • equals()
    • Object 的方法,默认实现也是比较引用(等价于 ==)。
    • 许多类(如 StringInteger、集合类等)重写了 equals(),用于比较逻辑/内容相等
  • 使用建议
    • 比较对象内容用 equals()(需检查 null),比较是否同一实例用 ==
    • equals() 配套应重写 hashCode()(见下题)。

9. hashCodeequals 的关系?

  • 合同(Contract)(重要):
    1. 如果两个对象通过 equals() 被判定为相等(a.equals(b)true),那么 a.hashCode() == b.hashCode() 必须成立。
    2. 反之不要求:hashCode 相等的对象不一定 equals() 相等(哈希冲突允许)。
  • 在哈希集合中的角色
    • 哈希表(HashMap/HashSet)先用 hashCode() 找到桶(bucket),若桶中有多个元素,再用 equals() 逐个比较确认相等或冲突。
    • 如果只重写 equals() 而不重写 hashCode() 会破坏集合行为(例如放入 HashSet 后无法正确查找)。
  • 实现要点
    • hashCode() 要尽量分散(降低冲突),并在对象不可变字段上基于相同规则计算。
    • 若对象可变,若用于哈希集合要小心:修改字段会导致 hashCode() 改变,破坏集合内部结构。

10. StringStringBufferStringBuilder 的区别?

  • String
    • 不可变(immutable),每次修改都会产生新的对象(或新内部 char/byte 数组)。
    • 线程安全(因为不可变),适合频繁读取、少量修改的场景。
  • StringBuffer
    • 可变的字符序列(内部有缓冲区 char[]/byte[]),几乎与 StringBuilder 接口相同。
    • 线程安全,其方法大多使用 synchronized,因此在多线程下可以被多个线程安全使用。
    • 相对较慢(同步开销)。
  • StringBuilder(Java 5+)
    • 可变,非线程安全(没有同步),比 StringBuffer 快。
    • 推荐在单线程或外部已同步的场景下使用。
  • 选择建议
    • 多线程需要可变字符串:StringBuffer(或外部同步)。
    • 单线程/局部构造字符串:StringBuilder
    • 常量字符串或少量拼接:String(编译器对 + 会优化为 StringBuilder)。

11. 为什么 String 是不可变的(immutable)?

  1. 安全性String 经常用于关键场景(类加载器、网络地址、文件名、权限检查、数据库连接字符串等)。不可变保证在传递引用时不被恶意或意外修改。
  2. 线程安全:不可变对象固有线程安全,多个线程可共享同一 String 实例而无需同步。
  3. 性能(缓存 hashCode)String 的哈希值可缓存(hash 字段),便于作为 Map 的 key,避免重复计算。
  4. 字符串常量池:可安全地将字面量放入池中重用,不用复制或担心修改。
  5. 优化:JVM 可进行共享、常量折叠等优化(更易于实现某些编译期/运行期优化)。

12. String 常量池的实现机制?

  • 概念:编译期和运行期维护一个字符串池(String Intern Pool),用于存放字符串字面量(literal)和显式 intern() 后的字符串,以便重用相同内容的 String 实例。
  • 编译期:源代码中的字符串字面量(如 "abc")会被放到常量池(编译后的 class 文件常量池),类加载后这些字面量会放入运行时的字符串池。
  • 运行期
    • 以前(Java 6 及更早):字符串常量池在 PermGen(方法区)中。
    • Java 7 起:常量池迁移到 Java 堆(运行时常量池也在堆里),避免 PermGen 问题。
  • intern():当调用 s.intern() 时,JVM 会检查池中是否已有相同内容的字符串:
    • 若存在,返回池中的引用;
    • 若不存在,将该字符串的引用加入池并返回它。
  • 编译时优化:字符串常量的拼接(编译时常量)会在编译期合并,如 "a" + "b""ab",直接放入常量池;而运行时拼接(变量参与)则使用 StringBuilder

13. new String("abc") 创建了几个对象?

  • 通常情形
    • "abc" 字面量尚未在常量池中存在,执行 new String("abc") 会导致 两个对象 被创建:常量池中的 "abc"(一个 String)和堆中通过 new 创建的新的 String 实例(内容通常是对常量池中字符数组的复制或共享,具体实现随 Java 版本而异)。
    • 如果字面量 "abc" 已经存在于常量池中(例如之前被加载过),那么 new String("abc") 只会创建 一个堆对象(new 的那个 String)。
  • 注意:自 Java 7/9 后 String 内部实现改变(压缩字符串、byte[] 存储等),但逻辑上上述结论成立:通常 1 或 2 个对象,取决于常量池中是否已存在该字面量。

14. final 关键字的作用?

  • 用于类(final class):类不可被继承(如 String)。
  • 用于方法(final 方法):方法不能被子类重写(override),用于确保行为不可变。
  • 用于变量(final 字段/局部变量)
    • 对基本类型:赋值后值不可改变(常量)。
    • 对引用类型:引用不可改变(不能指向另一个对象),但所指向对象的内部状态仍可变(除非对象本身不可变)。
    • static final 常用来定义常量(编译期常量)。
  • 用于参数(方法形参可以声明为 final):代表方法内不能修改该参数引用/值。
  • 其它用途:在多线程中,final 字段的写-构造过程被 JMM(Java 内存模型)处理,确保构造完成后其他线程可见(安全发布方面有好处)。

15. static 关键字的作用?

  • 用于声明类级别成员(字段、方法、初始化块、嵌套类),不依赖实例。
  • static 变量:类变量,所有实例共享一份。内存中仅有一份副本。
  • static 方法:类方法,可通过 ClassName.method() 调用;不能直接访问非静态成员(因无 this)。
  • static 代码块:类加载时执行一次(用于静态初始化)。
  • static 嵌套类:可以声明为静态的内部类(静态嵌套类),没有外部实例引用。

16. static 修饰变量、方法、代码块分别意味着什么?

  • static 变量(类变量)
    • 随类加载而创建,所有对象共享同一份数据。
    • 可通过 ClassName.fieldinstance.field(不推荐)访问。
  • static 方法
    • 属于类,调用时无需实例。
    • 不能使用 this、不能直接访问非静态成员。
    • 可作为工具方法(如 Math.abs())。
  • static 代码块
    • 在类加载阶段执行一次,用于复杂静态初始化(比如初始化静态常量、加载本地库等)。
    • 执行顺序:静态块按定义顺序执行,类加载时运行(在实例化之前)。

17. staticfinal 能一起用吗?

  • static final 常用于定义类常量(尤其是基本类型和 String),例如:

    1
    2
    public static final int MAX = 100;
    public static final String NAME = "abc";
  • 区别

    • 如果是编译期常量(static final 基本类型或 String 且在编译时可以确定),编译器会将其内联到使用处(被引用的类编译后看到的是常量值),注意跨模块修改可能导致需要重新编译引用方。
    • 如果是 static final 引用对象且不是编译期常量,则引用本身不可变,但对象内容可能可变。

18. static 内部类和非静态内部类的区别?

  • 静态内部类(static nested class)
    • 相当于外部类的一个静态成员。
    • 没有对外部类实例的隐式引用(不能直接访问外部类的非静态成员)。
    • 可以像普通类那样实例化:Outer.StaticInner inner = new Outer.StaticInner();
  • 非静态内部类(inner class)
    • 每个实例隐式持有一个外部类实例引用(Outer.this),可以直接访问外部类的所有成员(包括私有成员)。
    • 创建方式:Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();
    • 占用外部类对象的内存引用,可能导致内存泄露(如果长期持有内部类实例导致外部类不能被回收)。
  • 选择原则:如果内部类不需要访问外部实例成员,优先使用 static 嵌套类,避免额外引用。

19. Java 中的 thissuper 的区别?

  • this
    • 引用当前对象的引用。
    • 用于访问当前类的实例变量、调用当前类的其他构造器(this(...))、或传递当前对象引用。
  • super
    • 引用当前对象的父类部分。
    • 用于访问父类被覆盖的方法(super.method())、父类字段(若有同名字段)、以及在子类构造器第一行调用父类构造器(super(...))。
  • 注意
    • this()super() 必须在构造器的第一行(两者不能同时出现)。
    • super 不能用于访问父类的 private 成员(编译期限制)。

20. Java 中构造方法能否被重写?

  • 不能。重写(override)适用于实例方法,构造方法不是继承的成员,子类不能重写父类的构造器。
  • 可以重载(overload):在同一类中同名构造器有不同参数列表属于重载。
  • 子类构造器可通过 super(...) 显式调用父类构造器来完成父类初始化。

21. 接口和抽象类的区别?

  • 接口(interface)
    • 早期(Java 7 之前):只包含抽象方法(默认是 public abstract)和 public static final 常量。
    • Java 8+:可以包含 default 方法与 static 方法(可以有部分实现);Java 9+ 允许 private 方法。
    • 支持多继承(一个类可以实现多个接口)。
    • 没有实例字段(除 static final 常量外)。
  • 抽象类(abstract class)
    • 可以有实例字段(非静态成员变量)、已有实现的方法(具体方法)、构造器。
    • 适合表示一类有共同行为并共享状态的类层次。
    • 类只能单继承(只能继承一个抽象类)。
  • 选择建议
    • 如果需要多继承行为、只定义方法契约且不需要状态,使用接口。
    • 如果需要提供共有实现和状态,使用抽象类。

22. 接口中可以有 default 方法和 static 方法吗?

  • 可以(Java 8 起)
    • default 方法:为实现类提供默认实现,避免破坏已有实现(接口演化)。实现类可以覆盖(override)。
    • static 方法:属于接口自身,可以通过 InterfaceName.method() 调用,不能通过实例调用。
  • Java 9 及以后还可有 private 方法,供接口内部复用实现逻辑。

23. Java 8 接口新增了什么?

  • Java 8 在接口中新增了 default 方法static 方法,允许接口提供带实现的方法,从而便于接口的演化(向后兼容)。这也是 Java 8 引入函数式编程支持的基础之一(允许接口作为函数式接口)。

24. Java 8 有什么新特性,并详细介绍(重点)

一、Java 8 新特性深入解析

1. Lambda 表达式

概念

  • 匿名函数,允许把行为作为参数传递。
  • 简化匿名内部类写法,实现函数式编程。

示例

1
2
List<String> list = Arrays.asList("a", "bb", "ccc");
Collections.sort(list, (s1, s2) -> s1.length() - s2.length());

底层原理

  • 编译器生成静态/实例方法 + invokedynamic 指令。
  • JVM 调用 LambdaMetafactory 动态生成函数对象,实现 函数式接口
  • 捕获外部变量通过闭包机制存储(必须是 effectively final)。

特点

  • 语法简洁
  • 可作为参数传递行为
  • 性能高于匿名内部类(减少对象创建)

2. 函数式接口

概念

  • 仅有一个抽象方法的接口,用作 Lambda 表达式的目标类型。
  • 可用 @FunctionalInterface 标识。

示例

1
2
3
4
5
6
7
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}

Converter<String, Integer> c = Integer::valueOf;
System.out.println(c.convert("123")); // 123

底层原理

  • Lambda 表达式编译时生成实现函数式接口的函数对象。
  • 捕获变量通过闭包对象保存。

特点

  • 简化匿名类
  • 支持函数式编程
  • 可与 Stream / Optional 等结合

3. Stream API

概念

  • 对集合进行声明式操作(过滤、映射、归约)。
  • 支持 惰性求值并行处理

示例

1
2
3
4
5
6
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> filtered = names.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(filtered); // [ALICE, CHARLIE]

底层原理

  • Stream 是流水线对象,中间操作返回新的 Stream,终端操作触发计算。
  • 串行流:顺序迭代器处理
  • 并行流:ForkJoinPool 分块处理

特点

  • 声明式、链式操作
  • 支持并行
  • 可与 Lambda / 方法引用结合

4. 方法引用

概念

  • Lambda 表达式的简化写法,引用现有方法。
  • 类型:
    1. 静态方法引用:ClassName::staticMethod
    2. 实例方法引用:instance::method
    3. 构造器引用:ClassName::new

示例

1
2
3
List<String> names = Arrays.asList("a", "bb", "ccc");
names.forEach(System.out::println); // 实例方法引用
Supplier<List<String>> listSupplier = ArrayList::new; // 构造器引用

底层原理

  • 编译器转成 Lambda 表达式 + invokedynamic。
  • JVM 生成实现函数式接口的函数对象,内部持有方法引用。

特点

  • 简洁直观
  • 可减少 Lambda 代码
  • 与 Stream / Optional 配合使用

5. java.time API

概念

  • 替代 Date / Calendar,不可变、线程安全。
  • 核心类:
    • LocalDate / LocalTime / LocalDateTime(无时区)
    • ZonedDateTime(带时区)
    • Duration / Period(时间段)

示例

1
2
3
4
5
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Singapore"));
System.out.println(tomorrow);
System.out.println(zdt);

底层原理

  • 内部字段 final 保存值,不可变。
  • 工厂方法创建对象,链式操作返回新对象。
  • 使用 enum + 整型/字节优化存储(如 LocalDate 年月日用 int)。

特点

  • 不可变,线程安全
  • 链式操作,易组合
  • 支持各种历法和时区计算

6. Optional

概念

  • 容器对象,防止 NullPointerException
  • 可以包含值或为空(empty)。

示例

1
2
3
4
5
6
Optional<String> opt1 = Optional.of("Hello");
Optional<String> opt2 = Optional.ofNullable(null);

System.out.println(opt1.isPresent()); // true
System.out.println(opt2.orElse("Default")); // Default
opt1.map(String::toUpperCase).ifPresent(System.out::println); // HELLO

底层原理

  • 内部 value 字段存储非空值,空值使用单例 EMPTY
  • map / flatMap / filter 返回新的 Optional,对象不可变。
  • 与 Lambda / 函数式接口结合实现链式安全操作。

特点

  • 避免显式 null 判断
  • 支持链式调用
  • 可与 Stream / Lambda 配合

Java 8 六大核心特性底层对比表

特性 底层实现 核心原理 优势
Lambda invokedynamic + LambdaMetafactory 动态生成函数对象 简洁、性能高
函数式接口 单抽象方法接口 捕获变量通过闭包存储 简化匿名类,函数式编程
Stream API 流水线 + 延迟求值 中间操作惰性,终端触发 声明式、可并行
方法引用 Lambda 简化语法 + invokedynamic 内部持有方法引用 简洁直观,减少代码
java.time 不可变对象 + 工厂方法 final字段保存值,链式返回新对象 线程安全,可组合
Optional 包装对象 + 单例 EMPTY 不可变容器,函数式链式调用 避免 NPE,安全链式操作

二、synchronized 底层原理深入解析

1. JVM 锁的类型

锁类型 特点 性能
偏向锁 (Biased Lock) 无竞争时偏向第一个线程,轻量
轻量级锁 (Lightweight) 竞争少时使用 CAS 升级锁
重量级锁 (Monitor) 高竞争时使用 OS mutex,阻塞线程

2. synchronized 对象头结构

  • 每个对象在 JVM 内存中都有 对象头(Mark Word),存储锁信息、哈希码、GC 标记等。
1
2
3
4
对象头 Mark Word (32/64bit)
+----------------+----------------+----------------+
| HashCode | GC info | Lock info |
+----------------+----------------+----------------+
  • 锁标记位
    • 00:无锁
    • 01:偏向锁
    • 10:轻量级锁
    • 11:重量级锁

3. synchronized 执行流程

a) 偏向锁(无竞争)

1
线程获取锁 -> 在对象头打上线程ID -> 执行 -> 释放锁 -> 保持偏向状态

b) 轻量级锁(CAS竞争)

1
2
线程尝试CAS抢锁 -> 成功:持有锁 -> 执行 -> 释放
|-> 失败:升级为重量级锁

c) 重量级锁(阻塞)

1
线程无法获取锁 -> 阻塞(OS等待队列) -> 被唤醒 -> 获取锁 -> 执行 -> 释放锁

图示

1
2
3
4
5
6
7
8
9
+------------------+
| synchronized() |
+------------------+
|
JVM对象头Mark Word
|
+------------------------+
| 偏向锁 -> 轻量级锁 -> 重量级锁
+------------------------+

4. synchronized 方法示例

1
2
3
4
5
6
7
8
9
10
11
class Counter {
private int count = 0;

public synchronized void increment() {
count++;
}

public int getCount() {
return count;
}
}
  • 锁对象:实例方法锁住 this,静态方法锁住类对象 ClassName.class
  • 效果:同一时间只有一个线程能进入 increment(),保证 count++ 操作的原子性。

5. JVM 优化

  • 锁消除:编译器或 JIT 可检测无竞争情况,消除同步锁。
  • 锁粗化:把多次连续的小范围锁合并,减少加锁次数。
  • 偏向锁 / 轻量级锁 / 重量级锁升级:动态适应竞争情况,兼顾性能与正确性。

25.多态的实现机制是什么?

  • 概念:同一操作作用于不同对象时表现出不同的行为(主要表现为方法的动态绑定)。
  • 实现机制
    • 编译时:根据引用类型检查方法签名(静态类型检查)。
    • 运行时:实际调用的方法根据对象的实际类型(运行时类型)决定;即 动态绑定(dynamic dispatch)
    • JVM 通常通过 虚方法表(vtable)/方法查找机制 实现。调用一个非 static、非 private、非 final 的方法时,会在运行时查找实际类的实现并调用。
  • 结果:子类可以覆盖父类方法,调用者使用父类引用指向子类对象时,调用的是子类的覆盖方法(运行时决定)。
  • 注意staticprivatefinal 方法不会被动态绑定(是静态绑定)。

26. 方法重载(overload)和方法重写(override)的区别?

  • 方法重载(Overloading)

    • 同一类中方法名相同、参数列表不同(参数类型/个数/顺序),返回类型可以不同。
    • 编译时决定(重载解析在编译期完成),与继承关系无关。
  • 方法重写(Overriding)

    • 子类定义与父类相同方法签名(方法名 + 参数类型相同)的实现,目的是改变/扩展父类行为。
    • 运行时动态绑定,必须满足访问权限不能更严格,抛出的受检异常不能超过父类版本等规则。
  • 示例

    1
    2
    3
    4
    5
    6
    // overload
    void f(int x) {}
    void f(String s) {}
    // override
    class A { void m() {} }
    class B extends A { @Override void m() {} }

27. Java 支持多继承吗?如何实现类似效果?

  • 类的多继承:Java 不支持类的多继承(不能继承多个类),以避免菱形继承问题(diamond problem)。
  • 实现类似效果的方法
    • 接口多实现:一个类可以实现多个接口(Java 8 的 default 方法也带来类似多继承方法实现的可能,但有冲突解决规则)。
    • 组合/委托(composition/delegation):在类中持有其它类的实例并把调用委托给它们(优于继承的面向对象设计原则)。
  • 接口冲突解决:若多个接口提供相同默认方法,类必须重写该方法并明确调用哪个接口的默认实现(InterfaceName.super.method())。

28. Java 的四种访问修饰符?

  • public:对所有类可见(任何包)。
  • protected:对同包类和子类可见(即同包或子类可以访问)。
  • 默认(包私有,package-private)(不写修饰符):对同包类可见,包外不可见。
  • private:仅在本类内可见,包外/子类不可访问(子类无法直接访问父类 private 成员)。
  • 注意:对类(顶层类)只能使用 public 或默认(包私有),不能声明为 private/protected

29. transient 关键字的作用?

  • 用于标记字段在 Java 序列化(Serializable)过程中不被序列化
  • 被标记为 transient 的字段在序列化后不会写入序列化流,反序列化时这些字段会以默认值恢复(基本类型 0,引用类型 null)。
  • 常用于敏感信息(密码)、不需要持久化的缓存字段、或可重建的状态字段。
  • 注意:static 字段本身也不会被序列化(因为是类级别,不属于实例状态)。

30. volatile 关键字的作用?

  • 可见性:保证对 volatile 变量的写入对其它线程立即可见(读取总是从主内存而不是线程缓存读取)。
  • 禁止指令重排序(部分):读/写 volatile 存在的内存屏障能保证一定的有序性(写 volatile 发生在后续读的可见性上,更多细节参考 JMM)。
  • 不保证原子性:对单次读/写操作是原子的(对 long/double 在 Java 5 后也保证原子),但非原子操作(如 i++)不是原子的。
  • 典型用途
    • 状态标志(如 volatile boolean running)。
    • 用于实现双重检查锁定(DCL)单例中的 instance(在 Java 5+ 环境下有效)。
  • 与 synchronized 比较
    • volatile 轻量,只保证可见性与部分有序性,不保证互斥;适用于简单状态通信。
    • synchronized 提供互斥和可见性保证(更重),并可配合 wait/notify

31. synchronized 的作用及底层原理?

作用

  • 互斥(排它):保证同一时间只有一个线程可以执行被 synchronized 修饰的代码块或方法(针对同一把锁)。
  • 可见性:进入/退出同步块会建立 happens-before 关系,确保锁释放前的写对随后获取该锁的线程可见。
  • 用途:保护临界区、确保多个线程对共享可变状态的安全访问。

使用方式

  • 方法级别
    • synchronized void m() { ... }:等价于 synchronized(this)(实例方法)或 synchronized (ClassName.class)(静态方法)。
  • 代码块级别
    • synchronized(this) { ... }synchronized(lockObj) { ... } 更灵活,能减小锁的粒度。

底层原理(HotSpot 实现概要)

  • JVM 使用对象头(object header)中的 mark word 和关联数据结构维护锁状态。
  • 锁优化策略(为提升性能,HotSpot 引入若干优化):
    1. 偏向锁(Biased Locking):在没有竞争的情况下,锁会偏向于第一次获得它的线程,重复获取无需 CAS,减少开销。
    2. 轻量级锁(Lightweight Locking):使用 CAS 操作在栈上记录加锁记录,避免进入重量级监视器(monitor)。
    3. 重量级锁(Monitor/Mutex):当竞争激烈或 CAS 失败时,升级为重量级锁,使用操作系统互斥量(可能涉及线程阻塞/唤醒)。
  • monitorenter / monitorexit 是字节码指令(由编译器/字节码生成器生成)。
  • JIT 编译器可进行锁消除、锁粗化、锁优化等(当能证明无并发访问或已外部同步时)。

wait/notify/notifyAll

  • Object.wait()notify()notifyAll() 必须在持有对象监视器(即在 synchronized 块内)时调用,用于线程间协作(条件等待/通知)。
  • wait() 会释放锁并进入等待队列;notify() 唤醒等待队列中的一个线程(被唤醒线程在重新获得锁后继续)。

注意与陷阱

  • 锁粒度:避免用过大锁(如 synchronized 在方法头部锁住大量操作),谨慎使用 String 或装箱对象作为锁(可能会导致多个实例共用同一锁或锁被外部持有)。
  • 死锁:多线程锁顺序不当可能死锁;设计时谨防。
  • 性能:在高并发下可考虑使用 java.util.concurrent 包(ReentrantLockConcurrentHashMapAtomicXxx)等更细粒度、高性能的并发工具。

二、面向对象编程(OOP)


1. 面向对象的三大特性是什么?

  • 封装(Encapsulation):隐藏实现细节,只暴露必要接口。
  • 继承(Inheritance):子类复用父类属性和方法,扩展功能。
  • 多态(Polymorphism):同一接口,不同实现。表现为方法重写、方法重载。

2. 封装的作用是什么?

  • 隐藏对象内部实现细节,只暴露必要接口。
  • 提高代码复用性、安全性,避免数据被随意修改。
  • 例如:类的成员变量用 private,提供 getter/setter 访问。

3. 多态的优点是什么?

  • 接口统一:不同实现类可通过父类/接口操作。
  • 可扩展性强:新增子类不影响原有代码。
  • 解耦:调用方只依赖抽象,而不依赖具体实现。
  • 例:List list = new ArrayList();,后续可换成 LinkedList

4. 重写方法时返回值能否不同?

  • 不能完全不同
  • Java 允许 协变返回类型:子类方法的返回值类型可以是父类方法返回值的子类。
  • 例如:父类返回 Number,子类可以返回 Integer

5. 构造函数能否被继承?

  • 不能继承,因为构造函数名必须与类名相同。
  • 但子类可通过 super(...) 调用父类构造方法。

6. 构造函数能否 private?

  • 可以,常见于单例模式(饿汉/懒汉/枚举单例)。
  • 作用:禁止外部 new,只能通过类提供的方法获取实例。

7. 单例模式的几种实现方式?

  1. 饿汉式(类加载即实例化,线程安全,但可能浪费内存)。
  2. 懒汉式(延迟加载,需加 synchronized 保证线程安全)。
  3. 双重检查锁(DCL,volatile + synchronized,性能优)。
  4. 静态内部类(推荐,利用类加载机制,线程安全)。
  5. 枚举单例(最佳实践,防止反射和反序列化攻击)。

8. 饿汉式和懒汉式单例的区别?

  • 饿汉式:类加载时实例化 → 线程安全,启动时可能浪费内存。
  • 懒汉式:第一次使用时才实例化 → 节省资源,但需加锁保证线程安全。

9. 为什么要使用内部类?

  • 内部类可以 访问外部类的私有成员
  • 更好地组织代码,使逻辑更紧密。
  • 在需要回调或事件监听时常用。

10. 成员内部类、局部内部类、静态内部类的区别?

  • 成员内部类:依附于外部类实例,可以访问外部类实例变量。
  • 局部内部类:定义在方法内部,作用范围仅在方法内。
  • 静态内部类:不依赖外部类实例,只能访问外部类静态成员。

11. Java 中对象的创建方式有哪些?

  1. new 关键字。
  2. 反射 Class.newInstance()
  3. Constructor.newInstance()
  4. 通过 clone()
  5. 通过反序列化 ObjectInputStream.readObject()

12. 对象之间的浅拷贝和深拷贝的区别?

  • 浅拷贝:只复制对象本身,引用字段仍指向同一对象。
  • 深拷贝:连引用对象也复制一份,完全独立。

13. clone() 方法的原理?

  • 来自 Object 类,默认是 浅拷贝
  • 必须实现 Cloneable 接口,否则抛出 CloneNotSupportedException
  • 可重写 clone() 实现深拷贝。

14. 为什么 Java 不支持多继承?

  • 为了避免 菱形继承问题(多个父类方法冲突)。
  • Java 使用 接口 来实现多继承特性。

15. Java 是值传递还是引用传递?

  • Java 只有值传递
  • 对象参数传递的是 引用的副本(值),但指向同一对象。

16. 方法参数传递时是如何处理对象的?

  • 方法内修改对象的字段 → 会影响外部对象。
  • 方法内给引用变量重新赋值 → 不会影响外部对象。

17. 封装性体现在哪些方面?

  • 使用 private 修饰成员变量。
  • 提供 getter/setter 控制访问权限。
  • 使用访问修饰符(public/protected/default/private)控制类和方法的可见性。

18. 为什么需要继承?

  • 代码复用(减少重复)。
  • 提高扩展性(子类扩展父类功能)。
  • 实现多态,解耦业务逻辑。

19. 重写 Object 类的 toString 方法的意义?

  • 提高可读性,方便调试和日志记录。
  • 默认 toString() 打印类名+hashcode,不直观。
  • 重写后可打印对象核心字段信息。

20. 重写 Object 类的 equals 方法时需要注意什么?

  • 必须满足 自反性、对称性、传递性、一致性
  • hashCode() 保持一致:相等对象必须有相同的 hashCode。
  • 避免 NullPointerException

三、异常处理


1. Java 的异常体系结构?

  • 顶层类Throwable
    • Error(错误,程序无法处理,通常由 JVM 抛出)
      • 如:OutOfMemoryErrorStackOverflowError
    • Exception(异常,程序可处理)
      • Checked Exception(受检异常,编译期检查)
        • 如:IOExceptionSQLException
      • Unchecked Exception(运行时异常,编译器不强制处理)
        • 如:NullPointerExceptionArrayIndexOutOfBoundsException

👉 结构图:

1
2
3
4
5
Throwable
├── Error
└── Exception
├── Checked Exception
└── Unchecked Exception (RuntimeException)

2. Checked Exception 和 Unchecked Exception 的区别?

  • Checked Exception(受检异常)
    • 必须显式处理(try-catch 或 throws 声明)。
    • 编译器强制检查,否则无法通过编译。
    • 典型场景:IO、数据库、网络调用。
  • Unchecked Exception(非受检异常 / 运行时异常)
    • 不强制处理,编译器不检查。
    • 通常由程序逻辑错误导致。
    • 典型场景:NPE、除零、数组越界。

3. throw 和 throws 的区别?

  • throw
    • 用于方法体内部,抛出具体的异常对象
    • 语法:throw new Exception("msg");
  • throws
    • 用于方法声明处,标识该方法可能抛出的异常类型
    • 语法:public void test() throws IOException {}

👉 总结:

  • throw = 抛出异常实例。
  • throws = 声明可能抛出的异常类型。

4. try-catch-finally 的执行顺序?

  1. try 块先执行。
  2. 如果发生异常,跳到对应 catch 执行。
  3. finally 一定会执行(除非 System.exit())。
  4. 如果 trycatchreturn,会先执行 finally,再返回结果。

5. finally 中的 return 会覆盖 try 中的 return 吗?

会覆盖

  • 如果 try 中有 return,但 finally 中也有 return,最终返回的是 finally 的结果。
    👉 因此实际开发中 不推荐在 finally 中写 return

6. try-with-resources 的作用?

  • Java 7 引入,简化资源管理(如流、数据库连接)。
  • 自动关闭实现了 AutoCloseableCloseable 接口的资源。
  • 避免忘记 finally { resource.close(); }

示例:

1
2
3
4
5
try (BufferedReader br = new BufferedReader(new FileReader("a.txt"))) {
System.out.println(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}

7. 自定义异常类如何实现?

  • 继承 Exception(Checked)或 RuntimeException(Unchecked)。
  • 提供构造方法:无参、带 message、带 cause。

示例:

1
2
3
4
5
public class MyException extends RuntimeException {
public MyException(String msg) {
super(msg);
}
}

8. Error 和 Exception 的区别?

  • Error:系统级错误,JVM 无法恢复,程序不应该捕获。
    • OutOfMemoryErrorStackOverflowError
  • Exception:应用级异常,程序应该处理。
    • IOExceptionSQLExceptionNullPointerException

9. NullPointerException 常见场景有哪些?

  1. 调用空对象的方法:obj.toString()
  2. 访问空数组:arr.length
  3. 访问空集合:list.size()
  4. 自动拆箱:Integer num = null; int n = num;
  5. Map 查找为 null,再调用方法:map.get("key").toString()
  6. 依赖注入/未初始化的对象。

10. 运行时异常需要捕获吗?为什么

  • 通常不需要显式捕获,因为它们多由逻辑错误导致(如 NPE、除零)。
  • 捕获运行时异常并不能解决问题,应该通过修改代码逻辑避免。
  • 但在某些关键服务(如 API 层)可以用统一异常处理(如 Spring @ControllerAdvice)拦截,避免程序直接崩溃。

四、集合框架(Collections)


1. Collection 和 Collections 的区别?

  • Collection:集合接口,是所有集合类的父接口(List、Set)。
  • Collections:工具类,提供操作集合的静态方法(排序、搜索、同步包装等)。

2. List、Set、Map 的区别?

  • List:有序、可重复,按索引访问(ArrayList、LinkedList)。
  • Set:无序、不可重复,基于哈希或树(HashSet、TreeSet)。
  • Map:键值对存储,Key 不可重复,Value 可重复(HashMap、TreeMap)。

3. ArrayList 和 LinkedList 的区别?

  • ArrayList:基于动态数组,查询快(O(1)),增删慢(O(n))。
  • LinkedList:基于双向链表,增删快(O(1)),查询慢(O(n))。

4. HashMap 的底层实现?

  • JDK 1.7:数组 + 链表。
  • JDK 1.8:数组 + 链表 + 红黑树(链表长度 ≥ 8 时转为树)。

5. HashMap 1.7 和 1.8 的区别?

  1. 1.7:数组 + 链表,采用头插法,易出现并发死循环。
  2. 1.8:数组 + 链表/红黑树,尾插法,避免死循环,提高性能。
  3. 1.8 引入红黑树,查找效率从 O(n) 优化为 O(log n)。

6. HashMap 如何解决哈希冲突?

  • 方法:拉链法(链表)+ 红黑树。
  • 冲突时,将元素挂到链表/树上。

7. HashSet 的底层实现?

  • 基于 HashMap 实现。
  • HashSet 的元素存储在 HashMap 的 key 上,value 为一个固定对象 PRESENT

8. ConcurrentHashMap 的底层实现?

  • JDK 1.7:分段锁(Segment + HashEntry)。
  • JDK 1.8:CAS + synchronized,数据结构与 HashMap 类似,数组 + 链表 + 红黑树。

9. Hashtable 和 HashMap 的区别?

  • 线程安全性:Hashtable 是同步的,HashMap 不是。
  • null:Hashtable 不允许 key、value 为 null;HashMap 允许一个 null key 和多个 null value。
  • 效率:HashMap 更高效。

10. TreeMap 和 HashMap 的区别?

  • TreeMap:基于红黑树,有序(按 Key 排序)。
  • HashMap:基于哈希表,无序。

11. WeakHashMap 的特点?

  • Key 使用 弱引用,当没有强引用指向该 Key 时,会被 GC 回收。
  • 常用于缓存。

12. CopyOnWriteArrayList 的应用场景?

  • 写时复制:写操作时复制新数组,读操作不加锁。
  • 适用于 读多写少 的并发场景,比如缓存、订阅列表。

13. LinkedHashMap 的底层原理?

  • 基于 HashMap + 双向链表。
  • 保证插入顺序(或 LRU 顺序)。

14. Map 的 key 是否可以为 null?

  • HashMap:允许一个 null key。
  • Hashtable / TreeMap:不允许 null key。

15. ArrayList 扩容机制?

  • 初始容量 10,超过容量时扩容为原来的 1.5 倍。
  • 通过 Arrays.copyOf() 实现数组复制。

16. Vector 和 ArrayList 的区别?

  • Vector:线程安全(方法加 synchronized),扩容为 2 倍。
  • ArrayList:非线程安全,扩容为 1.5 倍。

17. PriorityQueue 的底层实现?

  • 基于 二叉小顶堆,保证队头元素是最小值。
  • 插入、删除操作时间复杂度 O(log n)。

18. BlockingQueue 的几种实现类?

  • ArrayBlockingQueue:数组结构,有界。
  • LinkedBlockingQueue:链表结构,可选容量。
  • PriorityBlockingQueue:优先级队列。
  • DelayQueue:延时队列。
  • SynchronousQueue:不存储元素,直接移交。

19. HashMap 死循环问题出现在哪个版本?

  • JDK 1.7 多线程扩容时,链表采用头插法,可能形成环,导致死循环。
  • JDK 1.8 使用尾插法,解决该问题。

20. fail-fast 和 fail-safe 的区别?

  • fail-fast:迭代过程中结构被修改,会抛出 ConcurrentModificationException。如:ArrayList、HashMap。
  • fail-safe:迭代基于副本,不会抛异常。如:CopyOnWriteArrayList、ConcurrentHashMap。

五、并发编程(JUC)


1. Java 中的线程生命周期?

  • NEW(新建)new Thread() 后,还没调用 start()
  • RUNNABLE(就绪/运行中):调用 start(),等待 CPU 调度。
  • BLOCKED(阻塞):等待锁。
  • WAITING(无限等待):调用 wait()join(),需其他线程唤醒。
  • TIMED_WAITING(限时等待):调用 sleep()wait(timeout)
  • TERMINATED(终止):线程执行完毕或异常退出。

2. Runnable 和 Callable 的区别?

  • Runnable:无返回值,不能抛出受检异常。
  • Callable:有返回值(Future 获取),可抛出异常。

3. ThreadPoolExecutor 的参数有哪些?

  • corePoolSize:核心线程数。
  • maximumPoolSize:最大线程数。
  • keepAliveTime:非核心线程存活时间。
  • unit:时间单位。
  • workQueue:任务队列(BlockingQueue)。
  • threadFactory:线程工厂(自定义线程名)。
  • handler:拒绝策略。

4. 线程池的拒绝策略有哪些?

  • AbortPolicy(默认):抛 RejectedExecutionException
  • CallerRunsPolicy:任务交给提交任务的线程执行。
  • DiscardPolicy:直接丢弃任务。
  • DiscardOldestPolicy:丢弃队列里最旧的任务,再尝试提交。

5. 创建线程的四种方式?

  1. 继承 Thread 类。
  2. 实现 Runnable 接口。
  3. 实现 Callable + FutureTask。
  4. 使用线程池(ExecutorService)。

6. synchronized 的底层实现原理?

  • 基于 对象头 (Mark Word)Monitor(管程)
  • 进入同步块时,尝试获取对象的 Monitor,获取不到则阻塞。
  • 底层依赖 JVM 指令monitorentermonitorexit

7. synchronized 和 ReentrantLock 的区别?

  • 锁类型:synchronized 是 JVM 级别,ReentrantLock 是 JUC 提供的 API。
  • 功能:ReentrantLock 支持公平锁/非公平锁、可中断、尝试加锁、条件变量。
  • 可重入性:两者都可重入。
  • 性能:synchronized 在 JDK1.6 之后优化(偏向锁、轻量级锁)性能已提升。

8. 公平锁和非公平锁的区别?

  • 公平锁:按照等待顺序获取锁。
  • 非公平锁:允许“插队”,减少上下文切换,提高吞吐量。
  • ReentrantLock 默认非公平。

9. AQS 的原理?

  • AQS(AbstractQueuedSynchronizer) 是构建锁和同步器的框架。
  • 内部维护一个 state(表示资源数量)和 FIFO 双向队列
  • 通过 CAS 修改 state,失败则将线程加入等待队列,挂起阻塞,等资源释放再唤醒。

10. CountDownLatch 的应用场景?

  • 倒计数器:一个线程等待多个线程完成后再执行。
  • 例子:主线程等待多个子任务执行完毕。

11. CyclicBarrier 的应用场景?

  • 栅栏:一组线程互相等待,直到所有线程到达屏障点再继续。
  • 例子:多人游戏,所有玩家都准备好再开始。

12. Semaphore 的应用场景?

  • 信号量:控制并发访问的线程数量。
  • 例子:停车场限制车位数、数据库连接池。

13. Exchanger 的应用场景?

  • 线程间数据交换:两个线程配对交换数据。
  • 例子:生产者和消费者交换缓冲区。

14. volatile 能保证原子性吗?

  • 不能,只能保证可见性和禁止指令重排。
  • 例子:count++ 在多线程下仍然不安全。

15. CAS 的原理?

  • Compare And Swap:比较内存值是否为预期值,如果是则更新,否则重试。
  • 底层依赖 CPU 的 cmpxchg 指令,保证原子性。

16. ABA 问题是什么?怎么解决?

  • 问题:CAS 只比较值,无法感知值被改过。例如 A→B→A。
  • 解决:使用 版本号/时间戳(AtomicStampedReference、AtomicMarkableReference)。

17. ThreadLocal 的作用及原理?

  • 作用:为每个线程提供独立变量副本,避免共享数据冲突。
  • 原理:Thread 内部有个 ThreadLocalMap,以 ThreadLocal 为 key 存储变量。

18. ThreadLocal 内存泄漏的原因?

  • ThreadLocalMap 的 key 是 弱引用,可能被回收,value 却还存在,导致泄漏。
  • 解决:用完及时 remove()

19. JUC 中的原子类有哪些?

  • 基本类型原子类:AtomicInteger、AtomicLong、AtomicBoolean。
  • 引用类型原子类:AtomicReference、AtomicStampedReference。
  • 数组原子类:AtomicIntegerArray、AtomicLongArray。
  • 对象属性更新器:AtomicIntegerFieldUpdater 等。

20. 乐观锁和悲观锁的区别?

  • 悲观锁:假设会发生冲突,先加锁再操作(synchronized、ReentrantLock)。
  • 乐观锁:假设不会冲突,先操作再检查(CAS)。

六、JVM & 内存模型


1. JVM 内存结构?

JVM 运行时主要分为:

  • 堆(Heap):存放对象实例,垃圾回收的主要区域。
  • 虚拟机栈(Stack):存放方法调用的局部变量、操作数栈、动态链接、返回地址。
  • 本地方法栈(Native Method Stack):为 native 方法服务。
  • 程序计数器(PC Register):记录线程当前执行的字节码位置。
  • 方法区(Method Area / Metaspace):存放类元数据、常量池、静态变量、JIT 编译后的代码。

2. 堆和栈的区别?

  • :存放对象实例,线程共享,需垃圾回收。
  • :存放方法帧、局部变量,线程私有,生命周期随线程结束。

3. 堆内存的分代模型?

  • 新生代(Young Generation):Eden + Survivor(S0/S1)。
  • 老年代(Old Generation):存放长生命周期对象。
  • 永久代/元空间:存放类元信息、常量池。

4. 新生代、老年代、永久代的区别?

  • 新生代:对象新建时分配,大部分对象“朝生夕死”。
  • 老年代:存活时间长或大对象直接进入。
  • 永久代(JDK 7 及之前):存放类信息、静态变量。
  • 元空间(JDK 8 之后):替代永久代,使用本地内存,避免 OOM: PermGen。

5. 元空间和永久代的区别?

  • 永久代(PermGen):使用 JVM 内存,容易 OOM。
  • 元空间(Metaspace):使用本地内存,理论上更大,更灵活。

6. 对象在堆中的分配过程?

  1. 在 Eden 区分配。
  2. Minor GC 时,幸存的对象进入 Survivor 区。
  3. 多次 Minor GC 仍存活 → 晋升老年代。
  4. 大对象(如大数组)可能直接进入老年代。

7. 对象什么时候进入老年代?

  • 在 Survivor 区经过多次 Minor GC,达到晋升阈值(默认 15 次)。
  • 大对象超过阈值,直接进入老年代。
  • Survivor 区放不下,直接进入老年代。

8. JVM 的垃圾回收算法?

  • 标记-清除:标记存活对象,清除未标记。缺点:碎片多。
  • 标记-整理:标记存活对象并移动,整理内存,避免碎片。
  • 复制算法:将活对象复制到新区域,清空旧区域,适合新生代。

9. 垃圾收集器有哪些?

  • Serial(单线程,适合小内存)。
  • ParNew(多线程版 Serial)。
  • Parallel Scavenge(吞吐量优先)
  • CMS(低延迟,响应快)
  • G1(区域化,均衡停顿时间)
  • ZGC、Shenandoah(超低延迟,适合大内存场景)

10. CMS 和 G1 的区别?

  • CMS:并发收集,低延迟,但可能产生内存碎片。
  • G1:基于分区(Region),可预测停顿时间,整理内存,适合大堆内存。

11. Minor GC 和 Full GC 的区别?

  • Minor GC:回收新生代,速度快,频繁。
  • Full GC:回收整个堆(新生代 + 老年代 + 元空间),耗时长,频率低。

12. 什么是 Stop The World?

  • GC 期间,所有工作线程必须停下来等待,造成应用暂停(STW)。
  • STW 是不可避免的,只能尽量减少时长。

13. JVM 常见的调优参数?

  • -Xms 初始堆大小。
  • -Xmx 最大堆大小。
  • -Xmn 新生代大小。
  • -XX:SurvivorRatio Eden:Survivor 比例。
  • -XX:+PrintGCDetails 打印 GC 日志。
  • -XX:+UseG1GC 指定垃圾收集器。
  • -XX:MaxMetaspaceSize 元空间大小。

14. 类加载的双亲委派机制?

  • 过程:加载请求先交给父加载器,如果父加载器无法加载,再由子加载器加载。

15. 双亲委派的好处?

  • 避免类重复加载。
  • 保证核心类(如 java.lang.String)不会被篡改。

16. 类加载器的种类?

  • Bootstrap ClassLoader:加载核心类库。
  • Extension ClassLoader:加载扩展类。
  • Application ClassLoader:加载用户应用类。
  • 自定义 ClassLoader:用户自己扩展。

17. OOM 的常见类型?

  • Java heap space:堆溢出。
  • GC overhead limit exceeded:GC 回收无效。
  • Metaspace:元空间溢出。
  • Direct buffer memory:直接内存溢出。
  • Unable to create new native thread:线程数过多。

18. 内存泄漏和内存溢出的区别?

  • 内存泄漏:不再使用的对象仍然被引用,不能被 GC 回收。
  • 内存溢出:内存不足,申请不到更多空间。

19. finalize() 方法的作用?

  • 对象被 GC 前执行一次清理逻辑。
  • 缺点:执行不确定、性能差,JDK9 开始废弃,推荐使用 try-with-resources/AutoCloseable

20. Java 内存模型(JMM)的核心内容?

  • 定义了多线程如何共享内存。
  • 关键点:
    • 主内存:存放共享变量。
    • 工作内存:每个线程的本地副本。
    • happens-before 规则:保证可见性、有序性。
    • volatile、synchronized、final 提供内存语义。

七、泛型、注解、反射


1. 什么是泛型?

  • 定义:泛型(Generic)是 参数化类型,让类、接口、方法可以操作不同类型的数据,而不用编写重复代码。

  • 好处

    1. 类型安全(编译期检查,避免 ClassCastException)。
    2. 代码复用(相同逻辑可适配多种类型)。
    3. 可读性高(类型明确)。
  • 例子

    1
    2
    List<String> list = new ArrayList<>();
    list.add("abc"); // 编译期检查类型

2. 泛型的类型擦除机制?

  • 原理:Java 的泛型是 伪泛型,只在 编译期有效,编译后泛型信息被擦除(Type Erasure)。

  • 结果

    • List<String>List<Integer> 在运行时是同一个类型:List
    • 泛型方法的类型参数会被擦除为 上界(extends)或 Object
  • 例子

    1
    2
    3
    List<String> l1 = new ArrayList<>();
    List<Integer> l2 = new ArrayList<>();
    System.out.println(l1.getClass() == l2.getClass()); // true

3. 泛型通配符 ? extends 和 ? super 的区别?

  • ? extends T:表示类型的 上界,接受 T 及其子类。
    • 适合 生产者(Producer),只读,不可写。
  • ? super T:表示类型的 下界,接受 T 及其父类。
    • 适合 消费者(Consumer),可以写入 T 及其子类对象。
  • 口诀:PECS(Producer Extends, Consumer Super)。

4. 注解的作用是什么?

  • 作用:注解(Annotation)是 元数据,为代码提供说明信息。
  • 分类
    • 编译期使用(如 @Override, @SuppressWarnings)。
    • 运行时使用(结合反射,驱动框架逻辑,如 @Autowired)。
  • 应用场景:框架开发(Spring、Hibernate)、编译器检查、文档生成等。

5. 元注解有哪些?

Java 提供了四个元注解(注解的注解):

  1. @Target —— 指定注解可用的位置(类、方法、字段、参数等)。
  2. @Retention —— 指定注解的生命周期(源码/编译期/运行时)。
  3. @Documented —— 是否包含在 javadoc 中。
  4. @Inherited —— 子类是否能继承父类的注解。

6. 自定义注解如何实现?

  • 定义

    1
    2
    3
    4
    5
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
    String value();
    }
  • 使用

    1
    2
    @MyAnnotation("test")
    public void foo() {}
  • 解析(反射):

    1
    2
    3
    Method m = clazz.getMethod("foo");
    MyAnnotation ann = m.getAnnotation(MyAnnotation.class);
    System.out.println(ann.value());

7. 反射的作用?

  • 定义:反射(Reflection)允许程序在 运行时 获取类的信息,并操作其属性、方法、构造器。
  • 作用
    • 框架(如 Spring、MyBatis)自动注入和配置。
    • 动态代理。
    • 动态加载类。

8. 反射的性能问题?

  • 原因
    • 反射调用方法比直接调用多了安全检查、方法查找,性能差 10~20 倍。
  • 优化
    • 使用 setAccessible(true) 关闭安全检查。
    • 使用 MethodHandle(JDK7+)。
    • 使用 LambdaMetafactory(JDK8+)做动态代理。

9. 反射能否访问私有字段?

  • ,需要 setAccessible(true)

    1
    2
    3
    Field field = clazz.getDeclaredField("name");
    field.setAccessible(true);
    field.set(obj, "newValue");
  • 注意:JDK 9 模块化后,跨模块访问可能会有 InaccessibleObjectException


10. 动态代理的两种实现方式?

  1. JDK 动态代理:基于 InvocationHandler + Proxy,只能代理 接口
  2. CGLIB 动态代理:基于 ASM 字节码生成,继承目标类,能代理

11. JDK 动态代理和 CGLIB 的区别?

  • JDK 动态代理
    • 代理接口。
    • JDK 自带,无需依赖。
    • 性能略低。
  • CGLIB
    • 代理类(生成子类)。
    • 不能代理 final 类/方法。
    • 性能更高(字节码生成)。

12. 反射如何创建对象?

  1. 使用 Class 的 newInstance()(已废弃):

    1
    Object obj = clazz.newInstance();
  2. 使用 Constructor

    1
    2
    Constructor<?> ctor = clazz.getConstructor(String.class);
    Object obj = ctor.newInstance("param");

13. Class.forName 和 ClassLoader.loadClass 的区别?

  • Class.forName:加载并 初始化 类(会执行静态代码块)。
  • ClassLoader.loadClass:仅加载,不初始化(延迟执行 <clinit>)。

14. 注解和反射结合能实现哪些功能?

  • IOC / DI(依赖注入,Spring 的 @Autowired)。
  • ORM 框架(MyBatis 的 @Mapper,JPA 的 @Entity)。
  • AOP(@Aspect)。
  • 配置驱动开发(@Configuration, @Value)。

15. 注解处理器(APT)的作用?

  • APT(Annotation Processing Tool):编译期扫描注解并生成代码。
  • 典型应用
    • Dagger2(依赖注入)。
    • ButterKnife(视图注入)。
    • Lombok(自动生成 getter/setter/toString 等)。

八、Java 8+ 新特性

  1. Lambda 表达式的作用?
  2. 函数式接口有哪些?
  3. Stream 流的常见操作?
  4. Optional 的作用?
  5. CompletableFuture 的作用?
  6. Java 8 接口的默认方法和静态方法?
  7. forEach 和传统 for 的区别?
  8. parallelStream 的原理?
  9. Java 9 模块化系统(Jigsaw)是什么?
  10. Java 14 的 switch 表达式增强?
  11. Java 16 的 record 特性?
  12. Java 17 的 sealed class 特性?
  13. var 关键字是什么?
  14. ZGC 的特性?
  15. Project Loom 的虚拟线程是什么?

九、IO


一、Java IO 基础与流(1–20)


1. Java IO 的总体体系结构是什么?

  • IO 的两大基类
    • 字节流:InputStreamOutputStream(处理二进制数据)。
    • 字符流:ReaderWriter(处理文本字符)。
  • 按功能划分
    • 节点流(直接对接数据源,如 FileInputStreamFileReader)。
    • 处理流(包装其他流,如 BufferedReaderDataInputStream)。
  • 核心设计模式:装饰器模式(Decorator Pattern),通过层层包装增强功能。

2. InputStream / OutputStream 的常见子类有哪些?

  • InputStreamFileInputStreamBufferedInputStreamDataInputStreamObjectInputStreamByteArrayInputStream
  • OutputStreamFileOutputStreamBufferedOutputStreamDataOutputStreamObjectOutputStreamByteArrayOutputStream

3. Reader / Writer 的常见子类有哪些?

  • ReaderFileReaderBufferedReaderInputStreamReaderCharArrayReaderStringReader
  • WriterFileWriterBufferedWriterOutputStreamWriterCharArrayWriterStringWriter

4. 字节流与字符流的本质区别和使用场景?

  • 字节流(8bit):面向二进制数据,如图片、音视频、压缩文件。
  • 字符流(16bit,基于 Unicode):面向文本,自动处理字符编码。
  • 联系:字符流常常通过 InputStreamReader/OutputStreamWriter 包装字节流并指定编码。

5. 为什么要有缓冲流?

  • 原因:文件 IO 属于系统调用,每次读写代价高。
  • 作用:减少磁盘交互次数,提高性能。
  • 例子BufferedReader.readLine() 能按行读取,效率远高于 FileReader.read()

6. flush() 与 close() 的区别?

  • flush():将内存缓冲区数据立刻写入目标(但不关闭流)。
  • close():先执行 flush(),然后释放底层资源。
  • 注意点
    • 若忘记 flush(),可能导致部分数据未写入文件。
    • close() 后再调用 write() 会抛异常。

7. try-with-resources 的工作原理?

  • 语法糖try (Resource r = ...) {},要求资源实现 AutoCloseable
  • 原理:编译器自动生成 finally { r.close() }
  • suppressed exceptions:如果 tryclose() 都抛异常,关闭时的异常会被标记为 suppressed,可通过 Throwable.getSuppressed() 获取。

8. 装饰器模式在 Java IO 中如何体现?

  • 例子

    1
    2
    3
    Reader reader = new BufferedReader(
    new InputStreamReader(
    new FileInputStream("a.txt"), StandardCharsets.UTF_8));
  • 本质:每层流只关心自己的功能(缓冲、解码、行读取),通过链式组合增强能力。


9. File 类的能力与限制?

  • 能做的:文件/目录的元数据操作(路径、存在性、权限、大小、修改时间等)。
  • 不能做的:不支持读写文件内容,需结合 InputStream/OutputStream

10. RandomAccessFile 的用途?

  • 功能:支持随机读写,内部有一个文件指针,可 seek() 到任意位置读写。
  • 典型场景:日志文件、数据库存储文件。
  • 并发问题:不是线程安全的,多线程写需加锁。

11. FileInputStream 与 FileReader 的区别?

  • FileInputStream:字节流,直接读二进制。
  • FileReader:字符流,内部用 InputStreamReader 并默认使用平台编码。
  • 使用建议
    • 非文本 → FileInputStream
    • 文本(需编码处理) → FileReader(但最好用 InputStreamReader 显式指定编码)。

12. 常见文件拷贝方式性能对比

  1. 裸 read/write:逐字节拷贝,最慢。
  2. 带缓冲BufferedInputStream/BufferedOutputStream,性能提升 5~10 倍。
  3. NIO FileChannel.transferTo():利用零拷贝,性能最高。

13. 如何安全处理字符编码?

  • 方式:使用 InputStreamReader/OutputStreamWriter 并显式指定编码。

  • 例子

    1
    new InputStreamReader(new FileInputStream("a.txt"), StandardCharsets.UTF_8);

14. 字符编码常见坑?

  • UTF-8 vs GBK 混用:解码错误,出现乱码。
  • BOM(Byte Order Mark):部分 UTF-8 文件前 3 字节 BOM 可能导致解析失败。
  • 截断:写入时按字节截断 UTF-8 多字节字符 → 乱码。
  • 解决:统一编码(UTF-8),避免隐式编码。

15. 文件追加(append)方式?

  • 实现

    1
    new FileOutputStream("a.txt", true);
  • 原子性问题:多线程写入需加锁,否则可能交叉覆盖。


16. 如何判断并处理文件状态?

  • 存在性file.exists()
  • 权限file.canRead(), file.canWrite()
  • 占用:只能通过打开尝试,若被占用会抛异常。

17. File.mkdirs() 与 mkdir() 区别?

  • mkdir():只创建当前目录,父目录不存在则失败。
  • mkdirs():会递归创建所有不存在的父目录。
  • 返回值:成功返回 true,否则 false

18. 临时文件(createTempFile)的用途?

  • 方法File.createTempFile("prefix", ".tmp")
  • 特点:自动生成唯一文件名。
  • 删除策略file.deleteOnExit() JVM 退出时删除,但依赖于正常退出。

19. 文件的读写锁与并发访问?

  • Java NIOFileChannel.lock() 提供文件锁(独占锁、共享锁)。
  • 注意:文件锁是 OS 级别,跨进程生效;性能开销较大,不适合频繁操作。

20. 流关闭异常处理最佳实践

  • 传统写法:try-finally 手动关闭流。
  • 推荐:try-with-resources,避免遗漏关闭。
  • 关闭异常:只 log,不影响主要业务异常处理。

二、Java IO 设计与模式(21–40)

  1. InputStream/OutputStream 的装饰器链常见组合(Buffered -> GZIP -> Cipher 等)。
  2. PushbackInputStream、SequenceInputStream 的作用与应用场景。
  3. PipedInputStream / PipedOutputStream(管道流)的使用与线程注意点。
  4. FilterInputStream/FilterOutputStream 的设计与扩展。
  5. IO 与异常处理:IOException 的常见子类与处理策略。
  6. 流复制的常见实现模板(模板方法式代码)。
  7. NIO 引入前的 IO 局限性(可扩展性、线程模型)。
  8. 如何实现一个带超时的读操作(Socket/Channel)?
  9. 流式处理与内存友好型处理(流式处理大文件)实践。
  10. Base64 编解码在流中的高效集成方法。
  11. 加密/解密流(CipherInputStream/CipherOutputStream)如何正确关闭?
  12. GZIPInputStream/GZIPOutputStream 的压缩流使用注意事项。
  13. ObjectInputStream/ObjectOutputStream 的工作机制(类元数据、句柄表)。
  14. 如何实现跨语言的序列化兼容(JSON/Protobuf/Avro)?
  15. 实现自定义 InputStream 子类时需要注意什么(read 方法语义)?
  16. 设计用于日志写入的高吞吐 IO 模式(异步批量写)。
  17. 流复制时如何统计速率与进度(带进度回调)?
  18. 如何安全处理二进制文件(流边界、magic header)?
  19. 大对象/大数组写入流时的内存优化策略(分块、流化)。
  20. 如何实现可重入/可恢复的断点续传文件写入?

三、文件系统、锁与操作(41–60)

  1. Java 中文件锁 FileLock 的类型(共享/独占)及实现原理。
  2. FileLock 的局限性(跨 JVM、跨 OS 行为差异)。
  3. 文件描述符泄露的常见原因与定位方法。
  4. 文件句柄上限(ulimit)对 Java 服务的影响与排查。
  5. 硬链接与软链接在 Java 中如何区分与操作?
  6. MappedByteBuffer(内存映射文件)的优劣与风险(内存回收、文件锁)。
  7. 大文件分片读取与并发合并策略。
  8. 如何高效统计大日志文件中某条件的行数(分块 + 并行)?
  9. 文件系统缓存(PageCache)对读写性能的影响机制。
  10. fsync / FileDescriptor.sync 在持久化保障上的作用。
  11. 文件顺序写与随机写的性能差异与优化建议。
  12. 磁盘类型(SSD vs HDD)对 IO 策略的影响。
  13. 原子重命名(renameTo/Files.move)的跨平台差异。
  14. 文件监控(WatchService)的实现限制与替代方案。
  15. 处理日志切割(rotation)时的文件句柄管理策略。
  16. 如何实现零停机部署中对文件的平滑迁移?
  17. 软删除(标记删除)与物理删除的 IO 考量。
  18. 备份/快照策略对 IO 的影响(冷备 vs 热备)。
  19. 大文件校验(MD5/SHA)在流式处理中的实现。
  20. 文件系统一致性问题(写入后立即读取到不同步)如何诊断。

四、序列化与反序列化(61–75)

  1. Java 原生序列化(Serializable)的机制和对象写入格式。
  2. serialVersionUID 的作用、自动生成与兼容性策略。
  3. transient 字段、static 字段在序列化中的处理。
  4. Externalizable 与 Serializable 的区别与使用场景。
  5. Java 序列化的安全风险(反序列化漏洞)与防护措施。
  6. 高性能二进制序列化方案对比:Kryo、Protostuff、Protobuf、Avro。
  7. JSON 和二进制序列化的权衡(可读性 vs 性能/大小)。
  8. 如何实现可演化的序列化协议(向前/向后兼容)?
  9. 对象图序列化时循环引用的处理(句柄机制)。
  10. 对象序列化性能调优要点(缓冲、对象重用)。
  11. 在分布式系统中如何管理序列化策略(跨服务版本)?
  12. 自定义序列化(writeObject/readObject)常见陷阱。
  13. 序列化时如何处理类加载器问题?
  14. 大对象序列化时的内存与 GC 风险如何降低?
  15. 使用序列化作为缓存(Redis/Memcached)时的注意事项。

五、NIO 深入:Buffer / Channel / Selector(76–100)


1. NIO 三大核心(Buffer、Channel、Selector)

  • Buffer:数据容器,所有读写操作都要通过它完成。
  • Channel:通道,连接数据源与 Buffer,支持双向读写。
  • Selector:多路复用器,检测多个 Channel 的事件(读、写、连接等),实现非阻塞 IO。

2. ByteBuffer 的 position / limit / capacity

  • capacity:缓冲区的总容量,创建时确定。
  • position:当前读写指针,表示下一个操作的位置。
  • limit:可操作的边界,写模式下=capacity,读模式下=写入的数据量。

3. flip(), clear(), rewind(), compact()

  • flip():切换写 → 读模式(limit=position,position=0)。
  • clear():清空缓冲,准备写(position=0,limit=capacity)。
  • rewind():重新读(position=0,limit 不变)。
  • compact():未读数据前移,position 指向未读数据末尾。

4. DirectByteBuffer vs HeapByteBuffer

  • HeapByteBuffer:数据在 JVM 堆内存,GC 管理,拷贝到内核态多一份。
  • DirectByteBuffer:数据在堆外内存,IO 时零拷贝,性能更高,但分配释放代价大。

5. ByteBuffer slice() / asReadOnlyBuffer()

  • slice():共享底层数据,新的缓冲区 position=0,limit=剩余。
  • asReadOnlyBuffer():生成只读视图,修改会抛 ReadOnlyBufferException

6. Scatter/Gather IO

  • Scatter Read:一个 Channel → 多个 Buffer(适合固定头+体)。
  • Gather Write:多个 Buffer → 一个 Channel(避免拼接)。

7. FileChannel 的关键方法

  • position():获取/设置文件指针。
  • size():返回文件大小。
  • truncate(long size):截断文件。
  • force(boolean metaData):强制写入磁盘。

8. MappedByteBuffer(内存映射)

  • 创建FileChannel.map(mode, position, size)
  • 优点:直接操作内存映射,效率高。
  • 释放:JDK 无直接 API,通常通过反射 Cleaner.clean(),需注意内存泄露。

9. Channel vs Stream

  • Stream:单向、阻塞。
  • Channel:双向、可非阻塞,可和 Selector 配合。

10. Selector 工作流程

  1. 注册channel.register(selector, ops)
  2. 选择selector.select(timeout)
  3. 遍历selectedKeys() 遍历处理
  4. 取消:调用 key.cancel()

11. Selector 底层实现

  • Linux:epoll
  • Windows:select
  • macOS:kqueue
    影响性能和可扩展性(epoll 适合高并发)。

12. 非阻塞 Channel 的 read/write 返回值

  • read

    • 0:读取字节数

    • 0:无数据

    • -1:EOF(关闭)

  • write

    • 0:写入字节数

    • 0:内核缓冲区满


13. 粘包/拆包问题

原因:TCP 流式传输无边界。
解决策略:

  • 固定长度(定长协议)
  • 分隔符(如 \n
  • 长度前置(常见)

14. 超时断开 & 心跳检测

  • 超时断开:在 select(timeout) 中检测长时间无读写的连接。
  • 心跳检测:客户端/服务端定期发送 PING,无响应则断开。

15. Pipe 在 NIO 中的作用

  • 单向通信(写端 → 读端),常用于线程间通信。
  • 但性能不如 BlockingQueue。

16. 多 Selector / 多 Reactor

  • 单 Reactor:一个线程负责 Selector。
  • 多 Reactor:主 Reactor 处理 accept,子 Reactor 处理 read/write。
  • 多 Selector:线程池分担 Channel 负载。

17. ByteBuffer 多线程问题

  • 不是线程安全的
  • 解决:每个线程独立 Buffer,或外部加锁。

18. DirectByteBuffer 避免泄露

  • 使用 ByteBuffer.allocateDirect() 代价大。
  • 建议:使用 -XX:+UseLargePages、池化管理(Netty 的 PooledByteBufAllocator)。

19. Selector selectedKeys 正确用法

  • 遍历 selectedKeys()
  • 处理完一个 key → iterator.remove()
  • 否则下次循环仍会触发。

20. NIO 高效读写循环模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while (true) {
selector.select();
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int n = sc.read(buf);
if (n == -1) sc.close();
else { buf.flip(); sc.write(buf); }
}
}
}

21. transferTo/transferFrom(零拷贝)

  • 直接在内核空间完成文件复制(减少用户态拷贝)。
  • 局限:Windows 一次传输大小有限,需循环。

22. FileChannel 高效复制

1
2
3
4
try (FileChannel in = new FileInputStream(src).getChannel();
FileChannel out = new FileOutputStream(dest).getChannel()) {
in.transferTo(0, in.size(), out);
}

23. 高并发服务器瓶颈

  • 单线程 Selector 性能不足。
  • Buffer/Direct 内存泄露。
  • 大量小包(TCP 粘拆包)。
  • epoll 空轮询 bug(CPU 飙高)。

24. Windows vs Linux

  • Linux:epoll,可伸缩到百万连接。
  • Windows:select,fd 数量受限,扩展性差。

25. 简单 Echo Server 思路

  1. 创建 Selector
  2. 注册 ServerSocketChannel
  3. 循环 select()
  4. 处理 accept → 注册 SocketChannel
  5. 处理 read → 回写数据

⚡总结:NIO 的核心是 缓冲区管理(ByteBuffer)、多路复用(Selector)、零拷贝优化(FileChannel.transferTo/MappedByteBuffer)。
在高并发系统里(如 Netty),这些 API 会结合线程模型和内存池进一步优化。


六、Socket / TCP / UDP / WebSocket(101–140)

  1. Socket 的基本概念:端点、三元组/四元组(IP:port + peer)。
  2. Java 中 Socket、ServerSocket、DatagramSocket 的主要 API 区别。
  3. TCP 与 UDP 的核心差异(可靠性、有序性、连接性)。
  4. TCP 三次握手(SYN、SYN-ACK、ACK)与四次挥手流程详解。
  5. TIME_WAIT、CLOSE_WAIT、FIN_WAIT1/2 等 TCP 状态含义与产生原因。
  6. 半开连接(half-open)是什么,如何检测与恢复?
  7. TCP 的流量控制(窗口)与拥塞控制(慢启动、拥塞避免、快重传、快恢复)基础。
  8. Nagle 算法(TCP_NODELAY)的原理和在延迟场景下的影响。
  9. TCP 延迟确认(delayed ACK)对交互型应用的影响。
  10. Socket 选项 SO_TIMEOUT、SO_KEEPALIVE、SO_REUSEADDR、SO_REUSEPORT、SO_LINGER 含义与使用场景。
  11. backlog 参数(ServerSocket 构造或 listen)与 accept 队列(syn, accept 队列)区别。
  12. ephemeral port(短暂端口)与端口耗尽问题及解决办法。
  13. SYN flood 攻击的原理和防护(SYN cookies、firewall)。
  14. TCP 快速打开(TCP Fast Open)是什么,有何优劣?(简述)
  15. MTU、MSS 与 IP 分片对传输的影响与诊断方法。
  16. UDP 的组播(Multicast)与广播(Broadcast)机制与 Java 支持(MulticastSocket)。
  17. UDP 丢包、乱序、包大小限制(最佳实践)。
  18. UDP 穿透 NAT(STUN/ICE)的基本原理。
  19. 如何在 Java 中实现高性能 UDP 服务器?(NIO + DatagramChannel)
  20. WebSocket 协议基础(握手、帧格式、ping/pong、close)。
  21. Java 实现 WebSocket 的常见库(javax.websocket、Netty websocket、Undertow)。
  22. TLS over TCP(HTTPS)的握手流程要点(证书验证、对称密钥协商)。
  23. 使用 SSLSocket / SSLServerSocket 和 SSLEngine 的差别及适用场景。
  24. SSL/TLS 握手的重用(session resumption)与性能优化。
  25. 中间人攻击(MITM)与证书链、CA、信任根的角色。
  26. 如何在 Socket 程序中实现心跳、超时与断线重连?
  27. TCP Keepalive 与应用层心跳的区别和协同使用。
  28. 如何通过抓包(tcpdump/wireshark)诊断 Socket 连接问题?
  29. Socket 的非阻塞 accept/read/write 实现注意点(资源/异步安全)。
  30. 如何避免 TCP 粘包/拆包在 Socket 原生编程中的影响(流式协议设计)?
  31. 实战:实现一个带长度前缀的 TCP 协议的 Java 服务端/客户端(思路)。
  32. 如何优雅处理大量短连接的场景?(连接池、HTTP keep-alive)
  33. Socket 端口复用(SO_REUSEADDR vs SO_REUSEPORT)在负载均衡中的用法。
  34. 多路复用(select/poll/epoll)与 socket 大连接数的处理。
  35. 网络字节序(big-endian)与数据编解码注意点。
  36. 使用 TCP_NODELAY(禁用 Nagle)时的 CPU/网络 trade-off。
  37. 如何在 Java 中做到零拷贝文件传输(Socket + FileChannel.transferTo)?
  38. Socket 关闭流程中遇到阻塞(SO_LINGER)的处理办法。
  39. 如何防止或检测 “socket half-closed” 的资源泄漏?
  40. 实战题:用 BIO、NIO、Netty 各实现一个简易的聊天服务器,比较性能与代码复杂度(思路要点)。

七、异步 IO、IO 模型与高阶模式(141–150)

  1. IO 模型分类:同步阻塞、同步非阻塞、IO 多路复用、信号驱动、异步(AIO)。
  2. Reactor 模式与 Proactor 模式的原理与区别。
  3. Java AIO(AsynchronousChannel、AsynchronousSocketChannel)API 介绍。
  4. CompletionHandler 与 Future 风格的异步回调比较。
  5. 高并发下选择 NIO 还是 AIO 的实际考量(实现复杂度、平台支持)。
  6. 事件驱动系统的 back-pressure 设计(流控、速率限制)。
  7. 线程模型设计:acceptors / io workers / business workers 的权衡。
  8. RCU、无锁队列在高并发 IO 框架中的应用场景。
  9. 如何在应用层实现请求队列与排队策略来缓解突发流量?
  10. 实战题:设计一个支持百万连接的服务器架构(核心组件与 IO 策略)。

八、零拷贝、内核与性能调优(151–160)

  1. 零拷贝(zero-copy)概念和常见实现(sendfile, mmap, splice)。
  2. FileChannel.transferTo/transferFrom 在 Linux 下如何利用 sendfile 实现零拷贝?
  3. mmap(内存映射文件)的实现原理与 GC /回收问题。
  4. DMA(Direct Memory Access)在零拷贝中的作用与限制。
  5. 内核态 / 用户态拷贝次数与零拷贝带来的减少效果。
  6. TCP 窗口、拥塞控制调优参数(net.ipv4.tcp_* 系列常见项)。
  7. 系统级调优:文件描述符上限(ulimit -n)、somaxconn、backlog、epoll_limits 等。
  8. 高并发网络服务的监控指标(fd 使用、accept latency、context switch、cpu steal)。
  9. 性能诊断工具与流程:iostat, vmstat, sar, perf, tcpdump, ss, strace。
  10. 实战题:如何定位并修复一个高并发服务器的 accept 阻塞 / 连接丢失 问题(诊断步骤)。

Java 基础面试题

一、语言特性 & 基础语法


1. Java 中的 == 和 equals() 有什么区别?如何正确重写 equals()?

  • ==
    • 对于基本数据类型:比较值是否相等。
    • 对于引用类型:比较两个引用是否指向同一块内存地址(即是否是同一个对象)。
  • equals()
    • 定义在 Object 类中,默认实现也是 ==
    • 一般会被类(如 StringInteger 等)重写,用来比较“内容”是否相同,而不是内存地址。

👉 正确重写 equals() 要满足 自反性、对称性、传递性、一致性,并且和 null 比较时返回 false
典型实现步骤:

  1. 判断 this == obj,若相等直接返回 true
  2. 判断 obj 是否为同类实例。
  3. 强制类型转换后逐个比较关键字段。

2. 为什么重写 equals() 时必须重写 hashCode()?不重写会怎样?

  • 原因:因为 HashMapHashSet 等基于哈希表的集合使用 hashCode() 来决定元素存放位置,然后再用 equals() 判断元素是否相同。
  • 契约(JDK 文档规定):
    • 如果两个对象 equals() 相等,那么它们的 hashCode() 必须相等。
    • 反之,hashCode() 相等并不一定意味着 equals() 相等。
  • 不重写会怎样
    • 两个内容相同的对象可能 equals() 为 true,但 hashCode() 不同,导致放进 HashSet 时被当作两个对象存储,违背集合“不重复”的原则。

3. Object 类有哪些方法?哪些方法经常需要重写?

  • 常用方法
    • equals():对象内容比较。
    • hashCode():哈希值,配合 equals
    • toString():对象的字符串表示。
    • clone():对象克隆(浅拷贝,需实现 Cloneable)。
    • finalize():对象被 GC 前调用(已不推荐使用)。
  • 同步方法
    • wait() / notify() / notifyAll():线程通信。
  • 类相关方法
    • getClass():获取对象的运行时类型。

👉 经常需要重写的:equals()hashCode()toString()


4. final、finally、finalize() 有什么区别?

  • final:关键字
    • 修饰类:类不能被继承。
    • 修饰方法:方法不能被重写。
    • 修饰变量:变量成为常量,只能赋值一次。
  • finally:关键字
    • 用于 try-catch-finally,无论是否有异常,finally 块中的代码一定会执行(除非 System.exit(0))。
  • finalize():方法
    • Object 类定义,在 GC 前调用,常用于释放非托管资源。
    • 已被标记为废弃(Java 9 开始),因为不可预测、不可靠。

5. Java 中的 instanceof 关键字是如何实现的?

  • 作用:判断某个对象是否为某个类或其子类的实例。
  • 实现原理
    • JVM 在对象头中存储了对象所属的类的元数据指针。
    • instanceof 实际是检查该类元数据是否是指定类或其子类。
    • 编译器会将 obj instanceof A 转换为字节码 instanceof 指令,由 JVM 在运行时判断。

6. 接口和抽象类的区别?接口能不能有构造函数?

  • 接口
    • 只能定义方法(Java 8 之后可有 defaultstatic 方法)。
    • 不能有构造函数(因为接口不能直接实例化)。
    • 可以实现 多继承(一个类可以实现多个接口)。
  • 抽象类
    • 可以有普通方法、抽象方法、成员变量。
    • 可以有构造函数(但不能直接 new,用于子类构造时调用)。
    • 只能单继承。

7. 接口可以多继承吗?抽象类可以实现接口吗?

  • 接口可以多继承interface A extends B, C {}
  • 抽象类可以实现接口:可以选择性实现接口的方法,未实现的方法仍保持 abstract,由子类实现。

8. default 方法和 static 方法在接口中的意义是什么?

  • default 方法
    • Java 8 引入,接口中可以有默认实现的方法。
    • 解决接口扩展的兼容性问题(给老接口新增方法不会导致所有实现类报错)。
  • static 方法
    • 属于接口本身,而不是实现类。
    • 用于提供工具方法或公共逻辑(类似 Collections 的工具类方法)。

9. 什么是内部类?分为哪几种?

  • 内部类:定义在类中的类,可以访问外部类的成员。
  • 种类
    1. 成员内部类(非静态内部类)
      • 依赖外部类实例,不能有 static 成员。
    2. 静态内部类
      • 类似普通类,但作用域在外部类内,可以有 static 成员。
    3. 局部内部类
      • 定义在方法体或代码块中,只能在该范围内使用。
    4. 匿名内部类
      • 没有名字,通常在需要“临时实现接口或继承类”的场景使用。

10. 匿名内部类和 Lambda 表达式的区别?

  • 匿名内部类
    • 实现接口/继承抽象类的 具体匿名类对象
    • 可以有多个方法,但通常只重写需要的方法。
    • 生成 .class 文件(额外开销)。
  • Lambda 表达式
    • 仅能用于 函数式接口(只有一个抽象方法的接口)。
    • 更简洁,本质是语法糖,编译后生成 invokedynamic 字节码指令,调用运行时生成的函数对象。
    • 不会创建新的 .class 文件,更轻量。

二、数据类型 & 内存


1. Java 中的基本数据类型和包装类型有哪些区别?

  • 基本数据类型(primitive):byte, short, int, long, float, double, char, boolean
    • 存储在栈上(或寄存器),效率高,空间占用固定。
    • 不能为 null,默认值固定(如 int 默认 0boolean 默认 false)。
  • 包装类型(wrapper):Byte, Short, Integer, Long, Float, Double, Character, Boolean
    • 是对象,存储在堆中。
    • 可以为 null,有丰富的 API。
    • 可以参与集合类(如 ArrayList 只能存对象)。
  • 自动装箱/拆箱:Java 会在基本类型和包装类之间自动转换。

2. 为什么 Integer 有缓存机制?范围是多少?

  • 原因
    • 为了避免频繁创建对象,提高性能。
    • 小整数使用频率高(如循环、下标),所以缓存常用的整数。
  • 范围-128 ~ 127
  • 实现
    • IntegerCache 静态内部类在类加载时初始化缓存数组。
    • Integer.valueOf(int i) 方法会先查缓存,再决定是否 new 对象。

3. 为什么 new Integer(127) == new Integer(127) 为 false,而 Integer.valueOf(127) == Integer.valueOf(127) 为 true?

  • new Integer(127):每次都会创建新对象,内存地址不同,所以 == 为 false。
  • Integer.valueOf(127):会查缓存,127-128~127 范围内,返回同一个对象引用,所以 == 为 true。

👉 总结

  • == 比较的是引用地址。
  • equals() 才是比较数值。

4. NaN 和 Infinity 在 Java 中如何表示?

  • NaN(Not a Number)
    • 0.0 / 0.0Math.sqrt(-1)
    • 特点:NaN != NaN,要用 Double.isNaN() 检测。
  • Infinity(正/负无穷大)
    • 1.0 / 0.0 = Infinity-1.0 / 0.0 = -Infinity
    • Double.isInfinite() 检测。

5. 为什么浮点数计算有精度问题?如何避免?

  • 原因
    • 浮点数采用 IEEE 754 二进制浮点表示,大多数十进制小数(如 0.1)不能精确表示,只能存储近似值。
    • 计算时会累积误差。
  • 避免方法
    • 使用 BigDecimal(推荐)。
    • 或使用整数替代(如金额用分而不是元)。

6. BigDecimal 为什么能解决浮点数精度问题?

  • 原理
    • 内部使用 BigInteger + 精度标识 scale 表示任意精度的十进制数。
    • 不依赖二进制浮点存储,而是基于字符串或整数进行精确计算。
  • 注意
    • 不能用 new BigDecimal(double)(会带入二进制误差)。
    • 推荐 BigDecimal.valueOf(double)new BigDecimal(String)

7. Java 中的字符集默认是什么?在不同平台上会不一样吗?

  • Java 内部使用 UTF-16(Unicode) 存储 charString
  • 默认字符集(Charset.defaultCharset())会随平台不同而不同:
    • Windows(中文环境):GBKGB2312
    • Linux / macOS:通常是 UTF-8
      👉 这会影响文件读写、网络传输等。

8. String、StringBuilder、StringBuffer 区别?线程安全性?

  • String
    • 不可变类(底层 final char[]),每次拼接都会生成新对象。
  • StringBuilder
    • 可变字符序列(底层 char[]),高效,非线程安全
  • StringBuffer
    • 可变字符序列,方法加了 synchronized线程安全,效率比 StringBuilder 低。

👉 单线程推荐 StringBuilder,多线程推荐 StringBuffer


9. 为什么 String 是不可变的?背后实现细节?

  • 原因
    1. 安全性:如网络请求 URL、文件路径、ClassLoader 名称等,一旦创建不可修改,避免被篡改。
    2. 线程安全:不可变对象天然线程安全。
    3. 哈希值缓存String 缓存了 hashCode,不可变保证哈希值不会变化。
    4. 字符串常量池优化:不可变对象可被复用。
  • 实现
    • private final char[] value(JDK 9 以后改成 byte[] + coder,节省内存)。

10. intern() 方法的作用是什么?

  • 作用

    • 将字符串放入 字符串常量池,返回池中的引用。
    • 若池中已有等值字符串,则返回已有对象引用。
  • 意义

    • 减少内存占用,提高比较效率(常量池中的 String 可以直接用 == 比较)。
  • 例子

    1
    2
    3
    4
    String s1 = new String("abc");
    String s2 = s1.intern();
    String s3 = "abc";
    System.out.println(s2 == s3); // true

三、集合框架


1. ArrayList 和 Vector 的区别

特性 ArrayList Vector
线程安全 非线程安全 线程安全(方法加 synchronized)
扩容机制 默认 1.5 倍 默认 2 倍
性能 高(无锁) 低(同步开销)
用途 多线程外部不安全 适合多线程共享场景,但现在较少使用
面试拓展 如何在多线程下安全使用 ArrayList?可用 Collections.synchronizedList()CopyOnWriteArrayList

原理

  • 都基于动态数组实现
  • 添加元素时如果容量不够,会创建一个新的数组并复制原数组内容

2. 为什么 ArrayList 的扩容是 1.5 倍?

  • 目的:平衡 内存消耗扩容频率

  • 原理

    • 过小扩容:频繁创建新数组,复制开销大
    • 过大扩容:浪费内存
  • JDK 1.8 的扩容公式:

    1
    newCapacity = oldCapacity + (oldCapacity >> 1); // 相当于 1.5 倍
  • 面试点:

    • LinkedList 不需要扩容,因为链表动态增长
    • 如何自定义扩容策略?

3. LinkedList 是双向链表还是单向链表?

  • Java 中 LinkedList 是双向链表

  • 结构

    1
    2
    3
    4
    5
    Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    }
  • 优点

    • 支持快速插入/删除(O(1))
    • 支持从头尾遍历
  • 缺点

    • 随机访问慢(O(n))
    • 内存占用大(需要 prev 指针)

4. HashSet 底层实现

  • 底层使用 HashMap 实现
  • 原理
    • 每个元素作为 HashMap 的 key
    • value 使用一个固定的 Object 对象 PRESENT
  • 特点
    • 不允许重复
    • 无序存储
  • 性能
    • 插入、删除、查找均 O(1) 平均
  • 面试拓展:HashSet 和 LinkedHashSet 的区别?
    • LinkedHashSet 维护了插入顺序

5. TreeSet 和 TreeMap 的底层实现

  • TreeMap:基于 红黑树 实现
  • TreeSet:内部使用 TreeMap 存储元素
  • 特点
    • 自动排序(自然排序或 Comparator)
    • 查找、插入、删除 O(log n)
  • 红黑树特性
    • 自平衡二叉搜索树
    • 插入/删除保持平衡,通过旋转和染色操作

6. PriorityQueue 的底层数据结构

  • 基于堆实现(默认最小堆)

  • 特性

    • 插入 O(log n)
    • 取出最小值 O(log n)
  • 内部实现

    1
    private Object[] queue; // 动态数组存储堆
  • 应用

    • 定时任务调度
    • Dijkstra 算法
  • 面试拓展:如何实现最大堆?

    • 使用 Comparator 或覆写 compare 方法

7. ConcurrentSkipListMap 的底层原理

  • 基于跳表实现
  • 特点
    • 有序 Map,支持并发操作
    • 读操作无锁(lock-free),写操作使用 CAS/锁粒度较小
  • 跳表原理
    • 多层链表
    • 顶层链表间隔大
    • 查找、插入、删除 O(log n) 平均

8. CopyOnWriteArrayList 的写时复制

  • 原理
    • 每次写操作(add、remove)都会复制一份底层数组
    • 读操作不加锁,直接读取旧数组
  • 优点
    • 高度并发读性能
    • 避免 ConcurrentModificationException
  • 缺点
    • 写操作成本高(数组复制)
    • 内存开销大
  • 应用场景
    • 读多写少(如监听器列表、配置缓存)

9. EnumMap 和 EnumSet

  • EnumMap
    • Key 是枚举类型
    • 底层使用数组存储,效率高
  • EnumSet
    • 元素为枚举
    • 底层使用 位向量(bit vector) 存储
  • 特点
    • 高效,占用内存小
    • 遍历速度快
  • 应用
    • 状态机、标志位集合

10. WeakHashMap 和 HashMap 的区别

特性 HashMap WeakHashMap
Key 引用类型 强引用 弱引用
GC 行为 不受垃圾回收影响 Key 被回收时自动删除 Entry
用途 普通 Map 缓存、内存敏感对象的映射
面试点 WeakHashMap 如何防止内存泄漏?

原理

  • WeakHashMap 的 key 是 WeakReference
  • 当 key 不再被外部强引用时,垃圾回收器回收,并通过 ReferenceQueue 清理 Entry

四、异常 & 错误处理


1. Java 中的异常分为哪两大类?

  • Error(错误)
    • 系统级错误,通常无法恢复
    • 例子:OutOfMemoryErrorStackOverflowError
    • 不建议捕获
  • Exception(异常)
    • 程序可以捕获、处理
    • 子类:
      1. 受检异常(CheckedException):必须 try/catch 或 throws 抛出
        • 例子:IOExceptionSQLException
      2. 非受检异常(UncheckedException / RuntimeException):可选择捕获
        • 例子:NullPointerExceptionIllegalArgumentException

面试点

  • Checked vs Unchecked 异常的设计初衷:
    • Checked 异常强制处理,提高程序健壮性
    • RuntimeException 用于编程错误,不必强制捕获

2. Error 和 Exception 的区别

特性 Error Exception
是否可恢复 不可恢复 可恢复(程序可处理)
捕获建议 不建议捕获 建议捕获和处理
子类示例 OutOfMemoryError IOException, SQLException
使用场景 JVM 内部错误 应用程序异常处理

3. 受检异常和非受检异常区别

特性 CheckedException UncheckedException
编译器检查 编译器强制要求处理 编译器不要求
继承关系 Exception 除 RuntimeException RuntimeException 子类
使用场景 可恢复异常,如文件不存在 编程错误,如空指针

面试点

  • Checked 异常在 API 设计中可提示调用者
  • RuntimeException 多用于程序逻辑错误,防止过多 try/catch

4. throw 和 throws 的区别

特性 throw throws
位置 方法体内 方法声明上
用途 抛出具体异常实例 声明方法可能抛出的异常类型
个数限制 一次抛出一个 可声明多个异常
面试示例 throw new IOException() void read() throws IOException

5. try-with-resources 的底层原理

  • 语法

    1
    2
    3
    try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    // 使用资源
    }
  • 底层实现

    • 自动调用资源的 close() 方法

    • 资源必须实现 AutoCloseable 接口

    • 编译器将其转换为:

      1
      2
      3
      4
      5
      6
      BufferedReader br = new BufferedReader(new FileReader("file.txt"));
      try {
      // 使用资源
      } finally {
      if (br != null) br.close();
      }
  • 优势

    • 自动关闭资源,避免资源泄漏
    • 支持多个资源声明
  • 面试拓展:多资源关闭的异常抑制机制 (addSuppressed)


6. 自定义异常如何设计?继承哪个类

  • 继承 RuntimeException

    • 用于非受检异常(编程错误、业务逻辑异常)
  • 继承 Exception

    • 用于受检异常(需要调用者处理)
  • 实现建议

    1
    2
    3
    4
    5
    6
    7
    8
    public class MyBusinessException extends RuntimeException {
    public MyBusinessException(String message) {
    super(message);
    }
    public MyBusinessException(String message, Throwable cause) {
    super(message, cause);
    }
    }
  • 面试点

    • 自定义异常加上异常链(cause)
    • 规范异常信息,便于日志追踪

7. 为什么不建议捕获 Throwable?

  • Throwable 包含 Error 和 Exception
  • 捕获 Throwable 会:
    • 捕获系统级错误(OutOfMemoryError)
    • 破坏 JVM 正常工作
  • 最佳实践
    • 只捕获可恢复的 Exception 或 RuntimeException
    • 捕获 Throwable 仅用于日志收集或监控

8. 异常链(Exception Chaining)是什么

  • 概念

    • 在捕获一个异常时,将原异常封装在新异常中抛出
  • 实现

    1
    2
    3
    4
    5
    try {
    method();
    } catch (IOException e) {
    throw new MyBusinessException("读取文件失败", e);
    }
  • 优势

    • 保留原始异常信息
    • 便于调试和日志追踪

9. Java 8 之后对异常处理的改进

  • Lambda 表达式

    • 对受检异常的处理更加简洁,但需要特殊处理
    1
    2
    3
    4
    list.forEach(item -> {
    try { process(item); }
    catch(IOException e) { e.printStackTrace(); }
    });
  • Optional & Streams

    • 避免空指针异常
    • 可以在 map/filter 中处理异常
  • 面试拓展

    • 异常在并行 Stream 中如何传递
    • CompletableFuture 异常处理

10. 多线程环境下如何正确传递异常

  • 问题

    • 子线程异常不会直接抛给主线程
  • 解决方案

    1. Future / Callable

      1
      2
      Future<?> future = executor.submit(() -> { throw new RuntimeException("error"); });
      future.get(); // 会抛 ExecutionException
    2. Thread.UncaughtExceptionHandler

      1
      thread.setUncaughtExceptionHandler((t, e) -> log.error("线程异常", e));
    3. CompletableFuture.exceptionally

      1
      2
      CompletableFuture.runAsync(() -> { throw new RuntimeException(); })
      .exceptionally(ex -> { log.error(ex); return null; });
  • 面试拓展

    • 如何处理线程池中未捕获异常
    • 异步任务异常链追踪

五、JVM & 内存管理


1. Java 内存模型(JMM)是什么?

  • 定义
    • Java 内存模型(Java Memory Model,JMM)是 Java 虚拟机规范中定义的一套规范,用于描述 Java 程序中各个线程如何访问共享内存以及如何保证可见性、原子性、有序性
  • 核心作用
    1. 解决多线程可见性问题
    2. 规定内存操作顺序(happens-before 原则)
    3. 定义变量访问原子性
  • 关键概念
    • 主内存(Main Memory):共享变量存储位置
    • 工作内存(Working Memory / Thread Local Cache):线程私有缓存
    • volatile、synchronized、final 的语义保证不同层次的可见性和原子性

2. JVM 内存分为哪几个区域?每个区域的作用是什么?

  1. 程序计数器(PC Register)
    • 每个线程私有
    • 保存当前线程执行的字节码指令地址
    • 支持线程切换和方法调用返回
  2. 虚拟机栈(Stack)
    • 每个线程私有
    • 存储局部变量、操作栈、动态链接信息、方法出口
    • 栈帧:方法执行的最小单位
    • 异常:StackOverflowError
  3. 本地方法栈(Native Method Stack)
    • JVM 调用本地方法(C/C++实现)使用
    • HotSpot 通常与虚拟机栈合并
  4. 堆(Heap)
    • 所有线程共享
    • 存储对象实例及数组
    • 垃圾收集的主要区域
    • 子区域:
      • 年轻代(Young Generation):Eden + Survivor0 + Survivor1
      • 老年代(Old Generation)
      • 元空间(Metaspace / 方法区):存放类元信息
  5. 方法区(Metaspace / 以前称为 PermGen)
    • 存储类信息、常量池、静态变量
    • Metaspace 从本地内存分配(不再受 JVM 堆大小限制)
  6. 运行时常量池
    • 类加载时生成
    • 存放字面量、符号引用、字符串常量等

3. 为什么 String 常量池要放在堆里而不是方法区?

  • 历史原因
    • JDK 1.6 及以前:字符串常量池在永久代(PermGen),易导致 OOM
  • 现代实现
    • JDK 7+ 将常量池移到堆
  • 优势
    1. 垃圾回收可管理
    2. 内存分配灵活,避免方法区溢出
    3. 多线程访问安全性提升(与堆对象 GC 同步)

4. 什么是垃圾回收(GC)的可达性分析算法?

  • 可达性分析(Reachability Analysis)

    • GC Roots 开始,通过引用链判断对象是否可达
    • 不可达对象即为垃圾,可以回收
  • 示意

    1
    2
    3
    4
    GC Roots
    |
    v
    Object A --> Object B
    • 如果 Object B 无法通过 GC Roots 访问,则被回收

5. 哪些对象可以作为 GC Roots?

常见 GC Roots:

  1. 虚拟机栈中的引用(局部变量、方法参数)
  2. 静态变量(类静态成员)
  3. 常量引用(常量池引用)
  4. JNI 引用(本地方法栈中的引用)

6. 强引用、软引用、弱引用、虚引用的区别

类型 特点 GC 行为 面试应用
强引用(Strong Reference) 普通对象引用 不可回收 普通对象
软引用(Soft Reference) 通过 SoftReference 引用 内存不足时回收 缓存(Memory-sensitive cache)
弱引用(Weak Reference) WeakReference 引用 GC 时立即回收 ThreadLocal 清理、缓存
虚引用(Phantom Reference) PhantomReference 引用 仅做引用队列通知 对象回收前做清理、资源释放

7. GC 算法

  1. 标记-清除(Mark-Sweep)
    • 标记可达对象,清除未标记对象
    • 优点:简单
    • 缺点:产生碎片
  2. 标记-整理(Mark-Compact)
    • 标记可达对象
    • 将存活对象移动到一端
    • 优点:解决碎片问题
    • 缺点:移动对象成本高
  3. 复制算法(Copying)
    • 将内存分为两块
    • 将存活对象复制到另一块,清理旧块
    • 优点:分配快,整理快
    • 缺点:内存利用率低

8. 常见的垃圾收集器

垃圾收集器 适用场景 特点
Serial GC 单核 STW 串行,简单、高吞吐量低
Parallel GC 多核 并行回收年轻代,高吞吐量
CMS GC 多核 并发回收老年代,减少 STW
G1 GC 大堆 分区回收,低延迟,预测停顿
ZGC 超大堆 并发标记-清理,低延迟
Shenandoah 大堆 并发标记-整理,减少 STW

9. 什么是 Stop-The-World (STW)?

  • 定义:JVM 暂停所有应用线程执行,专门进行 GC
  • 原因
    • 防止对象被修改导致回收错误
  • 面试拓展
    • G1/ZGC/Shenandoah 尽量缩短 STW 时间
    • 并发 GC 与 STW 的折中

10. JVM 调优常见参数

参数 作用
-Xmx 最大堆大小
-Xms 初始堆大小
-Xmn 年轻代大小
-XX:SurvivorRatio Eden/Survivor 比例
-XX:+UseG1GC 使用 G1 GC
-XX:MaxGCPauseMillis 最大停顿时间
-XX:+PrintGCDetails 打印 GC 日志
-XX:+HeapDumpOnOutOfMemoryError OOM 时生成堆转储

面试拓展

  • 如何调优老年代和年轻代比例?
  • GC 日志分析示例:年轻代满、老年代晋升、STW 停顿

六、并发编程基础


1. Java 中的 volatile 关键字的底层实现原理

  • 作用
    1. 保证可见性:一个线程修改变量后,其他线程立即可见
    2. 禁止指令重排:保证顺序性
  • 原理
    • 底层通过 内存屏障(Memory Barrier / Fence) 实现
    • volatile 读操作:
      • 加载屏障(LoadLoad + LoadStore)
    • volatile 写操作:
      • 写屏障(StoreStore + StoreLoad)
  • 注意
    • 不能保证 原子性
    • 适合 状态标识、开关变量

2. synchronized 的实现原理

  • 作用
    • 保证 互斥访问
    • 保证 可见性
  • 底层机制
    • Java 对象头包含 Mark Word
    • 每个对象可以关联一个 Monitor
    • Monitor 用于管理线程的加锁、阻塞、唤醒
  • 锁升级流程
    1. 偏向锁(默认轻量级线程无竞争)
    2. 轻量级锁(CAS 竞争,膨胀前)
    3. 重量级锁(锁膨胀,阻塞其他线程)

3. 偏向锁、轻量级锁、重量级锁

锁类型 适用场景 实现方式 优缺点
偏向锁 单线程 在对象头标记线程 ID 减少无竞争开销
轻量级锁 多线程竞争轻量 CAS 自旋 + 栈记录锁 高效,避免阻塞
重量级锁 多线程竞争严重 Monitor + 阻塞 开销大,可能 STW

面试拓展

  • 如何通过 -XX:+PrintFlagsFinal 查看锁标志位
  • 偏向锁默认延迟 4 秒启用

4. 什么是 CAS 操作?ABA 问题如何解决?

  • CAS(Compare-And-Swap)

    • 原子操作
    • 比较内存值是否为预期值,如果是就更新
    1
    CAS(V, A, B)
  • ABA 问题

    • 值先从 A 改成 B,又改回 A
    • CAS 无法检测变化,可能出错
  • 解决方案

    1. 版本号机制(AtomicStampedReference)
    2. 加锁

5. 什么是线程安全?举例线程安全类

  • 定义:多个线程访问同一个对象时,不会破坏对象的状态,并保证正确性
  • 线程安全类示例
    • 集合类:Vector, ConcurrentHashMap, CopyOnWriteArrayList
    • 原子类:AtomicInteger, AtomicReference
    • 工具类:ThreadLocal, BlockingQueue

6. ThreadLocal 的底层原理与内存泄漏问题

  • 原理
    • 每个线程维护一个 ThreadLocalMap
    • key 是 ThreadLocal 对象
    • value 是对应的值
  • 内存泄漏原因
    • ThreadLocalMap 的 key 是 弱引用
    • 如果 ThreadLocal 对象被回收,但线程仍存活,value 仍然存在
  • 解决方法
    • 使用 remove() 方法
    • 避免长生命周期线程存储大量 ThreadLocal

7. 线程池的核心参数

参数 作用
corePoolSize 核心线程数,常驻线程
maximumPoolSize 最大线程数
keepAliveTime 非核心线程空闲存活时间
workQueue 等待队列,用于存放任务
threadFactory 线程创建工厂
RejectedExecutionHandler 拒绝策略

8. 什么是拒绝策略

  • 触发场景:线程池满了,队列满了
  • 常见策略
    1. AbortPolicy:抛异常,默认
    2. CallerRunsPolicy:由调用线程执行任务
    3. DiscardPolicy:直接丢弃任务
    4. DiscardOldestPolicy:丢弃队列中最旧任务

9. Executors 提供的常见线程池

线程池类型 特点 使用场景
FixedThreadPool 固定线程数 CPU密集型任务
CachedThreadPool 可扩容线程池 短生命周期任务
SingleThreadExecutor 单线程顺序执行 顺序任务
ScheduledThreadPool 支持定时/周期执行 定时任务

面试拓展

  • Executors.newFixedThreadPool 会使用 LinkedBlockingQueue
  • CachedThreadPool 使用 SynchronousQueue,任务直接交给线程

10. Future 和 CompletableFuture 区别

特性 Future CompletableFuture
异步 提交任务后可阻塞获取结果 可链式异步操作
API get() 阻塞 thenApply, thenAccept 等链式
异常处理 阻塞式,捕获 ExecutionException 支持 exceptionally/handle
组合任务 组合困难 支持 allOf / anyOf / thenCombine

面试点

  • CompletableFuture 适合多异步任务组合
  • FutureTask 可作为 Future 的实现类

七、编译 & 运行时


1. Java 的反射机制是什么?底层原理?

  • 概念

    • 反射机制允许在运行时 获取类的信息、实例化对象、访问字段和方法,而不需要在编译期知道具体类型
  • 常用 API

    1
    2
    3
    4
    Class<?> clazz = Class.forName("com.example.User");
    Object obj = clazz.newInstance();
    Method method = clazz.getMethod("sayHello", String.class);
    method.invoke(obj, "world");
  • 底层原理

    • JVM 中类对象 Class 对应内存中的元数据(方法表、字段表、常量池)
    • 通过 Native 方法 调用,访问类的元数据和对象实例
  • 面试拓展

    • 反射可访问私有字段/方法(setAccessible(true)
    • 反射适用于框架(Spring、MyBatis)

2. 为什么反射性能差?JDK 9 之后如何优化?

  • 性能差原因
    1. 每次访问方法/字段都要进行动态解析
    2. JVM 无法做内联优化(方法调用不是静态绑定)
    3. 需要检查权限、类型转换
  • 优化措施
    • JDK 7 及以后MethodHandleinvokeDynamic
    • JDK 9+:增强模块化(模块边界检查优化)
    • 热点编译器 JIT 可以对反射调用做内联

3. 什么是动态代理?JDK 动态代理和 CGLIB 区别?

  • 动态代理

    • 在运行时生成代理对象,拦截方法调用,增强行为
  • JDK 动态代理

    • 基于接口
    • 使用 Proxy.newProxyInstance
    • 通过 InvocationHandler.invoke 调用
  • CGLIB

    • 基于子类继承(无接口也可代理)
    • 使用 ASM 字节码生成子类
    • 注意 final 类/方法不能被代理
  • 对比

    特性 JDK 动态代理 CGLIB
    是否需要接口 必须 不需要
    原理 Proxy + InvocationHandler ASM 字节码生成子类
    性能 略慢 较快
    局限性 final 类/方法不可代理

4. 注解的底层原理是什么?运行时注解如何实现?

  • 概念
    • 注解是一种 元数据,用于给类、方法、字段等提供信息
  • 底层原理
    • 编译时生成 .class 文件中 RuntimeVisibleAnnotations 属性
    • JVM 通过 java.lang.reflect.AnnotatedElement 获取
  • 运行时注解
    • @Retention(RetentionPolicy.RUNTIME)
    • 通过反射 API 读取注解并执行逻辑
  • 应用场景
    • Spring 注解(@Autowired@Service
    • JPA 注解(@Entity@Column

5. ClassLoader 的双亲委派模型是什么?

  • 原理

    • 当类加载请求到来时,先委托父 ClassLoader 加载
    • 如果父 ClassLoader 加载失败,再由子 ClassLoader 加载
  • 优点

    1. 避免重复加载
    2. 保证核心类(如 java.lang.*)唯一性
  • 示意

    1
    Bootstrap -> Extension -> App -> Custom
  • 面试拓展

    • 双亲委派模型是保证 JVM 核心类安全的重要机制

6. 如何打破双亲委派?

  • 自定义 ClassLoader
  • loadClass 中先加载自己定义的类,再委派父类(逆向委派)
  • 注意:破坏双亲委派可能引发类冲突、内存泄漏

7. 什么是 SPI(Service Provider Interface)机制?

  • 概念
    • Java 提供的一种服务发现机制
    • 通过接口 + 配置文件,动态加载实现类
  • 实现
    1. 接口 MyService
    2. 文件 META-INF/services/com.example.MyService
      • 内容:实现类全限定名
    3. ServiceLoader.load(MyService.class) 自动加载实现类
  • 应用
    • JDBC 驱动加载
    • Logging 框架(SLF4J)

8. Java 是如何实现跨平台的?

  • 原理
    1. Java 代码 -> 编译成 字节码(.class)
    2. JVM 负责字节码解释或即时编译(JIT)执行
    3. JVM 将字节码映射到不同操作系统和 CPU
  • 面试拓展
    • 字节码验证机制确保安全性
    • HotSpot JIT 提供本地优化

9. JIT(即时编译器)优化了哪些东西?

  • JIT(Just-In-Time Compiler)
    • 将热点字节码动态编译成本地机器码
  • 优化策略
    1. 方法内联:减少方法调用开销
    2. 循环优化:消除冗余计算
    3. 逃逸分析:栈上分配对象
    4. 锁消除/锁粗化:减少锁开销
    5. 指令重排序优化

10. 什么是逃逸分析?

  • 概念
    • 分析对象的引用范围
    • 判断对象是否“逃逸”出方法或线程
  • 优化应用
    1. 栈上分配:对象不逃逸可以直接分配在栈上
    2. 标量替换:将对象拆解为基本类型,提高缓存命中
    3. 锁消除:对象不逃逸,synchronized 可被消除
  • 面试拓展
    • JIT 结合逃逸分析可显著提升性能
    • 适用于短生命周期对象

八、常见场景 & 设计


1. 单例模式在 Java 中有哪些实现方式?哪种最优?

  • 常见实现方式

    1. 饿汉式(静态常量)

      1
      2
      3
      4
      5
      public class Singleton {
      private static final Singleton INSTANCE = new Singleton();
      private Singleton() {}
      public static Singleton getInstance() { return INSTANCE; }
      }
      • 优点:线程安全,简单
      • 缺点:类加载即初始化,不支持延迟加载
    2. 懒汉式(加锁)

      1
      2
      3
      4
      5
      6
      7
      8
      public class Singleton {
      private static Singleton instance;
      private Singleton() {}
      public static synchronized Singleton getInstance() {
      if(instance == null) instance = new Singleton();
      return instance;
      }
      }
      • 优点:支持延迟加载
      • 缺点:每次访问都有同步开销
    3. 双重检查锁(Double-Checked Locking)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class Singleton {
      private static volatile Singleton instance;
      private Singleton() {}
      public static Singleton getInstance() {
      if(instance == null) {
      synchronized(Singleton.class) {
      if(instance == null) instance = new Singleton();
      }
      }
      return instance;
      }
      }
      • 优点:线程安全 + 延迟加载 + 高性能
      • 推荐
    4. 静态内部类

      1
      2
      3
      4
      5
      public class Singleton {
      private Singleton() {}
      private static class Holder { private static final Singleton INSTANCE = new Singleton(); }
      public static Singleton getInstance() { return Holder.INSTANCE; }
      }
      • 原理:JVM 保证类加载线程安全
      • 优点:懒加载 + 高性能
      • 最佳实践之一
    5. 枚举单例

      1
      public enum Singleton { INSTANCE; }
      • 原理:JVM 保证枚举类型单例
      • 优点:线程安全、防止反射和序列化破坏
      • 最佳实践

2. 懒汉模式和饿汉模式的区别

特性 懒汉模式 饿汉模式
加载时机 延迟加载,第一次使用 类加载即初始化
线程安全 需加锁/双重检查 天然线程安全
性能 多线程同步有开销 无额外开销
内存使用 按需分配 类加载时分配

3. 枚举单例为什么是最佳实践

  • JVM 保证枚举实例唯一性
  • 防止反射和序列化破坏
  • 实现简洁,无需手动同步
  • 面试常考点:相比双重检查锁更安全

4. 为什么要用工厂模式?

  • 作用
    • 隐藏对象创建逻辑
    • 解耦调用方和实现类
  • 类型
    1. 简单工厂:静态方法返回对象
    2. 工厂方法:子类决定创建哪种对象
    3. 抽象工厂:一组相关对象的创建
  • 优势
    • 可扩展性高
    • 符合开闭原则

5. 装饰器模式和代理模式区别

特性 装饰器模式 代理模式
目的 动态增强对象功能 控制对象访问
结构 包装原对象,继承接口 持有原对象,方法委托
场景 IO 流(BufferedReader) 远程调用、权限控制
动态性 可以通过动态代理实现

6. Java 中的事件监听模型是怎么实现的?

  • 机制
    1. 事件源(Event Source):产生事件
    2. 事件监听器(Listener):接口,定义回调方法
    3. 注册与触发:源对象维护监听器列表,事件发生时通知监听器
  • 典型应用
    • Swing/AWT 事件
    • Spring ApplicationEvent

7. 为什么 Java 不支持多继承?

  • 原因
    • 避免 钻石问题(多继承产生方法冲突)
    • 简化对象模型,保证 JVM 类型安全
  • 替代方案
    • 接口多继承 + 默认方法(Java 8+)
    • 组合设计模式(优先使用 has-a 而不是 is-a)

8. 如何用 Java 实现回调机制?

  • 原理

    • 将函数封装成对象或接口传递给另一个对象
    • 由被调用对象在合适时机调用接口方法
  • 示例

    1
    2
    interface Callback { void onDone(String result); }
    void asyncProcess(Callback callback) { new Thread(() -> callback.onDone("ok")).start(); }

9. Java 中如何实现观察者模式?

  • 机制

    1. Subject(被观察者):维护观察者列表,提供注册/注销方法
    2. Observer(观察者):接口,定义 update 方法
    3. 触发事件:Subject 状态变化时调用所有 Observer 的 update
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    interface Observer { void update(String msg); }
    class Subject {
    List<Observer> observers = new ArrayList<>();
    void addObserver(Observer o) { observers.add(o); }
    void notifyAllObservers(String msg) {
    observers.forEach(o -> o.update(msg));
    }
    }

10. Optional 的设计初衷是什么?

  • 目的

    • 避免 null 值导致的 NullPointerException
    • 提供函数式 API,提高可读性
  • 使用方式

    1
    2
    3
    Optional<String> optional = Optional.ofNullable(getValue());
    optional.ifPresent(System.out::println);
    String val = optional.orElse("default");
  • 优势

    • 显示处理缺失值
    • 支持链式调用、函数式风格
  • 面试拓展

    • Optional 不适合用于集合属性,避免滥用

二、Java 并发编程(30题)


1. 线程的生命周期

  • 状态
    1. NEW:创建后,未调用 start()
    2. RUNNABLE:调用 start(),可运行(线程调度器决定何时执行)
    3. BLOCKED:等待锁
    4. WAITING:无限期等待(Object.wait() / LockSupport.park()
    5. TIMED_WAITING:超时等待(sleep(), join(timeout)
    6. TERMINATED:执行完毕

2. 创建线程的方式

  1. 继承 Thread 类
  2. 实现 Runnable 接口
  3. 实现 Callable + FutureTask
  4. 线程池(推荐)

3. Runnable 和 Callable 区别

特性 Runnable Callable
返回值 可以有返回值
异常 不可抛出检查异常 可以抛出异常
执行方式 new Thread(runnable) ExecutorService.submit(callable)

4. Future 和 CompletableFuture 区别

  • Future
    • 阻塞式获取结果
    • 不支持链式异步处理
  • CompletableFuture
    • 支持链式异步处理(thenApply, thenCombine, exceptionally)
    • 可组合多个异步任务

5. ThreadLocal 原理

  • 每个线程持有一个 ThreadLocalMap
  • key 是 ThreadLocal 对象,value 是线程私有变量
  • 内存泄漏风险:
    • ThreadLocal 弱引用被 GC,value 没有被回收
  • 解决方法:
    • 使用 remove() 清理

6. synchronized 的锁优化机制

  1. 偏向锁:减少无竞争开销
  2. 轻量级锁:CAS + 自旋
  3. 重量级锁:Monitor 阻塞线程
  4. 锁消除/锁粗化:JIT 优化

7. ReentrantLock 实现原理

  • 基于 AQS(AbstractQueuedSynchronizer)
  • 内部维护一个 state 表示锁状态
  • 队列 FIFO 管理等待线程
  • 支持公平锁/非公平锁

8. 公平锁和非公平锁区别

特性 公平锁 非公平锁
加锁顺序 FIFO 不保证
性能 高(线程争抢)
场景 避免饥饿 高性能锁场景

9. 乐观锁和悲观锁区别

  • 悲观锁:假设会发生冲突,直接加锁(如 synchronized / ReentrantLock)
  • 乐观锁:假设冲突少,使用 CAS(如 Atomic 类)

10. AQS 原理

  • 核心:state + CLH 队列
  • 特性
    • 独占模式 / 共享模式
    • 基于 FIFO 队列管理等待线程
  • 应用
    • ReentrantLock、Semaphore、CountDownLatch、FutureTask

11. CountDownLatch 和 CyclicBarrier 区别

特性 CountDownLatch CyclicBarrier
使用次数 一次性 可重复使用
功能 等待线程完成 等待线程到达同一点
场景 线程结束同步 多线程并行分段

12. Semaphore 的实现原理

  • 控制并发线程数量
  • 基于 AQS 的共享锁
  • acquire() 获取许可,release() 释放许可

13. Exchanger 的使用场景

  • 两个线程交换数据
  • 典型场景:生产者-消费者线程交换缓冲区

14. ForkJoinPool 的原理

  • 工作窃取算法
    • 空闲线程从其他线程队列中窃取任务
  • 适合 递归任务
  • 通过 ForkJoinTask 递归拆分计算

15. CAS 操作的底层实现

  • 基于 CPU 原子指令(CMPXCHG)
  • JVM Unsafe 提供支持
  • 优点:无锁、线程安全
  • 缺点:ABA 问题

16. ABA 问题及解决

  • 问题:值先 A → B → A,CAS 无法判断变化
  • 解决
    • 版本号机制(AtomicStampedReference)
    • 双重检查

17. Atomic 原子类原理

  • 基于 CAS + Unsafe + volatile
  • 实现非阻塞线程安全
  • 示例:AtomicInteger 使用 compareAndSwapInt

18. BlockingQueue 实现原理

  • 内部维护 锁 + 条件队列
  • put 阻塞满队列,take 阻塞空队列
  • 常用实现:
    • ArrayBlockingQueue(数组 + ReentrantLock)
    • LinkedBlockingQueue(链表 + ReentrantLock)

19. ConcurrentHashMap 的分段锁机制

  • JDK 1.7:
    • Segment[] + synchronized
  • JDK 1.8:
    • CAS + synchronized 链表 + 红黑树 + Node
  • 支持高并发读写

20. ConcurrentHashMap 扩容机制

  • 采用 分段扩容 + CAS
  • 链表转红黑树提高查找性能
  • 扩容过程中使用 迁移节点

21. CopyOnWriteArrayList 原理

  • 写时复制:
    1. 写操作复制数组
    2. 修改后替换引用
  • 适合读多写少场景

22. 线程池核心参数

参数 作用
corePoolSize 核心线程数
maximumPoolSize 最大线程数
keepAliveTime 非核心线程存活时间
workQueue 任务队列
RejectedExecutionHandler 拒绝策略

23. 线程池拒绝策略

  1. AbortPolicy(默认,抛异常)
  2. CallerRunsPolicy(调用线程执行)
  3. DiscardPolicy(丢弃任务)
  4. DiscardOldestPolicy(丢弃队列最老任务)

24. 线程池工作流程

  1. 提交任务 → 放入队列
  2. 核心线程未满 → 创建线程
  3. 队列满 → 创建非核心线程
  4. 达到最大线程 → 触发拒绝策略

25. ThreadPoolExecutor vs ScheduledThreadPoolExecutor

特性 ThreadPoolExecutor ScheduledThreadPoolExecutor
功能 通用线程池 支持定时 / 周期任务
队列 BlockingQueue DelayQueue
场景 并发任务 定时任务

26. 为什么不建议使用 Executors 创建线程池

  • Executors 会产生 无限队列(如 newCachedThreadPool)
  • 容易 OOM / 线程数过多
  • 推荐:手动使用 ThreadPoolExecutor 配置核心参数

27. JMM 内存可见性问题

  • 多线程访问共享变量,修改不立即可见
  • 解决方法
    • volatile
    • synchronized / Lock

28. happens-before 规则

  • 保证操作顺序与可见性
  • 常见规则:
    1. 锁的解锁 → 加锁
    2. volatile 写 → 读
    3. 线程 start() → run()
    4. 线程 join() → 结束

29. 死锁产生条件及避免

  • 四个必要条件
    1. 互斥
    2. 占有且等待
    3. 不可抢占
    4. 循环等待
  • 避免方法
    • 避免循环等待,按顺序加锁
    • 使用 tryLock + 超时
    • 使用单一锁

30. 常见并发容器及区别

容器 特性
ConcurrentHashMap 高并发读写,分段/链表+红黑树
CopyOnWriteArrayList 写时复制,读多写少
ConcurrentLinkedQueue 非阻塞

队列,基于 CAS |
| BlockingQueue(Array/Linked) | 支持阻塞 put/take |
| ConcurrentSkipListMap | 有序,基于跳表,线程安全 |


三、JVM 与性能优化(30题)


1. JVM 内存结构

  • 方法区 / 元空间(Metaspace):存放类信息、常量、静态变量
  • 堆(Heap):对象实例、GC 管理区
  • 栈(Stack):方法调用、局部变量
  • 程序计数器(PC):记录线程执行地址
  • 本地方法栈(Native Stack):执行 native 方法
  • 直接内存(Direct Memory):堆外内存,用于 NIO

2. 堆和栈的区别

特性
存储内容 对象实例 方法调用帧、局部变量
生命周期 GC 管理 随方法结束释放
线程共享 否(线程私有)
访问速度 较慢
内存大小

3. 新生代、老年代、永久代(元空间)的区别

  • 新生代(Young Generation):
    • 存放新创建对象
    • 分为 Eden + 2 个 Survivor 区
    • Minor GC 主要发生在此
  • 老年代(Old/Tenured Generation):
    • 长寿命对象
    • Full GC 主要作用区域
  • 永久代 / 元空间
    • 存放类元数据
    • Java 8+ 使用 Metaspace(Native 内存)

4. 对象的创建过程

  1. 类加载检查
  2. 分配内存
    • 堆上分配对象空间(TLAB/大对象直接分配)
  3. 初始化零值
  4. 构造器初始化
  5. 引用赋值

5. 对象的内存分配策略

  • 年轻代分配:TLAB(Thread Local Allocation Buffer)
  • 大对象直接进入老年代(如数组 > 1MB)
  • 长期存活对象晋升到老年代
  • 栈上分配:通过逃逸分析优化

6. GC Roots

  • 常见类型
    • 栈帧中的引用变量
    • 方法区静态变量
    • 常量引用
    • 本地方法栈引用

7. 垃圾回收算法

  • 引用计数(不可解决循环引用)
  • 可达性分析(GC Roots)
  • 具体回收算法
    • 标记-清除
    • 标记-整理
    • 复制算法
    • 分代回收

8. CMS 垃圾回收器流程

  1. 初始标记(Stop-The-World)
  2. 并发标记(标记可回收对象)
  3. 并发预清理
  4. 重新标记(Stop-The-World)
  5. 并发清理(回收老年代)
  • 特点:低停顿,但有浮动垃圾和内存碎片问题

9. G1 垃圾回收器原理

  • Region(分区)管理堆
  • 分代+并行+增量收集
  • 垃圾回收策略
    • 优先回收回收成本低的 Region
    • 并发标记 + 并行清理
  • 目标:控制停顿时间
  • 优势:无碎片、可预测停顿

10. ZGC 和 Shenandoah 特点

  • 低延迟 GC,停顿时间 < 10ms
  • 并发标记 + 并发压缩
  • 内存可扩展性好
  • 支持大内存 (>100GB)
  • 面试常问区别:ZGC 使用 Load Barrier,Shenandoah 使用 Region Pinning

11. Minor GC 与 Full GC 区别

特性 Minor GC Full GC
触发区域 新生代 整个堆(包括老年代)
停顿时间
频率
GC 类型 复制算法 标记-整理 / CMS / G1

12. OOM 常见类型及排查

  • Java Heap Space:堆内存不足
  • GC Overhead Limit:GC 花费过多时间
  • Metaspace:类元信息过多
  • Direct Memory:堆外内存溢出
  • 排查工具
    • jmap、jstack、VisualVM、MAT

13. 类加载器分类

  • Bootstrap(根类加载器)
  • Extension(扩展类加载器)
  • Application(系统类加载器)
  • 自定义 ClassLoader

14. 双亲委派模型破坏场景

  • 动态加载不同版本类
  • 热部署、插件化框架(Tomcat、OSGi)

15. JVM 常见参数调优

  • 堆大小-Xms -Xmx
  • 新生代比例-XX:NewRatio
  • GC 类型-XX:+UseG1GC
  • 线程栈大小-Xss
  • Metaspace-XX:MetaspaceSize

16. 内存泄漏 vs 内存溢出

  • 内存泄漏:对象不再使用,但仍有引用
  • 内存溢出(OOM):申请内存失败

17. 逃逸分析

  • 作用
    • 分析对象作用域
    • 栈上分配、锁消除、同步优化
  • JVM 优化
    • 栈上分配(不入堆)
    • 去掉无用锁(锁消除)

18. 方法内联

  • JIT 优化
  • 小方法直接替换调用,减少方法调用开销
  • 增加 CPU 指令局部性,提高性能

19. JIT 编译器优化

  • 方法内联
  • 循环展开
  • 逃逸分析
  • 常量折叠
  • 逃逸分析 + 栈上分配

20. Safepoint 机制

  • 所有线程暂停,进行 GC 或其他全局操作
  • JVM 可安全修改线程状态

21. Stop The World (STW)

  • 定义:暂停所有用户线程进行 GC 或其他 JVM 内部操作
  • 影响:应用停顿,低延迟场景需控制 STW 时间

22. Finalize 方法问题

  • 不确定执行时间
  • 性能开销大
  • 可导致对象“复活”,增加内存压力
  • 替代方案:try-with-resources / Cleaner

23. JVM 如何判断对象可回收

  • 可达性分析:从 GC Roots 出发,未被可达的对象可回收

24. TLAB(Thread Local Allocation Buffer)作用

  • 每个线程独享一块 Eden 内存
  • 作用
    • 减少多线程竞争
    • 提高对象分配效率

25. Direct Memory 为什么会 OOM

  • 堆外内存不足
  • NIO Buffer 使用 Unsafe 分配
  • 不受堆内存限制,但 JVM 无法直接管理

26. Metaspace 作用

  • 存放类元数据
  • Java 8+ 替代永久代
  • 可以通过 -XX:MetaspaceSize 限制

27. Full GC 触发条件

  • 老年代空间不足
  • 永久代 / 元空间不足
  • System.gc() 调用
  • 内存分配失败

28. 查看 JVM 内存使用情况

  • jstat -gc <pid>
  • jmap -heap <pid>
  • VisualVM / JConsole
  • MAT 分析 heap dump

29. Arthas 常用命令

  • dashboard:系统指标
  • thread:线程状态
  • heapdump:堆转储
  • monitor:方法耗时
  • watch:方法参数/返回值
  • jad:反编译类

30. JVM 调优常见面试题

  • 堆大小设置策略
  • 新生代/老年代比例
  • GC 选择(G1 / CMS / Parallel)
  • TLAB 与对象分配优化
  • Full GC 触发条件
  • 内存泄漏排查与 MAT 使用

四、Spring & Spring Boot(30题)

1. Spring 的 IoC 容器原理?

核心回答:
IoC(控制反转)是通过容器管理对象创建和依赖注入,开发者不需要手动 new 对象,而是由 Spring 容器统一管理。

深入解析:

  • IoC 容器的核心是 BeanFactoryApplicationContext
  • 工作流程:
    1. 配置解析:读取 XML / 注解 / JavaConfig;
    2. BeanDefinition:将配置转成 BeanDefinition;
    3. Bean 实例化:通过反射或 CGLIB 创建对象;
    4. 依赖注入:根据构造方法 / Setter / @Autowired 注入依赖;
    5. 初始化回调:执行 BeanPostProcessor / InitializingBean;
    6. 放入单例池:容器保存对象供全局使用。

延伸思考:

  • IoC 是 依赖查找(DL)依赖注入(DI) 的结合。
  • 面试可能追问:Spring 如何实现延迟加载?(@Lazy + 单例池管理)
  • 实战:配置文件少时用 XML,大项目推荐 JavaConfig + 注解

2. Bean 的生命周期?

核心回答:
创建 → 属性注入 → 初始化 → 使用 → 销毁。

深入解析:

  1. 实例化:通过反射创建对象;
  2. 依赖注入:注入属性;
  3. Aware 接口回调:如 BeanNameAware、ApplicationContextAware;
  4. BeanPostProcessor 前置处理:比如 @Autowired 的处理;
  5. 初始化方法:@PostConstruct / InitializingBean.afterPropertiesSet() / init-method;
  6. BeanPostProcessor 后置处理
  7. 就绪使用
  8. 销毁回调:@PreDestroy / DisposableBean.destroy() / destroy-method。

延伸思考:

  • 面试常追问:Spring 容器关闭时,单例 Bean 会被销毁吗?(会,调用 destroy 回调)。
  • 原型作用域 Bean 不会被销毁,需要开发者手动管理。

3. BeanFactory 和 ApplicationContext 的区别?

核心回答:

  • BeanFactory:最基础 IoC 容器,懒加载,轻量级;
  • ApplicationContext:继承 BeanFactory,支持 AOP、国际化、事件机制、自动装配,默认预实例化。

深入解析:

  • BeanFactory:面向低资源环境,功能有限;
  • ApplicationContext:常用实现有 ClassPathXmlApplicationContext、AnnotationConfigApplicationContext。

延伸思考:

  • 面试常问:为什么 ApplicationContext 默认是预加载?(减少运行时性能开销,启动时完成依赖检查)。

4. 单例 Bean 如何保证线程安全?

核心回答:
Spring 不保证单例 Bean 的线程安全,需要开发者在代码中保证。

深入解析:

  • IoC 容器中的单例 Bean 是 多线程共享的
  • 常见的线程安全措施:
    1. 无状态设计(推荐);
    2. 使用 ThreadLocal 保存状态
    3. 方法内部使用局部变量
    4. 必要时加锁(synchronized / Lock)

延伸思考:

  • 面试陷阱:Spring 单例 Bean 是不是线程安全的?答:不是。
  • 例子:Controller、Service 通常是无状态的,可以安全单例。

5. Spring AOP 的实现原理?

核心回答:
AOP(面向切面编程)通过 动态代理 实现,在方法执行前后插入增强逻辑。

深入解析:

  • 实现方式:
    1. JDK 动态代理:基于接口;
    2. CGLIB 代理:基于子类字节码增强。
  • AOP 核心组件:
    • JoinPoint(连接点)、Pointcut(切点)、Advice(通知)、Weaving(织入)、Proxy(代理对象)。
  • AOP 流程:调用目标方法 → 代理对象拦截 → 执行切面逻辑 → 继续执行原方法。

延伸思考:

  • 面试官可能问:Spring 默认用哪种代理?
    • 如果有接口 → JDK 动态代理;
    • 没有接口 → CGLIB。
  • JDK 代理只能基于接口,CGLIB 可以基于类。

6. JDK 动态代理和 CGLIB 的区别?

核心回答:

  • JDK:基于接口,生成实现类;
  • CGLIB:基于继承,生成子类字节码。

深入解析:

  • JDK 动态代理:利用 Proxy.newProxyInstance,性能较高;
  • CGLIB:利用 ASM 字节码操作生成子类,性能稍慢,但支持类代理;
  • Spring 默认优先使用 JDK,如果没有接口再用 CGLIB。

延伸思考:

  • 限制:CGLIB 无法代理 final 类 / final 方法;
  • Spring Boot 可通过 spring.aop.proxy-target-class=true 强制使用 CGLIB。

7. Spring 事务的传播机制?

核心回答:
事务传播机制定义了方法在调用时事务的边界,常见类型:

  • REQUIRED(默认)、REQUIRES_NEW、NESTED、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER。

深入解析:

  • REQUIRED:有事务就加入,没有就新建;
  • REQUIRES_NEW:挂起当前事务,新建一个;
  • NESTED:嵌套事务(依赖保存点 rollback);
  • SUPPORTS:有就用,没有就不用;
  • NOT_SUPPORTED:挂起事务,以非事务运行;
  • MANDATORY:必须在事务中调用;
  • NEVER:必须在非事务中运行。

延伸思考:

  • 面试陷阱:嵌套事务和新事务的区别?
    • 嵌套事务依赖外层事务回滚;
    • 新事务互相独立。

8. Spring 事务的隔离级别?

核心回答:
事务隔离级别解决并发问题,Spring 支持数据库的 5 种隔离级别:

  • DEFAULT(数据库默认)
  • READ_UNCOMMITTED(可能脏读)
  • READ_COMMITTED(防脏读)
  • REPEATABLE_READ(防脏读、不可重复读)
  • SERIALIZABLE(防脏读、不可重复读、幻读)

深入解析:

  • InnoDB 默认是 REPEATABLE_READ
  • Oracle 默认是 READ_COMMITTED

延伸思考:

  • 面试可能追问:MySQL 的 REPEATABLE_READ 如何避免幻读?(MVCC + 间隙锁)。

9. @Transactional 的实现原理?

核心回答:
@Transactional 通过 AOP 代理,在方法执行前后控制事务。

深入解析:

  • Spring 容器解析 @Transactional → 生成代理 → 在代理方法里执行事务增强逻辑:
    1. 获取事务管理器;
    2. 开启事务;
    3. 执行目标方法;
    4. 异常回滚 / 正常提交;
    5. 清理资源。

延伸思考:

  • 默认只回滚 RuntimeException 和 Error,检查异常需要 rollbackFor
  • 面试常问:为什么 @Transactional 方法用 private 修饰无效?
    → 因为 AOP 代理无法拦截 private 方法。

10. 循环依赖如何解决?

核心回答:
Spring 通过 三级缓存 解决单例 Bean 的循环依赖。

深入解析:

  • 三级缓存:
    1. singletonObjects(成品对象)
    2. earlySingletonObjects(提前暴露的对象,半成品)
    3. singletonFactories(对象工厂,提供代理对象)
  • 流程:A → 依赖 B → B 依赖 A → 提前暴露 A 的引用给 B → 依赖注入成功。

延伸思考:

  • 只能解决 单例 + setter 注入 的循环依赖;
  • 构造器注入循环依赖 无法解决,会抛出异常。

11. Spring 的三级缓存解决了什么问题?

核心回答:
解决 单例 Bean 的循环依赖,保证在创建过程中能提前暴露代理对象。

深入解析:

  • Spring 单例池维护了 三级缓存
    1. singletonObjects → 一级缓存(完全初始化的单例);
    2. earlySingletonObjects → 二级缓存(提前暴露的半成品对象);
    3. singletonFactories → 三级缓存(对象工厂,通常生成代理对象)。
  • 流程:
    • 当 A 依赖 B,B 又依赖 A 时:
      1. 创建 A → 放入三级缓存;
      2. B 依赖 A 时,先从三级缓存拿到 A 的工厂,暴露提前代理对象 → 放入二级缓存;
      3. 等 A 完成初始化后,替换为一级缓存。

延伸思考:

  • 面试官可能追问:为什么要三级缓存,不是二级就够了吗?
    → 因为 AOP 代理对象需要通过工厂提前暴露,不能只放半成品对象。
  • 只支持 setter 注入的循环依赖,不支持 构造函数循环依赖

12. Spring MVC 的工作流程?

核心回答:
请求 → DispatcherServlet → HandlerMapping → HandlerAdapter → Controller → ViewResolver → 响应。

深入解析:

  1. 用户请求到达 DispatcherServlet;
  2. DispatcherServlet 调用 HandlerMapping 寻找处理器;
  3. 通过 HandlerAdapter 调用具体 Controller;
  4. Controller 执行业务逻辑,返回 ModelAndView;
  5. ViewResolver 解析视图;
  6. DispatcherServlet 渲染并返回响应给客户端。

延伸思考:

  • 面试官常问:DispatcherServlet 是单例还是多例?(单例,线程安全依赖于无状态设计)。
  • 实战:可以用 @RestController 直接返回 JSON,省略 ViewResolver。

13. DispatcherServlet 的作用?

核心回答:
Spring MVC 的 前端控制器,负责请求分发和响应渲染。

深入解析:

  • 职责:
    1. 拦截请求;
    2. 调用 HandlerMapping 找到 Controller;
    3. 调用 HandlerAdapter 执行 Controller;
    4. 调用 ViewResolver 渲染视图;
    5. 返回响应。
  • 核心思想:统一入口,集中控制

延伸思考:

  • 面试追问:DispatcherServlet 在 Spring Boot 中怎么注册的?
    → 自动配置类 DispatcherServletAutoConfiguration 注册。

14. Spring Boot 的自动装配原理?

核心回答:
基于 @EnableAutoConfiguration + SpringFactoriesLoader 实现,根据 classpath 条件加载配置类。

深入解析:

  1. @SpringBootApplication → 启用 @EnableAutoConfiguration
  2. SpringFactoriesLoader 读取 META-INF/spring.factories 文件;
  3. 根据 @Conditional 注解判断是否加载;
  4. 把匹配的 Bean 注册到容器。

延伸思考:

  • 面试官可能追问:怎么禁用某个自动配置?
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
  • 或者在 application.yml 中配置 spring.autoconfigure.exclude

15. Spring Boot Starter 的机制?

核心回答:
Starter 是一组 依赖封装 + 自动配置,简化第三方库集成。

深入解析:

  • 命名规范:spring-boot-starter-xxx
  • 依赖 Starter → 自动引入必要依赖 + 自动装配类;
  • 例子:spring-boot-starter-web → 引入 Spring MVC + Tomcat + Jackson,并自动配置 DispatcherServlet。

延伸思考:

  • 面试官常问:怎么写一个自定义 Starter?
    1. 提供 AutoConfiguration 类;
    2. META-INF/spring.factories 注册;
    3. 打包发布。

16. @ConfigurationProperties 和 @Value 的区别?

核心回答:

  • @Value:单个属性注入;
  • @ConfigurationProperties:批量绑定配置,支持类型安全。

深入解析:

  • @Value("${key}"):只能注入单个值;
  • @ConfigurationProperties(prefix = "xxx"):把配置文件中 xxx.* 映射到 JavaBean。
  • @ConfigurationProperties 更适合复杂配置对象。

延伸思考:

  • 面试官可能追问:推荐用哪个?
    @ConfigurationProperties,因为支持校验(@Validated)、IDE 提示。
  • 实战:数据库配置常用 @ConfigurationProperties

17. Spring Boot 如何实现热部署?

核心回答:
主要通过 Spring Boot DevToolsJRebel

深入解析:

  • DevTools:监控 classpath 文件变化,触发 SpringContext 重启;
  • JRebel:字节码增强,做到无感知热替换;
  • IDE(IntelliJ IDEA / Eclipse)也支持 Build 自动 reload。

延伸思考:

  • DevTools 适合开发,JRebel 更强大但收费。
  • 生产环境不建议热部署,而是灰度发布/滚动更新。

18. Spring Boot 如何处理配置文件?

核心回答:
Spring Boot 读取 application.yml/properties,并支持多环境和优先级加载。

深入解析:

  • 默认加载顺序:
    1. 命令行参数;
    2. application.properties / application.yml
    3. 外部配置文件(config 目录);
    4. 默认配置。
  • 支持多环境:application-dev.ymlapplication-prod.yml,通过 spring.profiles.active=dev 切换。

延伸思考:

  • 面试官可能问:优先级谁最高?
    → 命令行参数。
  • 实战:生产环境常用外部化配置(如 --spring.config.location)。

19. @Conditional 注解的作用?

核心回答:
用于 条件装配,根据环境 / 配置 / Bean 是否存在决定是否加载 Bean。

深入解析:

  • 常见实现:
    • @ConditionalOnClass(类存在时装配);
    • @ConditionalOnMissingBean(没有 Bean 时装配);
    • @ConditionalOnProperty(配置项满足条件时装配);
    • @ConditionalOnWebApplication
  • 广泛用于自动配置。

延伸思考:

  • 面试追问:怎么写自定义条件?
    → 实现 Condition 接口,重写 matches() 方法。

20. Spring Boot 常见的优化点?

核心回答:

  • 启动优化:懒加载、裁剪 Starter;
  • 内存优化:减小 Bean 数量、引入轻量组件;
  • 数据库优化:连接池、批量操作;
  • 生产优化:Actuator 监控、外部化配置。

深入解析:

  1. 启动性能spring.main.lazy-initialization=true
  2. 数据库性能:HikariCP 连接池、批处理 SQL;
  3. 缓存优化:整合 Redis、Caffeine;
  4. 日志优化:异步日志(Logback AsyncAppender);
  5. 部署优化:分层构建(Layered Jar)、Docker 镜像加速。

延伸思考:

  • 面试常问:Spring Boot 为什么启动比 Spring 慢?
    → 因为要做自动装配和环境扫描。
  • 实战:生产中可用 spring-context-indexer 加快启动。

21. Spring Boot 的自动配置原理

核心回答
Spring Boot 的自动配置基于 SpringFactoriesLoader + @EnableAutoConfiguration + 条件注解(@ConditionalXXX)
它会根据 classpath 中依赖的 jar 包和已有 Bean 自动装配所需的配置。

深入解析

  1. 关键入口
    • @SpringBootApplication@EnableAutoConfigurationAutoConfigurationImportSelector
  2. 配置来源
    • META-INF/spring.factoriesMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  3. 条件装配
    • 通过 @ConditionalOnClass@ConditionalOnMissingBean 等来避免重复配置。
  4. 运行时流程
    • 启动时加载所有候选配置类 → 根据条件筛选 → 注册 BeanDefinition → 实例化 Bean。

延伸思考

  • 面试追问:如何排查“为什么某个 Bean 没有生效”?
    → 使用 --debug 参数查看 自动配置报告
  • 实战:如果不想加载某个自动配置,可以用 @SpringBootApplication(exclude=XXXAutoConfiguration.class)

22. Spring Boot 的启动流程

核心回答
启动流程:

  1. 创建 SpringApplication 对象。
  2. 准备环境(Environment)。
  3. 加载 ApplicationContext。
  4. 调用自动配置。
  5. 启动内嵌容器(Tomcat/Jetty/Netty)。
  6. 执行 CommandLineRunnerApplicationRunner

深入解析

  • 源码入口SpringApplication.run()
  • 事件监听
    • ApplicationStartingEvent(最早事件),
    • ApplicationReadyEvent(启动完成)。
  • Bean 加载
    • 先加载主类所在包及子包的组件(@ComponentScan)。
    • 再结合自动配置。

延伸思考

  • 面试官可能问:Spring Boot 如何支持外部配置?
    → 通过 ConfigFileApplicationListener 解析 application.properties / application.yml 并注入 Environment。

23. Spring Boot 的配置文件加载顺序

核心回答
Spring Boot 的配置优先级(从高到低):

  1. 命令行参数
  2. application.properties/yml(在 config/ > 当前目录 > classpath 下)
  3. 外部系统环境变量
  4. JNDI、系统属性
  5. 默认配置

深入解析

  • 多 profileapplication-dev.yml 会在 --spring.profiles.active=dev 时覆盖默认配置。
  • 优先级控制:同一属性多次定义时,高优先级覆盖低优先级。

延伸思考

  • 面试追问:如果配置冲突,如何快速定位?
    → 启动时加参数 --debug,查看配置源和优先级。

24. Spring Boot 如何实现热部署

核心回答
通过 Spring Boot DevToolsJRebel 实现热部署。

深入解析

  • DevTools 原理
    • 使用两个 ClassLoader:
      • Base ClassLoader(第三方依赖不会变),
      • Restart ClassLoader(自己代码,改动后只重新加载)。
  • 局限性:修改依赖库不会触发热部署,只能重启。

延伸思考

  • 实际开发中推荐用 DevTools + IDEA 自动构建
  • 面试追问:生产环境如何实现热更新?
    → 使用 Spring Cloud Config + 动态刷新(/actuator/refresh)。

25. Spring Boot 中的监控和健康检查

核心回答
Spring Boot 提供 Actuator 模块,可监控应用运行状态。

深入解析

  • 常见端点:
    • /actuator/health(健康检查)
    • /actuator/metrics(性能指标)
    • /actuator/env(环境变量)
    • /actuator/beans(已加载 Bean)
  • 健康检查可扩展:实现 HealthIndicator 接口,自定义检查逻辑(如 Redis、MQ)。

延伸思考

  • 面试追问:如何在 Kubernetes 中结合健康检查?
    → 配置 livenessProbereadinessProbe 调用 /actuator/health

26. Spring Boot 如何集成数据库(JDBC、JPA、MyBatis)

核心回答
Spring Boot 提供 spring-boot-starter-jdbcspring-boot-starter-data-jpa 等 starter,支持开箱即用。

深入解析

  • JDBC:直接操作数据库,结合 JdbcTemplate
  • JPA:基于 Hibernate,支持声明式 ORM(@Entity)。
  • MyBatis:需引入第三方 starter(mybatis-spring-boot-starter),用 @MapperScan

延伸思考

  • 面试追问:Spring Data JPA 与 MyBatis 区别?
    • JPA → ORM 自动化,适合快速开发。
    • MyBatis → SQL 手写,灵活可控,适合复杂业务。

27. Spring Boot 如何实现日志管理

核心回答
Spring Boot 默认使用 SLF4J + Logback,可支持 Log4j2。

深入解析

  • 配置方式
    • application.ymllogback-spring.xml
  • 日志级别TRACE < DEBUG < INFO < WARN < ERROR
  • 多环境日志
    • logback-spring.xml + springProfile 标签可区分 dev / prod。

延伸思考

  • 面试追问:如何将日志输出到 ELK(ElasticSearch + Logstash + Kibana)?
    → 使用 logstash-logback-encoder

28. Spring Boot 如何处理跨域问题(CORS)

核心回答
通过 @CrossOrigin 或全局配置 CorsFilter

深入解析

  • 方式1:注解

    1
    2
    3
    @CrossOrigin(origins="http://example.com")
    @GetMapping("/api/data")
    public String getData() { ... }
  • 方式2:全局配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Bean
    public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**").allowedOrigins("*");
    }
    };
    }

延伸思考

  • 面试追问:CORS 和 Nginx 反向代理跨域的区别?
    • CORS → Spring Boot 处理,适合单应用。
    • Nginx → 网关层处理,适合微服务架构。

29. Spring Boot 中的安全机制(Spring Security)

核心回答
Spring Security 提供认证(Authentication)+ 授权(Authorization)。

深入解析

  1. 认证流程
    • 用户请求 → UsernamePasswordAuthenticationFilter → AuthenticationManager → UserDetailsService → 认证成功 → SecurityContext 保存信息。
  2. 授权机制
    • 基于 URL(拦截路径)、方法级(@PreAuthorize)、表达式。
  3. 默认用户:Spring Boot 2.x 默认生成随机密码(日志中输出)。

延伸思考

  • 面试追问:如何在 Spring Security 中集成 JWT?
    • 自定义过滤器,解析 JWT 并注入 SecurityContext。

30. 如何优化 Spring Boot 启动速度

核心回答
优化手段:减少自动配置、延迟初始化、引入轻量依赖。

深入解析

  • 延迟加载spring.main.lazy-initialization=true
  • 移除不必要的 starter
  • 本地调试时跳过安全/监控配置
  • 使用 GraalVM Native Image 加速启动

延伸思考

  • 面试追问:Spring Boot 启动慢,如何排查?
    • 使用 --debugApplicationStartup 查看 Bean 加载耗时。

五、Spring Cloud(30题)


1. Spring Cloud 是什么?

核心回答
Spring Cloud 是基于 Spring Boot 的微服务治理框架,提供了分布式系统所需的全套组件:服务注册发现、负载均衡、配置中心、网关、熔断限流、链路追踪等。

深入原理

  • 定位:微服务的 “生态整合者”,不是单一技术,而是一套解决方案。
  • 核心组件
    • Eureka(注册中心)
    • Ribbon / LoadBalancer(客户端负载均衡)
    • Feign(声明式远程调用)
    • Hystrix / Resilience4j / Sentinel(熔断限流)
    • Config Server(配置中心)
    • Gateway(网关)
    • Sleuth + Zipkin(链路追踪)
  • 本质:解决分布式复杂性,降低开发门槛。

面试延伸

  • Spring Cloud 与 Kubernetes Service Mesh 的区别:前者 SDK 模式,后者 Sidecar 模式。

2. Spring Cloud 和 Dubbo 的区别?

核心回答

  • Dubbo:高性能 RPC 框架,核心是远程调用。
  • Spring Cloud:全家桶式微服务生态,功能更全面。

深入原理

  • 协议层:Dubbo 用 TCP/自定义协议,性能高;Spring Cloud 多用 REST/HTTP。
  • 功能覆盖:Dubbo 主要做服务调用,Spring Cloud 包含注册、配置、熔断、网关等。
  • 演进:Dubbo3.0 开始支持 gRPC/Triple 协议,与 Spring Cloud 越来越接近。

面试延伸

  • 公司内网场景(追求性能) → Dubbo;
  • 大规模分布式互联网场景(追求生态) → Spring Cloud。

3. Eureka 的工作原理?

核心回答
Eureka 是 Netflix 开源的注册中心,负责 服务注册与发现,遵循 AP 原则。

深入原理

  • 注册:服务启动后将自身信息(IP、端口、状态)注册到 Eureka Server。
  • 续约:服务定期发送心跳(默认 30s)。
  • 下线:服务关闭时发送下线请求。
  • 拉取:客户端定期从 Server 拉取注册表(默认 30s),本地缓存,提高可用性。

面试延伸

  • 面试官可能问:Eureka 与 Zookeeper 区别?
    • Eureka AP → 保证可用性(即使部分节点挂了仍能服务);
    • Zookeeper CP → 强一致性(选举可能导致服务不可用)。

4. Eureka 的自我保护机制?

核心回答
Eureka 会在网络分区或心跳丢失时,不立即移除服务实例,保证可用性。

深入原理

  • 触发条件:在 15 分钟内心跳失败比例 > 85%。
  • 行为:
    • 暂停剔除失效服务。
    • 保留已有注册表,允许客户端继续获取服务。
  • 好处:防止因网络抖动导致“服务雪崩”。

面试延伸

  • 缺点:可能把已经宕机的实例继续暴露出去。
  • 可配置 eureka.server.enable-self-preservation=false 来关闭。

5. Ribbon 的负载均衡策略?

核心回答
Ribbon 是客户端负载均衡组件,常见策略有:

  • 轮询(RoundRobin)
  • 随机(Random)
  • 权重(WeightedResponseTimeRule)
  • 最小并发(BestAvailableRule)

深入原理
Ribbon 在客户端维护服务列表,从注册中心拉取后,根据策略选择目标服务发起请求。

面试延伸

  • 与 Nginx 的区别:Ribbon 是 客户端负载均衡(调用方决定),Nginx 是 服务端负载均衡(请求先到代理)。

6. Ribbon 和 Nginx 的区别?

核心回答

  • Ribbon:客户端负载均衡,本地决定调用哪台服务。
  • Nginx:服务端负载均衡,所有流量先经过代理。

深入原理

  • 部署位置:Ribbon 在调用方应用中,Nginx 独立部署。
  • 扩展性:Ribbon 与 Eureka 配合,支持动态上下线;Nginx 需手动改配置或借助 Consul/Keepalived。

面试延伸

  • 面试官追问:为什么微服务更倾向于 Ribbon?
    • 因为客户端模式更灵活,可以无感知扩容/缩容。

7. Feign 的工作原理?

核心回答
Feign 是声明式 HTTP 客户端,通过接口 + 注解的方式调用远程服务。

深入原理

  • 运行时基于 动态代理(JDK Proxy)。
  • 结合 Ribbon 负载均衡,自动选择服务实例。
  • 可集成 Hystrix/Sentinel,支持熔断。

面试延伸

  • 面试官问:Feign 和 RestTemplate 的区别?
    • RestTemplate → 代码侵入性强,需要手动拼接请求。
    • Feign → 声明式,更优雅。

8. Feign 如何支持熔断?

核心回答
Feign 可集成 Hystrix 或 Sentinel,通过 Fallback 提供降级逻辑。

深入原理

  • Hystrix:在请求失败/超时时调用 fallback 方法。
  • Sentinel:通过注解 @SentinelResource + fallback 实现。

面试延伸

  • 面试官追问:熔断和降级区别?
    • 熔断:保护系统不被拖垮。
    • 降级:提供兜底逻辑,保证用户体验。

9. Hystrix 的工作原理?

核心回答
Hystrix 通过 线程池隔离 + 熔断器机制 保护服务,防止雪崩效应。

深入原理

  • 线程池隔离:不同服务调用用不同线程池,避免相互影响。
  • 熔断机制
    • 统计请求失败率。
    • 达到阈值 → 熔断打开,直接走 fallback。
    • 一段时间后 → 尝试半开,成功则恢复。

面试延伸

  • Hystrix 已停止维护,推荐用 Resilience4j 或 Sentinel。

10. Hystrix 的线程池隔离和信号量隔离?

核心回答

  • 线程池隔离:每个依赖用独立线程池,适合耗时操作。
  • 信号量隔离:限制并发数,不开线程池,适合低延迟操作。

深入原理

  • 线程池隔离:开销大,但最安全。
  • 信号量隔离:轻量,但不能防止调用阻塞线程。

面试延伸

  • 实战经验:大多数 IO 调用用线程池隔离,本地计算或轻量调用用信号量隔离。

11. Resilience4j 和 Hystrix 区别?

  • Resilience4j 基于 Java 8 函数式接口,更轻量,支持熔断、限流、重试、隔离等;
  • Hystrix 已停止维护,功能单一(熔断+线程隔离)。
  • Resilience4j 模块化,可按需引入。

12. Gateway 的核心功能?

  • 路由转发(根据路径、Header、参数转发请求)。
  • 过滤器机制(统一鉴权、日志、限流)。
  • 负载均衡(结合注册中心)。
  • 协议支持(WebSocket、HTTPS)。

13. Gateway 和 Zuul 的区别?

  • Zuul1 基于 Servlet,阻塞 IO,性能差。
  • Gateway 基于 Netty + Reactor,支持响应式,性能高。
  • Zuul2 虽然改为异步,但生态上 Gateway 更推荐。

14. Config Server 的作用?

  • 统一管理微服务配置,支持 Git、SVN 等存储。
  • 动态刷新配置,保证环境一致性。
  • 与 Bus 结合可广播刷新。

15. Config Server 如何保证配置实时刷新?

  • 客户端通过 @RefreshScope 实现动态刷新。
  • 手动触发 /actuator/refresh
  • 结合 Spring Cloud Bus,可用消息队列广播刷新。

16. Spring Cloud Bus 的原理?

  • 底层基于消息队列(Kafka、RabbitMQ)。
  • 当配置变更时,Bus 广播事件,所有服务接收并刷新配置。

17. Sleuth 的作用?

  • 在微服务调用链路中,自动生成 TraceId / SpanId
  • 实现请求链路追踪,定位性能瓶颈。

18. Sleuth 与 Zipkin 的关系?

  • Sleuth:负责埋点、生成日志。
  • Zipkin:负责收集、存储、展示调用链数据。

19. 分布式链路追踪的原理?

  • 通过唯一 TraceId 贯穿整个调用链。
  • 每次远程调用生成新的 SpanId。
  • 数据上报到集中式系统(Zipkin/Jaeger)。

20. Spring Cloud Stream 的作用?

  • 封装消息中间件(Kafka/RabbitMQ)。
  • 提供统一编程模型,屏蔽底层差异。
  • 支持消息驱动架构。

21. 消息驱动模型和事件驱动模型区别?

  • 消息驱动:强调解耦,消息可靠投递。
  • 事件驱动:强调系统对事件的响应。
  • Spring Cloud Stream 更偏消息驱动,但也支持事件模式。

22. Nacos 和 Eureka 的区别?

  • Nacos 除了服务注册发现,还提供配置中心功能。
  • Nacos 支持 AP/CP 模式切换;Eureka 固定 AP。
  • Nacos 提供 UI,更易运维。

23. Nacos 配置中心和注册中心的实现原理?

  • 配置中心:基于推拉结合(长轮询 + Server 推送)。
  • 注册中心:基于心跳机制维护服务健康状态。

24. Sentinel 的工作原理?

  • 基于 滑动窗口统计 请求 QPS、响应时间。
  • 支持多种限流、熔断、降级规则。
  • 与 Dubbo/Spring Cloud/Feign 无缝集成。

25. 限流的常见算法(令牌桶、漏桶)?

  • 令牌桶:系统以固定速率生成令牌,请求需拿到令牌才执行。
  • 漏桶:请求先进入桶,系统以固定速率处理,超出则丢弃。
  • 区别:令牌桶允许突发流量,漏桶更平滑。

26. 熔断和降级的区别?

  • 熔断:系统保护机制,拒绝调用,防止雪崩。
  • 降级:业务兜底方案,返回备用结果。
  • 熔断是“硬防护”,降级是“软兜底”。

27. CAP 定理和 Spring Cloud 的关系?

  • CAP:一致性(C)、可用性(A)、分区容错性(P)。
  • Eureka → AP;Consul → CP;Nacos → AP/CP 切换。

28. Spring Cloud 与 Kubernetes 的关系?

  • Spring Cloud:偏应用层,开发框架。
  • Kubernetes:偏基础设施,容器编排。
  • 可结合使用:Spring Cloud 负责应用治理,K8s 负责资源调度。

29. 微服务中的灰度发布?

  • 指新版本只对部分用户开放。
  • 实现方式:
    • Nginx/Gateway 按用户 ID 或流量比例路由。
    • Service Mesh(Istio)支持更灵活的流量控制。

30. Spring Cloud 如何实现服务网格?

  • 原生 Spring Cloud 偏 SDK 模式,缺乏服务网格能力。
  • 可结合 Spring Cloud + Istio/Envoy,实现流量治理、观测、熔断、灰度。
  • 越来越多团队采用 Spring Cloud Alibaba + Service Mesh 的混合方案。

六、微服务架构 & 分布式系统(30题)


1. 什么是微服务?

核心回答
微服务是一种架构风格,将单体应用拆分为一组小型、独立部署的服务,每个服务聚焦某个业务能力,通过 API(HTTP/gRPC/MQ)通信。

深入原理

  • 每个服务独立开发、部署、扩容。
  • 服务自治:有自己的数据库、缓存、逻辑。
  • 通常结合容器化、CI/CD、服务治理框架(如 Spring Cloud、Dubbo、K8s)。

延伸思考

  • 面试追问:和单体应用区别? → 单体耦合度高,微服务解耦但更复杂。

2. 微服务与 SOA 的区别?

核心回答

  • SOA:面向服务的架构,通常依赖 ESB(企业服务总线)。
  • 微服务:轻量级、去中心化,强调小而独立,常用 REST/gRPC。

深入原理

  • SOA → 偏企业内部,服务较大,依赖统一中间件(如 ESB)。
  • 微服务 → 服务更小,避免单点中心,强调去中心化自治。

延伸思考

  • 面试追问:为什么微服务比 SOA 更流行? → 云原生背景下,微服务更适应敏捷开发和快速迭代。

3. 微服务的优缺点?

核心回答

  • 优点:独立部署、技术多样性、弹性伸缩、快速迭代。
  • 缺点:分布式复杂性、运维难度高、数据一致性挑战、服务治理成本高。

延伸思考

  • 面试官常问:什么时候不适合微服务? → 业务小、团队小、系统简单时。

4. 微服务的拆分原则?

核心回答

  • 按业务边界(DDD 的领域驱动设计)。
  • 高内聚、低耦合。
  • 一个服务只聚焦一个核心业务能力。

深入原理

  • 拆分方式:按业务(订单、用户、支付)、按场景(核心 vs 辅助)、按非功能需求(性能瓶颈点)。

延伸思考

  • 常见错误:按数据库表拆分,导致过度依赖跨服务调用。

5. 如何保证微服务之间的数据一致性?

核心回答

  • 避免强一致,采用 最终一致性
  • 方案:分布式事务、消息驱动、补偿机制。

深入原理

  • 同步方案:XA(两阶段提交)。
  • 异步方案:TCC、SAGA、可靠消息。

延伸思考

  • 面试官追问:你在项目中怎么做? → 消息队列(事务消息/本地消息表)。

6. 分布式事务有哪些解决方案?

核心回答

  • XA(两阶段提交)
  • TCC(Try-Confirm-Cancel)
  • SAGA(长事务补偿)
  • 本地消息表 / 事务消息

深入原理

  • XA:强一致性,但性能差。
  • TCC:业务入侵高,但灵活。
  • SAGA:长事务场景,失败时用补偿。
  • 本地消息表:保证最终一致性。

7. TCC、SAGA、XA 的区别?

  • XA:数据库层两阶段提交,强一致,性能差。
  • TCC:应用层三步(预留资源、确认、回滚),侵入性高。
  • SAGA:长事务,每步都有补偿动作,保证最终一致性。

8. 本地消息表和可靠消息最终一致性?

  • 本地消息表:在本地事务中写业务表 + 消息表 → MQ 异步投递 → 消费端确认。
  • 可靠消息:MQ 支持事务(如 RocketMQ 半消息),确保消息必达,保证最终一致性。

9. 分布式锁的实现方式?

  • 数据库锁(悲观锁/唯一索引)。
  • Redis 分布式锁(setnx + expire)。
  • Zookeeper 分布式锁(临时顺序节点)。

10. Redis 分布式锁的缺陷?

  • 单点问题(需 Redis Cluster 或 Redlock)。
  • 锁过期可能导致误释放。
  • 不能保证严格公平。

11. Zookeeper 分布式锁的实现?

  • 利用 临时顺序节点
    • 客户端创建顺序节点。
    • 判断自己是否是最小节点 → 获取锁。
    • 不是则监听前一个节点的删除事件。

12. 数据库分库分表策略?

  • 垂直分库(按业务拆分)。
  • 水平分表(按用户 ID、时间做分片)。
  • 常见路由方式:哈希取模、范围、时间。

13. 水平拆分与垂直拆分区别?

  • 水平拆分:同一张表的数据分到多个库表(按范围/哈希)。
  • 垂直拆分:不同业务表放到不同库(用户库、订单库)。

14. 分布式 ID 生成方案?

  • UUID(无序,不适合数据库)。
  • 数据库自增 ID(单点瓶颈)。
  • Redis 自增。
  • 雪花算法(Snowflake)。
  • Leaf(美团开源)。

15. 雪花算法的原理?

  • 64 位长整型:
    • 时间戳(41 位)
    • 机器 ID(10 位)
    • 序列号(12 位)
  • 保证全局唯一、有序。

16. API 网关的作用?

  • 统一入口,做路由转发。
  • 鉴权、安全控制。
  • 流量控制、熔断、降级。
  • 日志监控。

17. 服务注册与发现机制?

  • 服务启动 → 注册中心注册(IP/端口/状态)。
  • 调用方 → 从注册中心获取地址列表。
  • 注册中心 → 通过心跳维持健康状态。

18. 微服务如何保证高可用?

  • 服务冗余(多实例部署)。
  • 负载均衡。
  • 熔断、降级、限流。
  • 自动故障转移(K8s + 服务治理框架)。

19. 微服务中的限流和熔断?

  • 限流:控制流量,保护系统(令牌桶/漏桶)。
  • 熔断:当下游服务异常时,直接失败并快速返回。

20. 微服务如何保证安全性?

  • 认证授权(OAuth2、JWT)。
  • HTTPS、加密通信。
  • API 网关统一鉴权。
  • 数据脱敏。

21. OAuth2 的原理?

  • 基于令牌的认证授权协议。
  • 四种模式:授权码、密码、客户端凭证、简化模式。
  • 典型流程:用户 → 授权服务器 → 获取 Token → 调用资源服务器。

22. JWT 的作用?

  • JSON Web Token,一种无状态令牌。
  • 内容包含三部分:Header、Payload、Signature。
  • 适合分布式系统做认证,无需存储 Session。

23. 单点登录 SSO 的实现?

  • 用户只需登录一次,获取全局 Token/Session。
  • 常见实现:CAS、OAuth2、JWT。
  • 核心:统一认证中心。

24. 配置中心的作用?

  • 集中化配置管理。
  • 支持动态刷新。
  • 环境隔离(dev/test/prod)。

25. 微服务中的监控体系?

  • 指标监控:Prometheus + Grafana。
  • 日志监控:ELK(Elasticsearch + Logstash + Kibana)。
  • 链路追踪:Sleuth + Zipkin/Jaeger。

26. Prometheus 的原理?

  • 基于 Pull 模型,定期从应用/Exporter 拉取指标。
  • TSDB(时间序列数据库)存储数据。
  • 结合 Grafana 做可视化。

27. ELK 在微服务中的应用?

  • Logstash:日志采集。
  • Elasticsearch:日志存储、搜索。
  • Kibana:日志分析、可视化。
  • 用于统一日志平台。

28. 服务调用链路追踪的意义?

  • 解决“调用链不清楚”的问题。
  • 快速定位性能瓶颈、错误位置。
  • 提升运维可观测性。

29. 服务雪崩效应是什么?

  • 一个服务故障 → 引发调用它的服务故障 → 最终导致整个系统崩溃。
  • 原因:依赖调用无保护(无熔断/限流)。

30. 如何设计一个高并发的微服务系统?

核心思路

  • 架构:微服务 + API 网关 + 服务治理。
  • 高可用:负载均衡、多副本、自动故障转移。
  • 高性能:缓存(Redis)、异步化、限流、降级。
  • 数据层:分库分表、读写分离。
  • 安全:OAuth2/JWT。
  • 可观测性:监控 + 日志 + 链路追踪。

七、数据库 & 缓存(30题)


1. MySQL 的存储引擎区别

  • 存储引擎是 MySQL 管理数据的底层组件,决定了数据存储方式、事务支持、锁机制、索引支持等。
  • 主要存储引擎
    • InnoDB:事务型,支持行级锁、外键、MVCC、崩溃恢复。适合高并发、事务要求高的场景。
    • MyISAM:非事务型,表级锁,支持全文索引。适合读多写少、日志或统计类表。
    • Memory:数据存储在内存中,访问速度快,但断电数据丢失。适合临时表。
    • Archive:归档引擎,只支持插入和查询,不支持更新,压缩存储。
  • 面试重点
    • 选择存储引擎需根据业务需求:事务要求、并发量、数据量和恢复策略。

2. InnoDB 和 MyISAM 的区别

特性 InnoDB MyISAM
事务 支持 ACID 不支持
锁类型 行级锁+表锁 表级锁
外键 支持 不支持
崩溃恢复 自动恢复 需手动修复
全文索引 5.6+ 支持 支持
并发场景 高并发写+读 读多写少
适用案例 金融、电商订单 日志分析、统计表

3. MySQL 事务特性

  • ACID 原则
    1. 原子性(Atomic):事务全部执行或全部回滚。
    2. 一致性(Consistency):事务执行前后数据库状态合法。
    3. 隔离性(Isolation):事务互不干扰。
    4. 持久性(Durability):提交后数据不会丢失。
  • 隔离级别
    • Read Uncommitted:允许脏读。
    • Read Committed:防止脏读,但可能不可重复读。
    • Repeatable Read(默认 InnoDB):防止不可重复读,间隙锁解决幻读。
    • Serializable:完全串行执行,性能最低,但完全隔离。
  • 面试追问:隔离级别与死锁、性能的权衡。

4. MVCC 的实现原理

  • 原理
    • InnoDB 为每行增加隐藏列 trx_idroll_pointer
    • 查询时根据事务 ID 判断哪一版本可见。
    • 读操作不阻塞写操作,实现 非阻塞读
  • 应用:提高并发性能,避免读写互相阻塞。
  • 面试追问:MVCC 如何实现 Repeatable Read,为什么不锁表。

5. MySQL 的索引类型

  • B+ 树索引:范围查询、排序,叶子节点按顺序连接。
  • 哈希索引:精确查找快(Memory 引擎)。
  • 全文索引:文本搜索。
  • 空间索引:地理信息查询(R-Tree)。
  • 面试追问:什么时候选择组合索引,覆盖索引优化查询。

6. 聚簇索引 vs 非聚簇索引

  • 聚簇索引
    • 数据行存储顺序与主键顺序一致。
    • 查询范围、排序效率高。
  • 非聚簇索引
    • 索引存 key + 主键,回表获取其他字段。
  • 面试追问:聚簇索引插入顺序对性能影响,回表成本。

7. B+ 树索引原理

  • 内节点:存 key + 子节点指针
  • 叶子节点:存完整记录或指针,并链表连接
  • 特点
    • 支持范围查询
    • 磁盘 IO 高效
  • 面试追问:为什么 MySQL 选择 B+ 树而不是 B 树或 Hash。

8. 覆盖索引 vs 回表

  • 覆盖索引:索引包含所有查询字段,无需访问表。
  • 回表:索引只包含部分字段,需要通过主键查表。
  • 优化方法:尽量让热点查询使用覆盖索引,减少 IO。

9. 索引下推优化

  • WHERE 条件在存储引擎层过滤,减少返回上层的数据量。
  • 特别在组合索引或函数查询时有效。
  • 面试追问:索引下推能否应用于非 InnoDB 存储引擎。

10. MySQL 锁机制

  • 表锁:锁整张表,低并发。
  • 行锁:锁单行,InnoDB 默认。
  • 意向锁:表级锁意图,避免死锁。
  • 间隙锁:防止幻读。
  • 面试追问:行锁、表锁的实现原理,锁冲突如何处理。

11. 行锁 vs 表锁

  • 行锁:粒度小,高并发,事务安全。
  • 表锁:粒度大,适合批量操作。
  • 应用场景:高并发写用行锁,批量更新可用表锁。

12. 间隙锁

  • 锁定索引间隙,防止其他事务插入幻读。
  • 与记录锁结合形成 Next-Key Lock
  • 默认在 Repeatable Read 下使用。

13. 死锁排查

  • SHOW ENGINE INNODB STATUS\G 查看最新死锁。
  • 优化:
    1. 统一访问表顺序。
    2. 尽量缩短事务时间。
    3. 加索引减少扫描行数。

14. SQL 执行计划

  • 使用 EXPLAIN 分析:
    • type:访问类型(ALL < INDEX < REF < CONST)
    • key:使用的索引
    • rows:扫描行数
    • Extra:如 Using index, Using temporary

15. 慢查询优化

  • 建索引(覆盖索引、组合索引)
  • 避免 SELECT *
  • 避免在列上函数操作
  • 分库分表
  • 缓存(Redis/Caffeine)

16. 分库分表难点

  • 跨库 join、分页
  • 分布式事务
  • 全局唯一 ID
  • 数据路由、迁移

17. 分布式事务

  • XA:两阶段提交,保证强一致性。
  • TCC:Try → Confirm → Cancel,业务补偿操作。
  • SAGA:长事务补偿。
  • 本地消息表 + MQ:最终一致性。

18. Redis 数据结构

  • StringHashListSetSorted Set
  • Bitmap、HyperLogLog、Geo
  • 应用:排行榜、计数、集合操作、地理位置、布隆过滤器

19. Redis 为什么快

  • 全内存存储
  • 单线程 + epoll
  • 高效数据结构
  • 避免锁竞争

20. Redis 持久化

  • RDB:定期快照
  • AOF:写命令日志,append only
  • 混合持久化:RDB + AOF,提高安全性与效率

21. Redis 过期策略

  • 惰性删除:访问时检查 TTL
  • 定期删除:周期性扫描
  • 定时删除:每个 key 单独定时器

22. Redis 内存淘汰策略

  • noeviction、allkeys-lru、volatile-lru、allkeys-lfu、volatile-ttl
  • LRU:最近最少使用
  • LFU:使用频率最低
  • TTL:临期优先淘汰

23. Redis 主从复制

  • 异步复制
  • 全量复制 + 增量复制
  • 读写分离,提升扩展性

24. Redis 哨兵机制

  • 监控、通知、故障转移
  • Sentinel 投票机制切换主节点
  • 高可用架构核心组件

25. Redis 集群模式

  • 16384 个哈希槽分片
  • Master + Slave
  • 跨槽操作有限制
  • 高可用 + 水平扩展

26. Redis 分布式锁

  • 单机:SETNX + EXPIRE
  • Redisson
    • 自动续期(看门狗机制)
    • 可重入锁
    • 公平锁
  • RedLock:多节点强一致性

27. Redis 常见问题

  • 内存不足 / OOM
  • 热点 key
  • bigkey 导致阻塞
  • RDB fork 阻塞
  • 主从延迟

28. 缓存穿透、击穿、雪崩

  • 穿透:不存在数据频繁访问 → 布隆过滤器
  • 击穿:热点 key 瞬间失效 → 互斥锁/永不过期缓存
  • 雪崩:大量 key 同时过期 → TTL + 随机值

29. 缓存与数据库一致性

  • Cache Aside:先 DB,再删缓存
  • 双写:DB + 缓存同步更新
  • 异步消息:MQ + 延时队列,实现最终一致性

30. Redis vs Caffeine

特性 Redis Caffeine
类型 分布式缓存 本地缓存
持久化 RDB / AOF 不支持
集群 支持 不支持
访问速度 毫秒级 纳秒级
场景 分布式共享,高并发 本地热点缓存

最佳实践:Caffeine(本地缓存)+ Redis(分布式缓存)+ DB → 多级缓存结构。


八、消息队列(30题)


1. 为什么要使用消息队列?

  • 解耦系统:生产者与消费者不直接调用,通过队列传递消息。
  • 异步处理:用户请求不等待后端业务完成,提高响应速度。
  • 削峰填谷:缓冲高峰流量,避免数据库或服务压力过大。
  • 可靠传输:通过持久化、ACK 机制保证消息不丢失。
  • 可扩展性:增加消费者可水平扩展,提高系统吞吐量。
  • 面试追问:为什么 MQ 可以解决微服务间耦合问题?

2. 消息队列的优缺点

  • 优点
    • 异步处理提升吞吐量
    • 系统解耦
    • 支持削峰填谷
    • 消息可靠性可控
  • 缺点
    • 系统复杂度增加
    • 消息延迟
    • 顺序和重复消费需要额外处理
  • 优化点:结合幂等设计、延迟队列和批量处理

3. RabbitMQ 核心组件

  • Producer:发送消息
  • Exchange:路由消息(Direct、Fanout、Topic、Headers)
  • Queue:存储消息
  • Binding:绑定 Exchange 和 Queue
  • Consumer:接收消息
  • 原理:Exchange 决定路由规则,Queue 负责缓冲和投递

4. RabbitMQ 消息确认机制

  • 生产者端
    • Publisher Confirm,确保消息写入 Broker
  • 消费者端
    • 自动 ack:消费即确认
    • 手动 ack:成功处理后确认
  • 应用实践:手动 ack 可处理异常重试,保证消息不丢失

5. Kafka 核心概念

  • Producer/Consumer:消息发送和接收
  • Topic/Partition:主题和分区
  • Broker/Cluster:节点和集群
  • Consumer Group:消费组实现负载均衡
  • Offset:记录消费位置
  • 原理:生产者写入 Partition,消费者根据 Offset 消费

6. Kafka 分区机制

  • Partition 提高并行
  • 分区内顺序保证
  • 路由策略:默认轮询,或 Key 哈希分区
  • 面试点:如何保证全局顺序?只能保证分区内顺序

7. Kafka 副本机制

  • Leader/Follower 结构
  • 同步机制:ISR 列表跟随 Leader
  • 高可用:Leader 异常自动切换
  • 面试拓展:副本同步模式对性能的影响

8. Kafka ISR 原理

  • ISR(In-Sync Replicas):跟上 Leader 的副本集合
  • 消息提交规则:只要 ISR 内副本写入才算成功
  • 优化:副本数量与 ACK 策略可调节可靠性与吞吐量

9. Kafka 消费组原理

  • 同一组内的每个 Partition 只被一个 Consumer 消费
  • 多组可重复消费,实现广播
  • 提高并行消费,减少重复消费
  • 拓展:消费组重平衡、再均衡机制

10. Kafka Offset 提交方式

  • 自动提交:消费完成后自动提交 Offset
  • 手动提交:
    • 同步提交:阻塞等待
    • 异步提交:性能高,可能丢少量消息
  • 实践:结合事务实现 Exactly Once

11. Kafka 高吞吐量原理

  • 顺序写磁盘,避免随机 IO
  • 批量发送消息,减少网络开销
  • 零拷贝机制,减少 CPU 消耗
  • 面试拓展:如何调整批量大小提高吞吐量

12. Kafka 零拷贝机制

  • 直接从磁盘 buffer 传到网络 buffer,无用户态复制
  • 利用 sendfile() 提升大流量传输效率

13. RocketMQ 核心特点

  • 高可靠,高性能
  • 顺序消息、事务消息原生支持
  • Broker 无状态,易水平扩展
  • 支持多语言客户端
  • 消息存储:CommitLog + ConsumeQueue + IndexFile

14. RocketMQ 顺序消息

  • 通过 Key 哈希映射到队列
  • 队列内顺序严格保证
  • 应用场景:订单支付、流水处理

15. RocketMQ 事务消息

  • 半消息机制
    1. 发送半消息到 Broker
    2. 执行本地事务
    3. 提交或回滚消息
  • 保证分布式事务最终一致性

16. 如何保证消息不丢失

  • 生产者端:确认机制
  • Broker端:消息持久化
  • 消费者端:手动 ack + 幂等消费

17. 如何保证消息不重复消费

  • 消息幂等:唯一 ID + 数据库唯一约束 + Redis 去重
  • Kafka Exactly Once + 事务处理

18. 如何保证消息有序

  • Kafka:Partition 内顺序
  • RocketMQ:队列内顺序
  • 注意:多 Partition 并行可能导致整体无序

19. 消息堆积解决办法

  • 增加消费者数量
  • 扩展队列分区
  • 流量削峰
  • 消费端批量处理,提高吞吐量

20. 如何设计延迟队列

  • RabbitMQ:TTL + Dead Letter Queue
  • Kafka:延迟消息插件 / 时间轮
  • Redis:SortedSet + 轮询
  • 场景:订单超时、短信延迟发送

21. 死信队列作用

  • 消费失败消息收集到死信队列
  • 支持重试、告警和问题排查
  • 提高系统可靠性

22. 幂等实现方式

  • 消息唯一 ID
  • 数据库唯一约束
  • Redis 去重缓存
  • 本地事务结合 MQ 保证幂等

23. 消息队列限流方式

  • 消费者限流:处理速率限制
  • 生产者限流:令牌桶/漏桶
  • Broker 队列容量限制 + 拒绝策略

24. 消息队列监控

  • 消息堆积量
  • 消费速率
  • Broker 健康状态
  • 延迟时间
  • 工具:Prometheus + Grafana、Kafka Manager、RocketMQ Console、RabbitMQ Management

25. Kafka vs RabbitMQ

特性 Kafka RabbitMQ
模型 Pub/Sub + Queue AMQP
消息存储 持久化日志 队列存储
顺序保证 Partition 内顺序 队列顺序
高吞吐 高(批量 + 零拷贝) 中等
消费模式 Pull / Push Push
使用场景 大数据流、日志、事件驱动 任务队列、异步处理、可靠投递

26. Kafka vs RocketMQ

特性 Kafka RocketMQ
顺序消息 Partition 内顺序 队列内顺序全局可控
事务消息 限制较多 原生支持
存储方式 CommitLog CommitLog + ConsumeQueue + IndexFile
高可用 ISR + 副本 Master-Slave + HA
生态 Flink、Spark 集成 分布式事务、支付系统应用

27. Pulsar 特点

  • 多租户支持
  • 持久化使用 BookKeeper
  • Topic 可分 Partition
  • 支持事务、延迟消息
  • Push/Pull 消费模式灵活

28. 消息中间件如何保证高可用

  • 多副本 + 自动切换
  • Broker 集群
  • 消息持久化
  • 客户端重试
  • Zookeeper/Controller 管理元数据

29. 消息队列水平扩展

  • Kafka:增加 Partition / Broker
  • RabbitMQ:集群 + Shovel/Federation
  • RocketMQ:增加 Broker + Topic 分区
  • 注意:分区扩展可能影响顺序,需要 Key 路由

30. 消息队列 vs 事件总线

  • MQ:关注消息可靠性、顺序、确认
  • EventBus:关注解耦、广播、轻量级
  • MQ 偏向可靠持久化,EventBus 偏向系统内部事件触发

九、DevOps & 容器化(30题)


1. CI/CD 的流程

  • CI(Continuous Integration,持续集成)
    • 开发者提交代码 → 自动构建 → 自动单元测试 → 自动生成构建产物
  • CD(Continuous Delivery/Deployment,持续交付/部署)
    • 自动部署到测试/生产环境 → 自动化验证 → 生产发布
  • 工具链
    • Jenkins、GitLab CI、Argo CD
  • 实践要点
    • 流程自动化
    • 保证构建产物可回滚
  • 面试拓展:CI/CD 与 DevOps 的关系

2. Jenkins 的作用

  • 自动化构建、测试、部署工具
  • 核心功能
    • 支持多种 SCM(Git、SVN)
    • Pipeline 管理
    • 插件生态丰富(Docker、K8s 集成)
  • 面试追问:Pipeline 如何保证构建可重复、可回滚

3. Docker 核心原理

  • 容器化技术
    • Linux namespace 隔离(进程、网络、文件系统)
    • Linux cgroups 限制资源(CPU、内存)
    • **联合文件系统(UnionFS)**实现镜像分层
  • 容器 ≈ 轻量级虚拟机,共享宿主 OS 内核

4. Docker 镜像 vs 容器

区别 镜像 容器
定义 只读模板 镜像运行实例
存储 分层只读文件系统 读写层 + 镜像
生命周期 永久 临时,可停止/删除
用途 构建应用 运行应用

5. Dockerfile 常用指令

  • FROM:基础镜像
  • RUN:执行命令构建镜像
  • COPY/ADD:复制文件到镜像
  • CMD/ENTRYPOINT:容器启动命令
  • EXPOSE:声明端口
  • ENV:环境变量
  • 优化:减少镜像层,顺序合理缓存

6. 镜像分层机制

  • 镜像每条指令生成一个只读层
  • 联合文件系统(UnionFS)叠加这些层
  • 容器启动时增加读写层
  • 优势
    • 共享公共层,节省空间
    • 支持增量构建

7. 容器 vs 虚拟机

区别 容器 虚拟机
内核 共享宿主 OS 内核 独立 OS
启动速度 秒级 分钟级
资源占用 轻量 重量
隔离 进程级 硬件级

8. Kubernetes 核心组件

  • Master
    • API Server:接收用户请求
    • Scheduler:Pod 调度
    • Controller Manager:状态管理
    • etcd:分布式配置存储
  • Node
    • Kubelet:Pod 生命周期管理
    • Kube-proxy:服务网络代理
    • Container Runtime(Docker/CRI-O)

9. Pod 生命周期

  • PendingRunningSucceeded/Failed
  • 状态管理
    • initContainer → main container → postStop Hook
  • 面试点:Pod 重启策略(Always/OnFailure/Never)

10. Deployment vs StatefulSet

特性 Deployment StatefulSet
Pod 标识 无固定名字 有固定名字
顺序更新 可并行更新 严格顺序更新
持久化存储 共享卷 独立持久卷
应用场景 无状态服务 数据库、Kafka Broker 等状态服务

11. K8s 服务发现机制

  • ClusterIP:集群内部访问
  • NodePort:节点端口访问
  • LoadBalancer:外部访问
  • DNS + kube-proxy:Pod 名称解析 + iptables 转发

12. ConfigMap 和 Secret

特性 ConfigMap Secret
数据类型 明文配置 Base64 编码敏感信息
使用方式 Env/Volume Env/Volume
安全性 不加密 加密可集成 KMS

13. Ingress 的作用

  • 集群外部访问 Pod 的入口
  • 功能:
    • 路由到不同服务
    • TLS 终端
    • 负载均衡
  • 面试点:Ingress Controller vs Service LB

14. K8s 水平扩缩容机制

  • HPA(Horizontal Pod Autoscaler):根据 CPU、内存或自定义指标调整 Pod 数量
  • VPA(Vertical Pod Autoscaler):自动调整 Pod 资源配置
  • Cluster Autoscaler:节点自动扩缩容

15. K8s 滚动更新机制

  • Deployment 滚动更新策略:
    • maxUnavailable:最大不可用 Pod
    • maxSurge:最大新增 Pod
  • 保证无中断升级

16. K8s 灰度发布

  • 方法
    • 分流流量(Ingress/Service Mesh)
    • 多版本 Deployment
    • Canary Pod + 路由权重

17. K8s 调度策略

  • 默认调度:基于资源(CPU/Memory)
  • 亲和性/反亲和性
    • NodeAffinity
    • PodAffinity / PodAntiAffinity
  • Taints & Tolerations:节点污点控制 Pod 调度

18. K8s 网络模型

  • 要求
    • Pod 可以直接互相通信
    • Pod 与 Node 通信
  • 方案
    • Flannel、Calico、Cilium 等
  • 面试点:Overlay 网络 vs Underlay 网络

19. Service Mesh 作用

  • 透明通信管理
  • 流量控制、熔断、限流
  • 可观察性(Tracing、Metrics)
  • 安全(mTLS)

20. Istio 工作原理

  • Sidecar Proxy:每个 Pod 注入 Envoy
  • 控制平面(Pilot):下发路由规则
  • Mixer / Telemetry:采集指标和日志
  • 应用
    • 灰度发布、流量镜像、故障注入、服务间安全通信

21. Helm 的作用

  • Kubernetes 包管理工具
  • 功能
    • Chart 模板化部署
    • 版本管理
    • 一键升级/回滚
  • 面试拓展:Helm 与 Kustomize 区别

22. K8s 存储方案

  • 临时存储:emptyDir
  • 持久卷:PersistentVolume + PersistentVolumeClaim
  • 存储类型
    • 本地存储
    • NFS
    • Ceph / GlusterFS
    • 云存储(EBS、OSS)

23. Prometheus 监控体系

  • Pull 模型采集指标
  • TSDB 存储时间序列数据
  • AlertManager 告警
  • 可监控 K8s 组件、Pod、应用指标

24. Grafana 的作用

  • 可视化 Prometheus 数据
  • 仪表盘定制
  • 支持多数据源(InfluxDB、ElasticSearch)
  • 面试点:报警可视化和多租户支持

25. K8s 安全机制

  • 认证(Authentication):Token / X.509 / OIDC
  • 授权(Authorization):RBAC
  • 网络策略(NetworkPolicy)
  • Secret 加密

26. 容器资源限制

  • CPU / Memory:requests / limits
  • QoS 分类
    • Guaranteed、Burstable、BestEffort
  • 面试点:容器 OOM 及限流策略

27. Docker Compose 作用

  • 多容器应用管理
  • 本地开发、测试
  • 声明式配置网络、依赖关系

28. DevOps 最佳实践

  • 持续集成/持续交付
  • 基础设施即代码(IaC)
  • 自动化测试与部署
  • 微服务 + 容器化 + 云原生

29. GitOps 理念

  • 将 Git 作为单一真源
  • K8s 自动同步 Git 状态
  • 优点:
    • 可追踪、可回滚
    • 流程统一、可审计

30. K8s 如何保证高可用

  • Master 节点多副本(HA)
  • ETCD 集群存储
  • Pod 多副本 + Deployment/StatefulSet
  • Service + LoadBalancer 提供稳定访问
  • Cluster Autoscaler 支持节点弹性扩展

十、场景设计题(30题)

  1. 如何设计一个秒杀系统?
  2. 如何设计一个短链系统?
  3. 如何设计一个分布式 ID 生成系统?
  4. 如何设计一个高可用缓存系统?
  5. 如何设计一个订单系统?
  6. 如何设计一个支付系统?
  7. 如何设计一个消息推送系统?
  8. 如何设计一个日志收集系统?
  9. 如何设计一个搜索引擎?
  10. 如何设计一个推荐系统?
  11. 如何设计一个分布式锁?
  12. 如何设计一个统一鉴权系统?
  13. 如何设计一个限流系统?
  14. 如何设计一个灰度发布系统?
  15. 如何设计一个电商购物车?
  16. 如何设计一个库存系统?
  17. 如何设计一个分布式文件存储系统?
  18. 如何设计一个微服务网关?
  19. 如何设计一个分布式任务调度系统?
  20. 如何设计一个监控报警系统?
  21. 如何设计一个高并发聊天室?
  22. 如何设计一个排行榜系统?
  23. 如何设计一个评论系统?
  24. 如何设计一个分布式事务系统?
  25. 如何设计一个高可用注册中心?
  26. 如何设计一个数据同步系统?
  27. 如何设计一个 API 限速系统?
  28. 如何设计一个高可用的支付网关?
  29. 如何设计一个跨境电商架构?
  30. 如何设计一个金融级别的微服务系统?