这是一份 Java 校招面试题复盘清单。
它不适合当作“背诵稿”逐字记忆,更适合当作复习地图:先知道面试会问哪些方向,再把每个问题整理成可以讲清楚的核心答案。
Java 校招面试通常会围绕三部分展开:
模块 占比 重点 技术基础 约 40% Java、Spring、MySQL、Redis、JVM、并发 项目深挖 约 40% 项目背景、技术选型、难点、优化、问题排查 学习能力 约 20% 最近在学什么、为什么做 Java、如何解决问题
如果项目里有 Elasticsearch,那么 ES 往往会成为面试官重点追问的方向。
HashMap 底层主要是数组、链表和红黑树。
当放入一个 key-value 时,会先根据 key 的 hashCode() 计算 hash,再定位到数组下标。如果该位置没有元素,就直接放入;如果已经有元素,就会形成链表或红黑树。
在 Java 8 之后,当链表长度达到一定阈值,并且数组容量足够大时,链表会转换为红黑树,用来提高查询效率。
可以这样回答:
HashMap 通过 hash 定位数组下标,数组中每个位置叫 bucket。发生 hash 冲突时,Java 8 以前主要用链表,Java 8 之后链表过长会转为红黑树。扩容时会重新计算元素位置,所以 HashMap 的性能和初始容量、负载因子、hash 分布都有关系。
HashMap 没有做同步控制,多线程同时读写时可能出现数据覆盖、状态不一致、扩容异常等问题。
常见风险:
多个线程同时 put,可能覆盖彼此写入。
扩容时结构变化,其他线程同时访问可能拿到异常结果。
统计数量 size 可能不准确。
所以多线程场景通常使用 ConcurrentHashMap。
Java 8 中,ConcurrentHashMap 主要通过 CAS 和 synchronized 保证线程安全。
它不是给整张表加一把大锁,而是尽量缩小锁粒度。
常见回答:
Java 8 的 ConcurrentHashMap 底层也是数组、链表和红黑树。插入时,如果桶为空,会通过 CAS 放入节点;如果桶不为空,会对桶头节点加 synchronized,只锁当前桶。这样既保证线程安全,又比 Hashtable 整表加锁性能更好。
ArrayList 底层是动态数组,LinkedList 底层是双向链表。
对比项 ArrayList LinkedList 底层结构 动态数组 双向链表 随机访问 快,按下标访问 O(1) 慢,需要遍历 中间插入删除 需要移动元素 找到节点后修改指针 内存占用 相对较少 每个节点要存前后指针 常用场景 查询多 插入删除多,但实际也要看位置
面试里要注意:不要简单说 LinkedList 插入删除一定快。因为如果要先按索引找到位置,遍历本身也有成本。
String 是不可变对象,每次修改都会产生新字符串。
StringBuilder 是可变字符序列,线程不安全,但性能较好。
StringBuffer 也是可变字符序列,方法加了同步,线程安全,但性能通常低于 StringBuilder。
常见选择:
少量字符串拼接:直接用 String。
单线程大量拼接:用 StringBuilder。
多线程共享拼接对象:用 StringBuffer,但实际业务中较少这样用。
== 比较的是两边是否相等。
对于基本类型,比较的是值。
对于引用类型,比较的是对象地址。
equals() 是对象方法,默认实现也是比较地址,但很多类会重写它,比如 String 会比较字符串内容。
String a = new String ( " hello " ) ;
String b = new String ( " hello " ) ;
System . out . println ( a == b ) ; // false
System . out . println ( a . equals ( b )) ; // true
Java 异常体系的顶层是 Throwable。
它下面主要分为:
Error:严重错误,程序通常不主动处理,比如 OutOfMemoryError。
Exception:程序可以捕获和处理的异常。
Exception 又分为:
checked exception:编译期异常,必须处理或声明抛出。
unchecked exception:运行时异常,继承自 RuntimeException,例如空指针、数组越界。
面试里可以补一句:业务开发中不要滥用异常控制正常流程,异常更适合表示非预期情况。
反射是 Java 在运行时获取类信息、创建对象、调用方法、访问字段的能力。
常见应用场景:
Spring 创建 Bean、依赖注入。
MyBatis 映射对象字段。
注解解析。
测试框架调用测试方法。
动态代理。
反射灵活,但也有缺点:性能相对普通调用更低,可读性和安全性更差。
进程是操作系统资源分配的基本单位。一个应用程序运行起来通常就是一个进程。
线程是 CPU 调度的基本单位,一个进程中可以包含多个线程。
可以这样回答:
进程拥有独立的内存空间,线程共享同一进程的内存资源。线程切换成本通常低于进程,但共享数据也会带来线程安全问题。
synchronized 可以修饰方法或代码块,用来保证同一时间只有一个线程进入临界区。
它依赖对象监视器锁,也就是 monitor。
进入同步代码块时,线程尝试获取对象锁;执行完或异常退出时释放锁。
可以补充:
修饰普通方法,锁的是当前对象 this。
修饰静态方法,锁的是当前类的 Class 对象。
修饰代码块,可以指定锁对象。
volatile 主要有两个作用:
它不能保证复合操作的原子性。
比如 count++ 包含读取、加一、写回,不是一个原子操作,所以只加 volatile 仍然不安全。
常见场景:
线程池是提前创建并管理一组线程,任务来了以后交给线程池执行。
使用线程池的原因:
避免频繁创建和销毁线程。
控制并发线程数量。
提高响应速度。
统一管理任务队列、拒绝策略和线程生命周期。
ThreadPoolExecutor 常见核心参数:
参数 作用 corePoolSize核心线程数 maximumPoolSize最大线程数 keepAliveTime非核心线程空闲存活时间 unit时间单位 workQueue任务队列 threadFactory线程创建工厂 handler拒绝策略
执行流程可以概括为:
死锁是多个线程互相持有对方需要的资源,导致都无法继续执行。
死锁常见四个条件:
避免方式:
固定加锁顺序。
减少锁范围。
使用超时锁。
避免嵌套锁。
使用并发工具类代替手写锁。
JVM 运行时数据区主要包括:
程序计数器。
Java 虚拟机栈。
本地方法栈。
堆。
方法区。
线程私有:
线程共享:
栈主要存放方法调用相关信息,比如局部变量表、操作数栈、方法出口等。
堆主要存放对象实例,是垃圾回收重点关注的区域。
对比项 栈 堆 线程关系 线程私有 线程共享 存储内容 方法调用、局部变量 对象实例 生命周期 随方法调用入栈出栈 由 GC 管理 常见错误 StackOverflowErrorOutOfMemoryError
垃圾回收是 JVM 自动回收不再使用对象的机制。
判断对象是否可回收,主流方法是可达性分析:从 GC Roots 出发,能到达的对象是存活对象,不能到达的对象可以被回收。
常见 GC Roots:
虚拟机栈中的引用。
方法区中的静态变量引用。
常量引用。
本地方法栈中的引用。
常见算法:
标记-清除:先标记垃圾,再清除,可能产生内存碎片。
复制算法:把存活对象复制到另一块区域,适合新生代。
标记-整理:标记后整理存活对象,减少碎片。
分代收集:按对象生命周期分区,不同区域用不同算法。
OOM 是内存不足导致的错误。
常见原因:
创建了大量对象,堆空间不足。
大对象过多。
内存泄漏,旧对象一直被引用。
线程过多导致栈空间不足。
元空间加载类过多。
排查时通常会看日志、堆 dump、GC 情况和对象引用链。
常见关注点:
堆大小:-Xms、-Xmx
新生代大小:-Xmn
元空间大小:-XX:MetaspaceSize
GC 收集器选择。
GC 日志。
停顿时间和吞吐量。
调优不是盲目改参数,而是先看现象:内存是否够、GC 是否频繁、停顿是否过长、对象是否异常增长。
IoC 是控制反转。
原本对象由程序自己创建和管理,现在交给 Spring 容器创建和管理。
DI 依赖注入是 IoC 的一种实现方式。比如一个 Service 依赖 Mapper,不需要自己 new,而是由 Spring 注入。
AOP 是面向切面编程,用来把通用逻辑从业务代码中抽离出来。
常见场景:
日志。
权限校验。
事务。
监控统计。
接口耗时。
核心思想是:不修改业务方法本身,在方法执行前后织入增强逻辑。
简化流程:
实例化 -> 属性注入 -> 初始化前后处理 -> 初始化方法 -> 使用 -> 销毁
常见扩展点:
构造方法。
属性填充。
BeanPostProcessor。
InitializingBean 或 init-method。
DisposableBean 或 destroy-method。
Spring Boot 主要解决传统 Spring 项目配置繁琐的问题。
优势:
自动配置。
内置 Web 容器。
starter 依赖简化。
快速创建项目。
方便监控和部署。
一句话回答:
Spring Boot 让 Spring 项目更容易启动和维护,它通过自动配置和 starter 机制减少大量 XML 或手动配置。
自动配置的核心是根据类路径、配置文件和条件注解,自动创建合适的 Bean。
常见关键词:
starter。
auto configuration。
@EnableAutoConfiguration。
条件注解,比如 @ConditionalOnClass、@ConditionalOnMissingBean。
面试中可以这样说:
Spring Boot 会根据引入的依赖和当前环境判断是否满足条件,如果满足,就把对应配置类里的 Bean 注册到容器中。
RESTful API 是一种接口设计风格。
核心思想:
使用 URL 表示资源。
使用 HTTP 方法表示操作。
使用状态码表示结果。
示例:
索引是帮助 MySQL 快速查找数据的数据结构。
可以理解为书的目录。没有索引时,数据库可能要全表扫描;有索引时,可以更快定位数据。
索引能提高查询效率,但会增加写入成本和存储空间。
B+Tree 适合磁盘存储和范围查询。
原因:
树高度低,减少磁盘 IO。
非叶子节点只存索引,能放更多 key。
叶子节点之间有链表,范围查询效率高。
查询性能稳定。
覆盖索引指查询需要的字段都能从索引中获得,不需要回表查询。
比如有联合索引 (name, age):
SELECT name , age FROM user WHERE name = ' xiaoxi ' ;
如果查询字段都在索引里,就可能走覆盖索引。
常见情况:
对索引列使用函数。
对索引列做计算。
使用左模糊匹配,例如 LIKE '%abc'。
联合索引不符合最左前缀原则。
隐式类型转换。
OR 条件使用不当。
事务是一组数据库操作的集合,要么全部成功,要么全部失败。
比如下单时:
这些操作应该作为一个整体处理。
特性 含义 Atomicity 原子性 事务要么全部成功,要么全部失败 Consistency 一致性 事务前后数据满足约束 Isolation 隔离性 并发事务之间互相隔离 Durability 持久性 事务提交后数据持久保存
MVCC 是多版本并发控制。
它通过保存数据的多个版本,让读写尽量不互相阻塞。
在 InnoDB 中,MVCC 主要依赖:
隐藏字段。
undo log。
ReadView。
常见作用是支持可重复读和快照读。
常见原因:
数据主要在内存中。
使用高效数据结构。
单线程命令执行避免大量锁竞争。
IO 多路复用。
C 语言实现,执行效率高。
常见类型:
String。
Hash。
List。
Set。
Sorted Set。
Bitmap。
HyperLogLog。
Geo。
Stream。
缓存穿透是请求的数据在缓存和数据库中都不存在,导致请求持续打到数据库。
解决方式:
缓存击穿是热点 Key 过期瞬间,大量并发请求同时打到数据库。
解决方式:
缓存雪崩是大量 Key 同时过期,或者 Redis 整体不可用,导致大量请求涌向数据库。
解决方式:
过期时间加随机值。
多级缓存。
限流降级。
Redis 高可用。
Elasticsearch 是一个分布式搜索和分析引擎,常用于全文搜索、日志检索、商品搜索等场景。
它底层基于 Lucene,对外提供 REST API,支持分布式、倒排索引和复杂查询。
倒排索引是从词到文档的映射。
普通索引更像:
倒排索引更像:
这就是 ES 做全文搜索快的重要原因。
index 类似一类数据的集合。
document 是一条 JSON 数据。
type 在早期版本中用于区分类型,但新版本已经逐步移除,不建议在新项目中依赖 type。
可以类比:
ES 关系型数据库 index table 或 database 的某种集合概念 document row field column
DSL 是 Elasticsearch 的查询语言,使用 JSON 描述查询条件。
示例:
常见条件:
must:必须匹配,影响评分。
should:可选匹配,可能影响评分。
filter:必须匹配,但不参与评分,适合过滤条件。
must_not:必须不匹配。
ES 在全文搜索场景下更快,主要因为它使用倒排索引。
MySQL 更擅长结构化数据查询和事务处理,全文检索不是它最核心的场景。
可以这样回答:
ES 会先对文本分词,再建立词到文档的倒排索引。查询关键词时,可以快速定位包含该词的文档。MySQL B+Tree 索引更适合精确匹配和范围查询,对复杂全文搜索、相关性评分和分词检索不如 ES 合适。
Docker 是容器化技术,可以把应用和依赖打包成镜像,再以容器方式运行。
它解决了“本地能跑,服务器不能跑”的环境一致性问题。
对比项 Docker 虚拟机 隔离方式 进程级隔离 硬件级虚拟化 启动速度 快 慢 资源占用 较少 较多 系统内核 共享宿主机内核 每个虚拟机有完整操作系统 适用场景 应用部署、微服务 强隔离、多系统环境
常见命令:
面试时如果结合项目部署经历回答,会比单纯背命令更好。
REST API 是基于 REST 风格设计的接口。
它通常使用 HTTP 协议,通过 URL 表示资源,通过 HTTP 方法表示操作。
这个问题和 Spring 里的 RESTful API 本质相同。
微服务是把一个大系统拆成多个小服务,每个服务负责一个相对独立的业务能力。
优点:
服务独立部署。
技术栈可以更灵活。
方便水平扩展。
团队边界更清晰。
缺点:
服务间调用复杂。
分布式事务困难。
监控、链路追踪、部署复杂度提高。
API 网关是系统入口,负责把外部请求转发到内部服务。
常见功能:
路由转发。
鉴权。
限流。
熔断。
日志。
跨域处理。
如果你的项目重点是 Elasticsearch,面试官很可能会围绕项目继续追问。
可以提前准备这些问题:
问题 准备方向 为什么项目要用 ES MySQL 搜索能力不足、全文检索、分词、排序、性能 数据怎么同步到 ES 同步写、异步消息、定时补偿 ES 和 MySQL 数据不一致怎么办 重试、补偿任务、最终一致性 索引怎么设计 index、mapping、分词器、字段类型 搜索结果怎么排序 相关性评分、业务权重、时间、热度 查询慢怎么优化 filter、分页限制、字段选择、索引设计
面试时不要只说“我用了 ES”。更好的说法是:
我在项目中用 Elasticsearch 解决全文检索问题。MySQL 更适合事务和结构化查询,但对分词搜索、相关性排序支持有限。所以把需要搜索的数据同步到 ES,通过倒排索引提升搜索效率。项目里还需要考虑 MySQL 和 ES 的数据一致性,比如通过消息队列异步同步,并配合定时任务补偿。
学习能力问题通常不会太难,但很考验真实感。
常见问题:
最近在学什么技术?
为什么选择 Java?
遇到不会的问题怎么解决?
看过哪些技术文档?
项目里最有收获的地方是什么?
回答建议:
不要只说“我在学 Java”。
要说具体学了什么、为什么学、怎么实践。
最好能和项目或面试岗位关联起来。
例如:
最近我在补 Redis 和 Elasticsearch。Redis 主要关注缓存穿透、击穿、雪崩以及数据结构的使用场景;Elasticsearch 主要学习倒排索引、DSL 查询和数据同步。因为我的项目里有搜索场景,所以我想把搜索链路和缓存链路都理解得更完整。
如果时间有限,可以按这个顺序准备:
Java 基础:集合、字符串、异常、反射。
MySQL:索引、事务、MVCC、索引失效。
Redis:数据结构、缓存问题、常用命令。
Spring Boot:IoC、AOP、自动配置、REST API。
JVM:内存结构、GC、OOM。
并发:线程池、锁、volatile、死锁。
Elasticsearch 项目:倒排索引、DSL、数据同步、搜索优化。
Docker 和 Linux:能讲清楚部署和常用命令即可。
Java 校招面试不是只背八股。
基础题要能答清楚概念,项目题要能讲清楚自己做了什么、为什么这样做、遇到了什么问题、怎么解决。
这份清单的重点不是一次性背完,而是把每个问题都整理成三层:
这样回答时会更稳,也更像真的理解过。