Java 面试高频题——集合、JVM、并发场景题一次说清
·
金三银四跳槽季,Java 后端面试其实有规律可循。这篇把最高频的考点梳理一遍,每道题说清楚「怎么答」和「为什么」。
一、集合框架
1. HashMap 底层原理
面试官想问: 你知道 HashMap 怎么存数据的吗?线程安全吗?
JDK 7:数组 + 链表(头插法)
JDK 8:数组 + 链表/红黑树(尾插法)
put 流程:
计算 key 的 hash → 定位数组下标
→ 如果没冲突直接放
→ 有冲突挂链表(超过8个转红黑树)
→ 容量超过负载因子(75%)就扩容
get 流程:
计算 hash → 定位下标
→ 比较 key 的 equals
→ 链表/红黑树中查找
关键要说的点:
- 为什么转红黑树?链表太长查询慢(O(n)→O(logn))
- 为什么负载因子是0.75?空间和时间的平衡
- HashMap 线程不安全 → 多线程 put 可能死循环(JDK7头插法导致)
- 线程安全用 ConcurrentHashMap(分段锁/CAS+synchronized)
2. ArrayList vs LinkedList
| ArrayList | LinkedList | |
|---|---|---|
| 底层 | 数组(连续内存) | 双向链表 |
| 随机查 | ✅ O(1) | ❌ O(n) |
| 尾部增删 | ✅ O(1) | ✅ O(1) |
| 中间插入 | ❌ O(n) 要移动元素 | ✅ O(1) 改指针 |
| 内存 | 连续,省内存 | 每个节点多存前后指针 |
一句话:查多用 ArrayList,改多用 LinkedList。
3. ConcurrentHashMap 怎么保证线程安全
JDK 7:分段锁 Segment(继承 ReentrantLock)
默认16段,理论上16个线程同时写
JDK 8:CAS + synchronized(锁链表头节点或红黑树根节点)
并发度更高,粒度更细
要说的重点: JDK8 比 JDK7 并发更好,而且 synchronized 在 JDK8 已经优化过,不像以前那么重了。
二、JVM
1. 内存区域划分
线程私有:
程序计数器 → 当前线程执行到哪一行
虚拟机栈 → 局部变量、方法调用(StackOverflowError)
本地方法栈 → native 方法
线程共享:
堆 → 对象实例(OOM 重灾区)
方法区 → 类信息、常量、静态变量
2. GC 垃圾回收
哪些对象是垃圾? 从 GC Root 出发,不可达的对象。
GC Root 有哪些:
- 栈帧中的局部变量引用的对象
- 静态变量引用的对象
- 常量引用的对象
- native 方法引用的对象
垃圾回收算法:
| 算法 | 原理 | 适用 |
|---|---|---|
| 标记-清除 | 标记垃圾→直接清 | 有碎片问题 |
| 标记-复制 | 分两块,活的对象复制到另一块 | 新生代(朝生夕死) |
| 标记-整理 | 标记→存活对象往一边挪 | 老年代 |
分代回收:
新生代:Eden + Survivor(8:1) → Minor GC,复制算法
老年代:存活多次的对象 → Major GC,标记-整理
3. 频繁 Full GC 怎么排查
jstat -gcutil pid看各个代的使用率jmap -histo pid看哪些对象占的内存大jstack pid看是否有死锁或线程阻塞- 结合代码分析:是不是 new 了大量对象没释放?是不是一次性查了太多数据?
三、并发编程
1. synchronized 原理
synchronized 在 JVM 层面是通过 Monitor(监视器锁)实现的
JDK 1.6 以后做了锁升级:
无锁 → 偏向锁(只有一个线程竞争)
→ 轻量级锁(CAS 自旋,少量线程)
→ 重量级锁(操作系统互斥量)
锁升级是为了优化: 大部分场景下锁竞争不激烈,没必要一上来就用重量级锁。
2. ThreadLocal 内存泄漏问题
ThreadLocal 适合: 每个线程需要自己的一份数据副本(比如数据库连接、用户登录信息)。
内存泄漏原因:
ThreadLocalMap 的 key 是弱引用(WeakReference)
如果 key 被 GC 回收了,value 还在 → value 永远访问不到
→ 内存泄漏
解决: 用完一定要调用 remove() 清理。
3. 线程池参数怎么设置
// CPU 密集型:corePoolSize = CPU核心数 + 1
// IO 密集型:corePoolSize = CPU核心数 × 2(或更多)
// 混合型:拆分任务或按压测结果定
拒绝策略怎么选:
- 重要的任务(支付、下单)→ CallerRunsPolicy
- 不重要的日志 → DiscardPolicy
- 默认 AbortPolicy 直接抛异常,适合大多数场景
四、Spring 全家桶
1. Spring Bean 生命周期
实例化 → 属性赋值 → Aware 接口 → BeanPostProcessor前置
→ @PostConstruct(init-method) → BeanPostProcessor后置
→ 就绪 → 销毁(@PreDestroy)
2. SpringBoot 自动配置原理
@SpringBootApplication
├── @EnableAutoConfiguration → 加载 META-INF/spring.factories
├── @ComponentScan → 扫描当前包及子包
└── @Configuration → 标识为配置类
// 条件注解:
@ConditionalOnClass // classpath 有某个类才生效
@ConditionalOnMissingBean // 容器中没有该 Bean 才创建
@ConditionalOnProperty // 配置文件中指定值才生效
所以为什么 SpringBoot 比 Spring 省事?自动配置 + 条件注解,不用手写 XML。
3. Spring AOP 两种代理
JDK 动态代理:目标类必须有接口
CGLIB 代理:目标类可以没有接口(通过继承实现)
SpringBoot 2.x 默认:目标类有接口用 JDK,没有用 CGLIB
可以通过 spring.aop.proxy-target-class=true 强制用 CGLIB
五、MySQL
1. 索引失效的场景
-- ❌ 对索引列使用了函数
WHERE DATE(create_time) = '2025-01-01'
-- ✅ 改写成范围查询
WHERE create_time >= '2025-01-01' AND create_time < '2025-01-02'
-- ❌ 隐式类型转换
WHERE phone = 1234567890 -- phone 是 varchar
-- ✅ 加引号
WHERE phone = '1234567890'
-- ❌ LIKE 以 % 开头
WHERE name LIKE '%张三%'
-- ✅ 或者用全文索引
-- ❌ 联合索引跳过了最左列
-- 索引 (a,b,c) 但只查 b 和 c
2. 最左前缀原则
-- 联合索引 (name, age, phone)
-- 以下查询走索引
WHERE name = '张三' -- ✅
WHERE name = '张三' AND age = 25 -- ✅
WHERE name = '张三' AND phone = '138' -- ✅ 只用到 name 列,phone 走不到
-- 以下不走索引
WHERE age = 25 -- ❌ 跳过了 name
六、面试沟通技巧
- 不会的题:先说自己了解的部分,然后说"这个我目前掌握得不够深,回去补一下"
- 源码题:不一定非要背过每一行代码,说清楚原理和流程就够了
- 项目题:重点说难点、怎么解决的、带来了什么效果,不要只罗列功能
最常被问的一个问题: “你项目中遇到的最大难点是什么?怎么解决的?”
→ 提前准备好这个回答,比背100道面试题都管用。
💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。
更多推荐
所有评论(0)