小鹏汽车 Java开发二面 深挖项目+系统设计
一面过了之后隔了三天约了二面,同样是视频面试,时长将近一个小时。二面的面试官是技术负责人,上来先深挖项目,然后是几道比较有深度的技术题,最后出了一道系统设计题。整体感觉比一面难不少,很多问题没有标准答案,考察的是思维方式和工程经验。
1. 详细讲一下你项目中最复杂的模块,为什么这样设计?有没有做过重构?
二面必问,而且会顺着你说的内容一直追问,所以只说自己真正理解的部分。
回答框架:
- 背景:这个模块解决什么业务问题,规模有多大(QPS、数据量)
- 设计:核心架构是什么,为什么这样划分,技术选型的理由
- 难点:遇到过什么问题,怎么发现的,怎么解决的
- 演进:如果做过重构,重构前后的对比,重构的收益
面试官最想听到的是你的思考过程,而不是"我用了 Spring Boot + MySQL + Redis"这种罗列。如果有数据支撑(优化后性能提升了多少、减少了多少故障)会更有说服力。
2. JVM 调优你做过吗?说说你的思路和实际经验
JVM 调优的核心是减少 Full GC 频率和 STW 时间。
调优思路:
- 先收集数据:开启 GC 日志(-Xlog:gc*),用 jstat、jmap、jvisualvm 观察堆使用情况、GC 频率和耗时
- 分析问题:是 Minor GC 太频繁(新生代太小)?还是 Full GC 太多(老年代晋升过快或内存泄漏)?还是单次 GC 时间太长(堆太大)?
常见调优手段:
- 合理设置堆大小(-Xms -Xmx),避免动态扩容开销,建议设为相同值
- 调整新生代比例(-XX:NewRatio),对象存活率低的业务可以加大新生代
- 选择合适的收集器:低延迟场景用 G1 或 ZGC,G1 可以通过 -XX:MaxGCPauseMillis 设置目标停顿时间
- 排查内存泄漏:用 jmap dump 堆快照,用 MAT 分析大对象和引用链
实际经验举例:发现服务每隔几小时 Full GC 一次,通过 MAT 分析发现某个缓存 Map 没有设置上限,持续增长撑满老年代,加了 LRU 淘汰后问题解决。
3. 说说 Java 线程池的核心参数,线程池的执行流程是怎样的?
核心参数:
- corePoolSize:核心线程数,线程池维持的最小线程数,即使空闲也不销毁
- maximumPoolSize:最大线程数
- keepAliveTime:非核心线程空闲超过这个时间后销毁
- workQueue:任务队列,常用 LinkedBlockingQueue(无界)、ArrayBlockingQueue(有界)、SynchronousQueue(不存储)
- rejectedExecutionHandler:拒绝策略
执行流程:
- 提交任务,当前线程数 < corePoolSize,创建核心线程执行
- 当前线程数 >= corePoolSize,任务放入队列
- 队列满了,当前线程数 < maximumPoolSize,创建非核心线程执行
- 队列满了且线程数达到 maximumPoolSize,执行拒绝策略
拒绝策略:AbortPolicy(抛异常,默认)、CallerRunsPolicy(调用者线程执行,起到降速作用)、DiscardPolicy(静默丢弃)、DiscardOldestPolicy(丢弃队列最老的任务)。
生产环境建议用有界队列+CallerRunsPolicy,避免任务无限堆积撑爆内存。
4. MySQL 的锁机制你了解多少?行锁、间隙锁、临键锁分别是什么?
行锁(Record Lock):锁定索引上的单条记录,只在有索引的情况下生效,没有索引会退化为表锁。
间隙锁(Gap Lock):锁定索引记录之间的间隙,防止其他事务在这个范围内插入数据,解决幻读问题。只在可重复读隔离级别下存在。
临键锁(Next-Key Lock):行锁+间隙锁的组合,锁定一条记录及其之前的间隙,是 InnoDB 默认的行锁算法。
举例:索引值有 1、5、10,查询 where id = 5,临键锁锁定的范围是 (1, 5],同时还会锁 (5, 10) 的间隙,防止并发插入 id=3 或 id=7 的记录。
死锁场景:两个事务分别持有不同行的锁,然后互相等待对方的锁,InnoDB 会自动检测死锁并回滚代价小的事务。可以通过固定加锁顺序、缩短事务、减少锁范围来预防。
5. 说说 MySQL 主从复制的原理,以及主从延迟怎么处理?
主从复制原理:
- 主库将所有写操作记录到 binlog(二进制日志)
- 从库的 IO 线程连接主库,读取 binlog 写入本地的 relay lo
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经,带你练透java圣经

查看19道真题和解析