本文目录导读:

- 场景题一:设计一个短链接系统(系统设计 + 基础)
- 场景题二:OOM排查实战(JVM调优 + 性能排查)
- 场景题三:分布式锁的实现(并发 + 分布式)
- 场景题四:线程池拒绝策略导致任务丢失(并发 + 生产故障)
- 场景题五:数据库分库分表后跨库查询(架构设计)
- 附加建议
下面为你提供 5个高频Java面试场景题,涵盖设计、调优、并发、排查和架构方面,并给出解题思路和代码示例。
场景题一:设计一个短链接系统(系统设计 + 基础)
面试官问:如果让你设计一个类似 bit.ly 的短链接服务,你如何保证短链接不重复、高并发下不冲突,并且能快速跳转?
思路解析:
- 生成短码:使用
Base62(数字+大小写字母)编码自增ID;或使用MurmurHash对原URL取哈希,再截断。 - 冲突处理:自增ID天然不冲突;哈希方案需要回查数据库或用布隆过滤器预判。
- 存储:MySQL建表
(short_code, original_url, created_at),主键用short_code,并加缓存。 - 缓存:使用 Redis 做两级缓存(短码→原URL),用 Caffeine 做本地缓存,降低Redis压力。
- 跳转:302 重定向(便于统计点击量)。
代码示例(核心部分):
// Base62 编码
public class Base62Encoder {
private static final String BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static String encode(long num) {
StringBuilder sb = new StringBuilder();
while (num > 0) {
sb.append(BASE62.charAt((int) (num % 62)));
num /= 62;
}
return sb.reverse().toString();
}
public static long decode(String str) {
long num = 0;
for (char c : str.toCharArray()) {
num = num * 62 + BASE62.indexOf(c);
}
return num;
}
}
场景题二:OOM排查实战(JVM调优 + 性能排查)
面试官问:线上应用突然发生 OutOfMemoryError: Java heap space,你会怎么排查?
回答要点(逐步排查):
- 保留现场:加
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof参数,让JVM自动导出堆快照。 - 用MAT分析:
- 打开堆转储文件,看
Dominator Tree(支配树),找到占用最大内存的对象。 - 检查是内存泄漏(Leak Suspects)还是内存溢出(对象确实都还活着,只是太多)。
- 打开堆转储文件,看
- 区分场景:
- 泄漏:查看GC roots引用链,找到是谁强引用了这个对象(如静态集合未清理)。
- 溢出:比如一次性加载大文件到内存,或分页查询时
offset过大导致缓存穿透。
- 代码层面排查:检查是否有
ThreadLocal未remove、是否有InputStream未关闭、是否有大集合未置空。
代码示例(一个典型泄漏案例):
public class MemoryLeakDemo {
// 静态List持有对象,导致无法回收
public static List<Object> leakList = new ArrayList<>();
public void leak() {
while (true) {
Object obj = new Object();
leakList.add(obj); // 一直在增加,从不清理
}
}
}
场景题三:分布式锁的实现(并发 + 分布式)
面试官问:在分布式系统中,如何保证多个服务实例对同一个资源操作的互斥性?
回答要点:
- 基于 Redis SETNX
- 命令:
SET lock_key unique_value NX PX 30000 - 释放:用Lua脚本保证原子性(先判断value是否一致再DEL)。
- 问题:主从切换时可能丢锁;需要续期(看门狗机制)。
- 命令:
- 基于 ZooKeeper 临时有序节点
- 每个客户端创建临时有序节点,取序号最小的获得锁,后续节点监听前一个节点。
- 优点:没有锁过期时间,不会死锁;缺点:性能比Redis略差。
- 最佳实践:Java中直接用
Redisson的RLock,自带看门狗续期。
代码示例(Redisson 实现):
@Autowired
private RedissonClient redissonClient;
public void doWithLock(String key) {
RLock lock = redissonClient.getLock(key);
try {
// 尝试加锁,最多等待5秒,锁有效期30秒(看门狗会自动续期)
boolean isLocked = lock.tryLock(5, 30, TimeUnit.SECONDS);
if (!isLocked) {
throw new RuntimeException("获取锁失败");
}
// 业务逻辑...
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
场景题四:线程池拒绝策略导致任务丢失(并发 + 生产故障)
面试官问:某次线上高峰期,使用 Executors.newFixedThreadPool(10) 处理异步任务,发现大量任务被丢弃,你怎么处理?
问题分析:
- 根本原因:
newFixedThreadPool默认使用LinkedBlockingQueue(无界队列),但队列满了后会用AbortPolicy(抛异常)丢弃任务。 - 改进方案:
- 自定义线程池,使用 有界队列 + CallerRunsPolicy(让提交任务的线程自己执行),避免任务丢失与雪崩。
- 做好 拒绝监控:在拒绝时打印日志或发送告警。
代码示例(自定义线程池):
public class ThreadPoolConfig {
public static ExecutorService createPool() {
int core = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
core, // 核心线程数
core * 2, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲回收时间
new ArrayBlockingQueue<>(1000), // 有界队列
new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build(),
new CallerRunsPolicy() // 拒绝策略:调用者执行
);
return executor;
}
}
场景题五:数据库分库分表后跨库查询(架构设计)
面试官问:订单表数据量达到亿级,做分库分表后(按 user_id hash),如何做订单列表查询(跨分片)和订单详情查询?
回答要点:
- 方案设计:
- 订单详情:订单ID应包含分片键信息(如
user_id + 序号),或使用 UID 生成器(如 snowflake),查详情时直接路由到对应分片。 - 订单列表:如果按 user_id 分片,那么一个用户的订单都在同一分片,直接用
WHERE user_id = ?查询即可。 - 跨分片查询:如后台运营需要查全量订单,使用 中间层聚合(如 ShardingSphere 联邦查询),或同步到 Elasticsearch 做宽表查询。
- 订单详情:订单ID应包含分片键信息(如
- 注意事项:
- 避免跨分片 JOIN:尽量把关联表也按相同维度分片。
- 使用 全局ID:雪花算法或号段模式,保证全局唯一。
代码示例(分片键路由思路):
public class OrderService {
// 根据订单ID最前面几位截取分片号,如 user_id % 16
public int getShardByUserId(Long userId) {
return (int) (userId % 16);
}
public Order getOrderById(Long orderId, Long userId) {
int shard = getShardByUserId(userId);
// 直接路由到对应分片库的表
return orderMapperShard[shard].selectByOrderId(orderId);
}
}
附加建议
面试时,场景题不要直接说“我不会”,而是按以下步骤回答:
- 澄清需求:问清是功能问题、性能问题还是稳定性问题。
- 给出方案:说一两个可行的技术方案。
- 对比利弊:分析每个方案的优劣势与适用场景。
- 落地实施:给出关键代码或伪代码。
- 验证优化:如何测试、监控、回滚。
如果你有想针对某个方向(如秒杀、消息队列、微服务链路追踪)深入了解的,可以告诉我,我可以给你更细致的场景题!