Java基础面试要点:从语法、集合、并发到JDK 8/17/21新特性的系统梳理
前言
Java 基础面试题看起来范围很大:有的问 equals 和 hashCode,有的问 String、泛型、异常、集合、线程池、锁、JVM、类加载,也有的会继续追问 JDK 8、JDK 17、JDK 21 分别带来了哪些重要变化。
如果只背零散结论,遇到追问很容易断。更好的方式是把 Java 基础理解成几条主线:
1 | 语言基础 -> 面向对象 -> 标准类库 -> 集合与泛型 -> 并发与内存模型 -> JVM 与类加载 -> JDK 版本演进 |
本文按面试中最常见的模块梳理 Java 基础要点,并专门总结 JDK 8、17、21 的新特性,适合作为面试前的复习清单。
Java 整体特点
Java 常见特点包括:
- 面向对象。
- 跨平台,依赖 JVM。
- 自动内存管理和垃圾回收。
- 静态类型语言。
- 标准库和生态成熟。
- 支持多线程和并发编程。
面试中可以这样概括:
Java 源码编译成字节码,字节码运行在 JVM 上。JVM 屏蔽不同操作系统差异,并通过类加载、运行时数据区、JIT 编译和垃圾回收支撑程序运行。
flowchart TD
A[Java Source] --> B[javac 编译]
B --> C[Bytecode .class]
C --> D[JVM]
D --> E[ClassLoader]
D --> F[Runtime Data Area]
D --> G[JIT Compiler]
D --> H[GC]
D --> I[Operating System]
基础语法
Java 基础语法面试常问数据类型、包装类型、自动装箱、字符串和运算规则。
基本类型
Java 有 8 种基本类型:
| 类型 | 说明 |
|---|---|
| byte | 1 字节整数 |
| short | 2 字节整数 |
| int | 4 字节整数 |
| long | 8 字节整数 |
| float | 单精度浮点数 |
| double | 双精度浮点数 |
| char | 字符 |
| boolean | 布尔值 |
基本类型不是对象,存储和计算成本较低。包装类型如 Integer、Long 是对象,可以用于泛型、集合和空值表达。
自动装箱和拆箱
自动装箱是基本类型转包装类型,拆箱是包装类型转基本类型。
1 | Integer a = 10; // 装箱 |
常见坑:
1 | Integer a = 128; |
Integer 默认缓存 -128 到 127,超过范围后可能创建新对象。包装类型比较数值应使用 equals,但要注意空指针。
String
String 是不可变对象。字符串内容一旦创建不能修改,拼接、替换等操作会产生新对象。
不可变的好处:
- 线程安全。
- 可以缓存 hash。
- 适合作为 Map key。
- 字符串常量池可以复用。
- 安全性更好,避免引用被外部修改。
常见对比:
String:不可变,适合少量字符串操作。StringBuilder:可变,非线程安全,性能较好。StringBuffer:可变,方法同步,线程安全但开销更高。
面试常问:
1 | String a = "hello"; |
a == b 通常为 true,因为都指向字符串常量池;a == c 为 false,因为 new 创建了堆对象。比较内容要用 equals。
面向对象
Java 面向对象核心包括封装、继承、多态。
- 封装:隐藏内部实现,通过方法暴露能力。
- 继承:子类复用父类能力。
- 多态:父类引用指向子类对象,运行时根据真实类型调用方法。
常见问题:
重载和重写
重载发生在同一个类中,方法名相同,参数列表不同,编译期决定。
重写发生在父子类之间,子类覆盖父类方法,运行期根据对象真实类型决定。
抽象类和接口
抽象类适合表达“是什么”,可以有状态和部分实现。
接口适合表达“能做什么”,强调能力和规范。
JDK 8 后接口可以有默认方法和静态方法,JDK 9 后接口可以有私有方法,用于复用接口内部逻辑。
equals 和 hashCode
equals 用于判断对象逻辑相等,hashCode 用于哈希容器定位桶位置。
约定:
- 两个对象
equals为 true,hashCode必须相等。 - 两个对象
hashCode相等,equals不一定为 true。 - 重写
equals通常必须重写hashCode。
如果只重写 equals 不重写 hashCode,对象放入 HashMap、HashSet 时可能出现查不到、重复存储等问题。
异常
Java 异常体系:
1 | Throwable |
- Error:严重错误,通常不应捕获,例如
OutOfMemoryError。 - Checked Exception:编译期要求处理,例如
IOException。 - RuntimeException:运行时异常,例如
NullPointerException、IllegalArgumentException。
常见实践:
- 不要吞异常。
- 不要直接捕获
Exception后只打印。 - 业务异常要有明确错误码。
- 资源关闭优先使用 try-with-resources。
- 日志中保留堆栈和上下文。
泛型
泛型用于在编译期提供类型约束,减少强制类型转换。
1 | List<String> names = new ArrayList<>(); |
Java 泛型使用类型擦除实现。运行时大多数泛型信息会被擦除为原始类型。
常见问题:
- 不能直接创建泛型数组。
List<String>和List<Integer>运行时类型都是List。- 泛型主要保证编译期类型安全。
通配符:
? extends T:上界通配符,适合读。? super T:下界通配符,适合写。
常见口诀是 PECS:Producer Extends, Consumer Super。
集合框架
Java 集合核心包括 List、Set、Map、Queue。
List
- ArrayList:底层数组,查询快,尾部追加快,中间插入删除可能慢。
- LinkedList:底层链表,插入删除有优势,但随机访问慢。
实际项目中 ArrayList 更常用,因为 CPU 缓存友好,遍历和随机访问性能更好。
Set
- HashSet:基于 HashMap,元素不重复,无序。
- LinkedHashSet:保持插入顺序。
- TreeSet:基于红黑树,支持排序。
Map
- HashMap:数组 + 链表 + 红黑树,非线程安全。
- LinkedHashMap:保持插入顺序或访问顺序,适合 LRU。
- TreeMap:基于红黑树,按 key 排序。
- ConcurrentHashMap:线程安全,高并发场景常用。
HashMap
HashMap 高频问题包括底层结构、扩容、哈希冲突和线程安全。
JDK 8 中 HashMap 结构:
1 | 数组 + 链表 + 红黑树 |
当链表长度超过阈值并且数组容量足够时,链表会转为红黑树,降低极端哈希冲突下的查询成本。
常见参数:
- 初始容量默认 16。
- 负载因子默认 0.75。
- 超过阈值触发扩容。
HashMap 不是线程安全的。多线程写入可能导致数据错乱。并发场景应使用 ConcurrentHashMap。
ConcurrentHashMap
JDK 8 的 ConcurrentHashMap 使用 CAS + synchronized + volatile 等机制提升并发能力。
特点:
- 读操作大多无锁。
- 写操作锁粒度较小。
- 通过 volatile 保证可见性。
- 扩容时支持多线程协助迁移。
它适合高并发 Map 场景,但不代表所有复合操作都是原子的。例如先 get 再 put 仍可能有并发问题,可以使用 computeIfAbsent 等原子方法。
并发基础
Java 并发面试常问线程创建、线程池、锁、volatile、synchronized、AQS、ThreadLocal。
线程创建方式
常见方式:
- 继承 Thread。
- 实现 Runnable。
- 实现 Callable 配合 FutureTask。
- 使用线程池。
生产环境更推荐线程池,而不是频繁创建线程。
synchronized
synchronized 是 JVM 层面的内置锁,可以修饰方法或代码块。
它保证:
- 原子性。
- 可见性。
- 有序性的一部分约束。
锁对象不同,保护范围也不同:
- 实例方法锁当前对象。
- 静态方法锁 Class 对象。
- 代码块锁指定对象。
volatile
volatile 保证可见性和禁止指令重排序,但不保证复合操作原子性。
例如:
1 | volatile int count = 0; |
count++ 不是原子操作,仍然有并发问题。
volatile 常用于状态标志、双重检查锁定中的实例引用等场景。
线程池
线程池核心参数:
- corePoolSize:核心线程数。
- maximumPoolSize:最大线程数。
- keepAliveTime:非核心线程空闲存活时间。
- workQueue:任务队列。
- threadFactory:线程工厂。
- rejectedExecutionHandler:拒绝策略。
执行流程:
1 | 提交任务 -> 核心线程未满则创建 -> 否则入队 -> 队列满则创建非核心线程 -> 达到最大线程数后触发拒绝策略 |
常见拒绝策略:
- AbortPolicy:抛异常。
- CallerRunsPolicy:调用者线程执行。
- DiscardPolicy:直接丢弃。
- DiscardOldestPolicy:丢弃队列最旧任务。
面试中要注意:不建议直接使用 Executors.newFixedThreadPool 等快捷方法,因为队列可能无界,容易导致内存风险。更推荐显式使用 ThreadPoolExecutor。
ThreadLocal
ThreadLocal 为每个线程提供独立变量副本。
常见用途:
- 保存用户上下文。
- 保存 traceId。
- 保存事务或连接上下文。
- 保存日期格式化对象。
常见风险:
- 线程池中线程会复用。
- 使用后不 remove 可能导致上下文污染。
- ThreadLocalMap 的 key 是弱引用,value 仍可能残留。
最佳实践:使用完后在 finally 中清理。
Java 内存模型
Java 内存模型关注多线程下共享变量的可见性、有序性和原子性。
核心概念:
- 主内存。
- 工作内存。
- happens-before。
- volatile。
- synchronized。
- final 语义。
happens-before 常见规则:
- 程序顺序规则。
- volatile 写先行发生于后续 volatile 读。
- 解锁先行发生于后续加锁。
- 线程 start 先行发生于线程内动作。
- 线程内所有操作先行发生于其他线程检测到该线程结束。
JVM 内存区域
运行时数据区常见划分:
- 程序计数器。
- Java 虚拟机栈。
- 本地方法栈。
- 堆。
- 方法区。
- 运行时常量池。
线程私有:
- 程序计数器。
- 虚拟机栈。
- 本地方法栈。
线程共享:
- 堆。
- 方法区。
常见异常:
StackOverflowError:栈深度过深。OutOfMemoryError: Java heap space:堆内存不足。OutOfMemoryError: Metaspace:元空间不足。
类加载
类加载流程:
1 | 加载 -> 验证 -> 准备 -> 解析 -> 初始化 |
类加载器:
- Bootstrap ClassLoader。
- Platform ClassLoader。
- Application ClassLoader。
- 自定义 ClassLoader。
双亲委派模型的作用:
- 避免核心类被重复加载。
- 防止核心 API 被篡改。
- 保证类加载层次稳定。
常见追问:什么时候会打破双亲委派?
例如 SPI、应用服务器隔离、热部署、OSGi、某些插件化场景。
垃圾回收
GC 面试常问对象是否可回收、垃圾收集器、GC 日志和调优。
判断对象是否存活:
- 引用计数法。
- 可达性分析。
Java 主流使用可达性分析,从 GC Roots 出发,无法到达的对象可回收。
常见 GC Roots:
- 虚拟机栈中引用的对象。
- 方法区静态属性引用的对象。
- 常量引用的对象。
- 本地方法栈 JNI 引用的对象。
常见收集器:
- Serial。
- Parallel。
- CMS。
- G1。
- ZGC。
JDK 17、21 中 G1 和 ZGC 都是面试中常见话题。G1 面向可预测停顿,ZGC 目标是低延迟。
JDK 8 新特性
JDK 8 是 Java 生态影响最大的版本之一,面试高频。
Lambda 表达式
Lambda 让函数式接口写法更简洁。
1 | List<String> names = List.of("Tom", "Jerry"); |
它适合简化回调、集合遍历、函数式接口实现。
函数式接口
函数式接口只有一个抽象方法,可以使用 @FunctionalInterface 标记。
常见接口:
- Function。
- Consumer。
- Supplier。
- Predicate。
Stream API
Stream 用于集合流水线处理。
1 | List<String> result = names.stream() |
常见操作:
- filter。
- map。
- flatMap。
- sorted。
- distinct。
- collect。
- reduce。
注意:Stream 不是数据库查询引擎。大量数据、复杂过滤仍应在数据库层完成。
Optional
Optional 用于表达可能为空的返回值,减少直接空指针。
1 | Optional<User> user = userRepository.findById(id); |
不要滥用 Optional 作为实体字段,也不要为了 Optional 而写更复杂的代码。
默认方法
接口可以定义 default 方法,便于接口演进。
1 | default void log() { |
新日期时间 API
JDK 8 引入 java.time:
- LocalDate。
- LocalTime。
- LocalDateTime。
- ZonedDateTime。
- Instant。
- Duration。
- Period。
相比 Date 和 Calendar,新 API 不可变、线程安全、语义清晰。
CompletableFuture
CompletableFuture 用于异步编排。
1 | CompletableFuture.supplyAsync(() -> queryUser()) |
使用时要注意线程池,不要把阻塞任务随意丢到公共 ForkJoinPool。
Metaspace
JDK 8 移除了永久代,改用 Metaspace。类元数据主要存放在本地内存中。
常见问题是类加载过多、动态代理过多、热部署泄漏导致 Metaspace OOM。
JDK 17 新特性
JDK 17 是长期支持版本,从 JDK 8 升级时很常见。
var 局部变量类型推断
var 是 JDK 10 引入的局部变量类型推断,JDK 17 中已稳定可用。
1 | var name = "Java"; |
它只能用于局部变量,不能用于成员变量、方法参数、返回值。使用时要保证代码可读性。
文本块
文本块适合多行字符串。
1 | String sql = """ |
适合 SQL、JSON、HTML 等多行文本。
switch 表达式
switch 可以作为表达式返回值。
1 | String type = switch (status) { |
写法更简洁,也减少 break 遗漏问题。
record
record 用于声明不可变数据载体。
1 | public record UserDTO(Long id, String name) {} |
编译器自动生成构造方法、访问器、equals、hashCode、toString。
适合 DTO、配置项、返回值对象,不适合复杂可变领域对象。
sealed class
密封类限制哪些类可以继承。
1 | public sealed class Result permits Success, Failure {} |
适合表达有限类型层次,配合模式匹配可以让分支处理更安全。
instanceof 模式匹配
1 | if (obj instanceof String s) { |
减少强制类型转换样板代码。
Helpful NullPointerException
JDK 14 引入更明确的空指针提示,JDK 17 中可用。异常信息能指出哪一段表达式为 null,提升排查效率。
ZGC 和 G1 改进
JDK 17 中 G1 是默认垃圾收集器,ZGC 已经成熟可用。对低延迟服务,ZGC 是重要选择之一,但是否使用仍要结合吞吐、延迟、内存和运维经验压测。
JDK 21 新特性
JDK 21 也是长期支持版本,核心亮点集中在虚拟线程、模式匹配和现代化并发模型。
虚拟线程
虚拟线程是 JDK 21 最重要的新特性之一。
1 | try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { |
特点:
- 创建成本远低于平台线程。
- 适合大量阻塞 IO 任务。
- 能简化传统异步回调模型。
- 不适合直接提升 CPU 密集型任务性能。
面试中要强调:虚拟线程不是让 CPU 变多,而是降低阻塞 IO 场景下线程资源成本。
结构化并发
结构化并发在 JDK 21 中是预览特性,用于把一组并发任务视为一个整体管理,便于取消、异常传播和结果汇总。
适合一个请求内并行调用多个下游服务的场景。
由于是预览特性,生产使用要看团队规范和版本策略。
Sequenced Collections
JDK 21 引入有序集合接口,统一表达集合的首尾访问能力。
相关接口:
- SequencedCollection。
- SequencedSet。
- SequencedMap。
可以更统一地访问第一个、最后一个元素以及反向视图。
switch 模式匹配
JDK 21 中 switch 模式匹配正式可用。
1 | String text = switch (obj) { |
它让类型判断和分支处理更简洁,配合 sealed class 更适合表达有限状态和结果类型。
record patterns
record patterns 可用于解构 record。
1 | record Point(int x, int y) {} |
适合处理不可变数据载体和模式匹配。
字符串模板
字符串模板在 JDK 21 中是预览特性,用于更安全、可读地表达字符串插值。因为仍是预览特性,面试中知道即可,生产使用要谨慎。
JDK 8、17、21 对比
| 版本 | 定位 | 高频特性 |
|---|---|---|
| JDK 8 | 经典长期支持版本,生态基础 | Lambda、Stream、Optional、java.time、CompletableFuture、接口默认方法、Metaspace |
| JDK 17 | 现代长期支持版本,很多语法改进稳定 | record、sealed class、switch 表达式、文本块、instanceof 模式匹配、G1/ZGC 改进 |
| JDK 21 | 新一代长期支持版本,并发模型升级明显 | 虚拟线程、Sequenced Collections、switch 模式匹配、record patterns、结构化并发预览 |
升级时要关注:
- 依赖库兼容。
- 构建工具版本。
- 框架版本,例如 Spring Boot 对 JDK 的支持矩阵。
- JVM 参数变化。
- GC 策略变化。
- 反射和模块访问限制。
- 容器镜像和运行时基线。
常见排查思路
如果面试官问“Java 应用 CPU 高怎么排查”,可以按以下路径回答:
- 用 top 找到高 CPU 进程。
- 找到高 CPU 线程。
- 转换线程 ID 为十六进制。
- 用 jstack 查看线程栈。
- 定位是死循环、频繁 GC、锁竞争还是高并发计算。
- 结合业务日志和最近发布判断原因。
如果内存高或 OOM:
- 看堆内存、元空间、直接内存还是线程数。
- 查看 GC 日志。
- dump heap 分析大对象和引用链。
- 检查缓存、集合、ThreadLocal、连接池、动态代理。
- 区分内存泄漏和正常流量增长。
如果接口慢:
- 看线程池是否耗尽。
- 看数据库和远程调用耗时。
- 看锁等待。
- 看 GC 暂停。
- 看日志和 traceId。
高频面试题
Java 为什么能跨平台?
Java 源码编译成字节码,字节码运行在不同平台的 JVM 上。JVM 屏蔽操作系统差异,实现“一次编译,到处运行”。
== 和 equals 有什么区别?
== 比较基本类型值,比较引用类型地址;equals 默认也是比较地址,但很多类会重写为逻辑相等,例如 String。
HashMap 为什么线程不安全?
HashMap 没有同步控制,多线程同时 put、resize 或修改链表/树结构时可能出现数据覆盖、结构异常或读取不一致。并发场景应使用 ConcurrentHashMap。
volatile 能替代锁吗?
不能完全替代。volatile 保证可见性和禁止重排序,但不保证复合操作原子性。计数、余额修改这类操作仍需要锁或原子类。
JDK 8 最重要的新特性是什么?
Lambda、函数式接口、Stream API、Optional、java.time、新的 CompletableFuture、接口默认方法,以及永久代改为 Metaspace。
JDK 17 有哪些重要特性?
record、sealed class、switch 表达式、文本块、instanceof 模式匹配、Helpful NullPointerException,以及 G1/ZGC 等 JVM 能力改进。
JDK 21 最值得关注的特性是什么?
虚拟线程是最值得关注的特性,适合大量阻塞 IO 场景;此外还有 Sequenced Collections、switch 模式匹配、record patterns、结构化并发预览等。
总结
Java 基础面试的主线可以围绕四个问题展开:
- 语言怎么表达:基础类型、面向对象、异常、泛型、字符串。
- 类库怎么使用:集合、Map、Stream、Optional、时间 API。
- 并发怎么保证:线程池、锁、volatile、ThreadLocal、JMM。
- 运行时怎么工作:JVM、类加载、GC、JDK 8/17/21 版本演进。
把这条链路讲清楚,再结合 HashMap、ConcurrentHashMap、线程池、JVM 排查和 JDK 新特性,Java 基础相关问题就能从“背 API”升级成“理解语言和运行时机制”。


