途虎养车-Java开发-二面 面经

1、介绍一下你最近做的一个有挑战性的项目

我最近负责的是一个汽车配件供应链系统的重构项目,这个系统日均订单量在30万左右,面临的主要挑战有三个方面。

  1. 性能问题,原系统在高峰期响应时间经常超过3秒,用户体验很差。我通过引入Redis多级缓存、优化数据库索引、改造慢查询,将P99响应时间降到了200ms以内。具体做法是对热点商品数据做了本地缓存+Redis缓存的二级缓存,对库存查询做了批量查询优化,还把一些复杂的关联查询改成了分步查询在应用层组装。
  2. 数据一致性问题,涉及到订单、库存、物流多个系统的数据同步。我引入了RocketMQ的事务消息机制,保证了分布式事务的最终一致性。同时设计了补偿机制和对账任务,每天凌晨会跑对账脚本检查数据是否一致。
  3. 系统稳定性问题,原来是单体应用,一个模块出问题整个系统都挂。我主导了微服务拆分,按业务域拆成了商品、订单、库存、支付等服务,引入了Sentinel做限流熔断,配置了服务降级策略。上线后系统可用性从95%提升到了99.5%以上。

这个项目让我对高并发系统设计和分布式架构有了更深的理解,也锻炼了我解决复杂问题的能力。

2、JVM内存模型和垃圾回收机制详细说一下

JVM内存模型分为线程共享区和线程私有区。

  1. 线程共享区包括堆和方法区。堆是最大的内存区域,存储对象实例,分为新生代和老年代,新生代又分为Eden区和两个Survivor区,比例是8:1:1。方法区存储类信息、常量、静态变量,JDK8之后改成了元空间使用本地内存。
  2. 线程私有区包括程序计数器、虚拟机栈、本地方法栈。程序计数器记录当前线程执行的字节码行号,虚拟机栈存储局部变量表、操作数栈、方法出口等,每个方法对应一个栈帧。

垃圾回收机制方面:

  1. 判断对象是否存活用的是可达性分析算法,从GC Roots开始遍历,能到达的对象是存活的,不能到达的就是垃圾。GC Roots包括虚拟机栈中的引用、方法区的静态变量、常量引用等。
  2. 回收算法有标记-清除、标记-复制、标记-整理三种。新生代用复制算法,因为对象存活率低,复制成本小。老年代用标记-清除或标记-整理,因为对象存活率高,复制成本大。
  3. 垃圾收集器我们用的是G1,它把堆分成多个Region,可以预测停顿时间,适合大堆内存。G1的回收过程包括新生代回收、并发标记、混合回收、Full GC。我们配置了-XX:MaxGCPauseMillis=200控制停顿时间,-XX:G1HeapRegionSize=16m设置Region大小。
  4. 实际调优经验是,先用jstat监控GC情况,如果Full GC频繁就要分析原因。可能是堆内存不够需要调大-Xmx,可能是内存泄漏需要用jmap dump堆然后用MAT分析,也可能是大对象直接进老年代需要调整-XX:PretenureSizeThreshold。

3、Spring Boot的自动装配原理是什么

Spring Boot自动装配的核心是@EnableAutoConfiguration注解,它的实现原理是:

  1. 启动类上的@SpringBootApplication包含了@EnableAutoConfiguration注解,这个注解通过@Import导入了AutoConfigurationImportSelector类
  2. AutoConfigurationImportSelector会读取所有jar包下的META-INF/spring.factories文件,获取EnableAutoConfiguration对应的配置类列表
  3. 这些配置类通过@Conditional系列注解来判断是否生效,比如@ConditionalOnClass判断类路径下是否存在某个类,@ConditionalOnMissingBean判断容器中是否缺少某个Bean
  4. 如果条件满足,配置类就会生效,通过@Bean注解向容器中注册组件。比如RedisAutoConfiguration会判断是否有Redis相关的类,如果有就自动配置RedisTemplate
  5. 用户可以通过application.yml配置属性来定制自动配置的行为,这些属性通过@ConfigurationProperties注解绑定到配置类上
  6. 如果用户自己定义了某个Bean,自动配置就会失效,因为有@ConditionalOnMissingBean的判断

这种设计的好处是约定大于配置,开发者只需要引入starter依赖就能自动配置好组件,大大简化了开发工作。我在项目中也自定义过starter,把公司的通用组件封装成starter供其他团队使用。

4、分布式锁的实现方案有哪些,各有什么优缺点

分布式锁主要有三种实现方案:

  1. 基于Redis实现,最常用的方式。可以用SETNX+EXPIRE命令,但要注意原子性问题,建议用SET key value NX EX seconds一条命令完成。释放锁时要判断是不是自己持有的锁,用Lua脚本保证原子性。还要考虑锁续期问题,可以用Redisson框架,它实现了看门狗机制自动续期。优点是性能高、实现简单,缺点是Redis主从切换时可能丢失锁,可以用RedLock算法向多个Redis实例申请锁来解决。
  2. 基于Zookeeper实现,利用临时顺序节点。每个客户端创建一个临时顺序节点,序号最小的获得锁,其他节点监听前一个节点。释放锁时删除节点,后续节点收到通知尝试获取锁。优点是可靠性高,节点删除会自动释放锁,缺点是性能不如Redis,需要依赖Zookeeper集群。
  3. 基于数据库实现,创建一张锁表,通过唯一索引或for update来实现。优点是不需要额外组件,缺点是性能差,数据库压力大,不适合高并发场景。

我在项目中用的是Redisson实现的分布式锁,它封装了很多细节,比如锁续期、可重入、公平锁等特性都支持。在处理订单库存扣减时,用分布式锁保证了并发安全,同时配置了合理的超时时间避免死锁。

5、如何设计一个高可用的系统架构

设计高可用系统需要从多个层面考虑:

  1. 服务层面,首先是无状态设计,应用服务器不保存状态,session存Redis,这样可以随意扩缩容。然后是服务冗余,每个服务至少部署3个实例,挂掉一个不影响整体。还要做好服务隔离,核心服务和非核心服务分开部署,避免相互影响。
  2. 数据层面,数据库做主从复制,主库挂了从库可以顶上。Redis做哨兵或集群模式,保证高可用。重要数据要做异地多活,不能只依赖一个机房。还要定期备份数据,制定灾难恢复预案。
  3. 限流降级,用Sentinel做接口限流,防止流量突增压垮系统。配置降级策略,非核心功能可以降级,比如推荐服务挂了就返回默认推荐。熔断机制也很重要,

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试圣经 文章被收录于专栏

Java面试圣经,带你练透java圣经

全部评论

相关推荐

面试官喜欢问用过什么ai,这时候就不能局限于ChatGPT、DeepSeek、豆包这种网页版对话工具,这些只是基本操作。面试官更想知道的是,你有没有用过能直接赋能开发提效的 AI 工具(比如 IDE 集成类、代码专属 AI 工具),以及你如何通过 Agent 思维、精准提示词设计,把 AI 变成真正的生产力助手。比如,只说 “用过 ChatGPT 写代码”,远不如说 “用 Cursor 的实时代码补全功能重构过 Spring Boot 接口的冗余逻辑”“靠 Claude Code 分析 JVM 堆转储日志,定位了并发场景下的内存泄漏问题”“基于 LangChain 搭过简易的本地知识库 Agent,用来自动检索项目历史文档,解决跨模块接口调用的疑难问题” 来得有说服力。除此之外,“开发中遇到过 AI 幻觉吗?怎么解决的?” 也是高频追问。毕竟真实工作里,AI 生成的代码或方案并非万能,甚至会出现 “一本正经输出错误答案” 的情况。比如你让 AI 写一个基于 Redis 的分布式锁,它可能会漏掉 finally 块的解锁逻辑,导致死锁;或者让它优化 MySQL 慢查询,它给出的索引方案反而会让查询效率更低;更常见的是,遇到一些冷门框架的问题,AI 会拼接看似合理的解决方案,实则完全不适用。这些场景的核心矛盾,在于 AI 是基于海量语料的概率性输出,而非真正理解业务逻辑和技术原理。这时候,能讲清 “如何识别幻觉、如何解决幻觉”,远比单纯说 “用过 AI” 更能体现你的能力。比如可以说:“我会先交叉验证 AI 给出的方案 —— 对照官方文档、查看源码注释,或者搭建最小测试用例跑通验证;如果 AI 陷入错误循环,我会拆解问题,用更精准的提示词限定范围,比如明确‘基于 Redis 6.0 版本,用 SETNX + EX 命令实现分布式锁,必须包含超时兜底和解锁校验’;实在解决不了的,会放弃直接生成,转而让 AI 提供思路参考,再结合自己的技术积累完成落地。”说到底,面试官问 AI 相关问题,不是考你 “知道多少工具”,而是考你 “有没有把工具用出深度”—— 是否能借助 AI 提升开发效率,是否能分辨 AI 输出的对错,是否具备 “工具辅助 + 独立思考” 的复合能力。这才是校招和社招中,拉开候选人差距的关键。
面试官最爱问的 AI 问...
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务