来未来科技熙牛医疗Java后端实习一面
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 自我介绍 & 实习经历。
- OOM 之后是怎么排查的?
- 生产环境怎么查看 JVM 中的变量、请求入参?
- 项目的乐观锁怎么用的?
- Redis 分布式锁怎么实现重入和续期?
- 线程池参数有哪些?怎么调优?
- Spring 循环依赖是怎么回事?
- AOP 失效的常规场景有哪些?
- MySQL 有没有遇到慢 SQL?怎么处理?
- 微服务之间怎么交互?
- Dubbo 了解吗?
- 用过哪些消息中间件?
《参考解析》
OOM 排查:第一原则是保留现场,启动参数加 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump,否则只能靠复现。线上流程:jps -l 找到 pid → jstat -gcutil <pid> 1000 10 看各区占用和 GC 频率(老年代持续 99%、Full GC 后不降就是内存泄漏)→ jmap -histo:live <pid> | head -30 看哪些类实例最多 → jmap -dump:live,format=b,file=x.hprof <pid> 导出 → MAT 看 Dominator Tree 和 Leak Suspects,重点排查无界集合/缓存不淘汰、ThreadLocal 未 remove、监听器未注销、大对象(一次查出百万行)、类加载器泄漏导致 Metaspace 涨。还要分清 OOM 类型:Java heap space(堆)、GC overhead limit exceeded(堆但 GC 无效)、Metaspace(动态代理/热部署)、Direct buffer memory(NIO 直接内存,-XX:MaxDirectMemorySize)、unable to create new native thread(线程数上限)。止血手段:重启 + 限流,根因靠 dump 分析。
生产环境怎么看变量和入参:首选 Arthas(阿里开源,attach 到运行中的 JVM)。常用命令——sc -d 类名 查类加载信息、jad 类名 反编译看线上真实代码、watch 类名 方法名 '{params, returnObj, throwExp}' -x 3 观察入参和返回值、trace 类名 方法名 看方法内部各调用耗时、tt 做请求的录制回放、ognl 读静态字段。其次:应用日志里用 traceId 串起入参(必须脱敏,手机号密码不能打);APM(SkyWalking、Pinpoint)看链路和慢调用;jcmd <pid> VM.system_properties、jinfo 看启动参数;GC 日志配 -Xlog:gc*。注意生产不允许随便开远程 debug 端口(会挂起 JVM、有安全风险)。
Redis 分布式锁的重入与续期:自己实现的话,锁的 value 必须是「客户端唯一 id + 线程 id」,避免误删别人的锁,解锁用 Lua 保证「判断 + 删除」原子。重入用 Hash 结构:key 是锁名,field 是持有者标识,value 是重入次数;加锁时 field 已存在则 HINCRBY +1,解锁 -1,减到 0 再 DEL,整个过程用一段 Lua 脚本完成。续期用”看门狗”:加锁成功后注册一个定时任务,按 leaseTime / 3 的间隔检查业务是否还在执行,在跑就 EXPIRE 续到完整时长;进程宕机后看门狗随之停止,锁到期自动释放,不会死锁。生产上直接用 Redisson 的 RLock,tryLock 默认 leaseTime = -1 即启用看门狗(30s 租期、每 10s 续一次)。还要知道局限:主从异步复制下主节点宕机可能丢锁,要求强一致得用 RedLock 或换 etcd/ZooKeeper。
线程池参数与调优:七个参数——corePoolSize、maxPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。执行顺序是「核心线程 → 队列 → 扩到最大线程 → 拒绝策略」,这个顺序常被问。调优方法:先判断任务类型,CPU 密集型核心数设为 N+1,IO 密集型可到 2N 甚至更高,最终一定要靠压测确定;队列必须用有界队列(ArrayBlockingQueue),Executors.newFixedThreadPool 用无界 LinkedBlockingQueue 会 OOM,这是《阿里巴巴Java开发手册》明令禁止直接使用 Executors 的原因;拒绝策略选 CallerRunsPolicy 做背压降级,或自定义策略落库补偿;给线程起有意义的名字便于排查;监控 activeCount、queue.size()、completedTaskCount、拒绝次数并告警;用 ThreadPoolExecutor 构造函数显式创建。
Spring 循环依赖:指 A 依赖 B、B 又依赖 A。Spring 用三级缓存解决单例的字段/setter 注入:一级 singletonObjects(成品)、二级 earlySingletonObjects(半成品)、三级 singletonFactories(ObjectFactory,用于在需要时提前生成 AOP 代理)。流程是:创建 A 时先把 A 的工厂放进三级缓存 → 填充属性时发现依赖 B → 去创建 B → B 填充属性时从三级缓存拿到 A 的早期引用(必要时生成代理)放进二级缓存 → B 创建完成 → A 继续填充完成。要注意:构造器注入的循环依赖无法解决(对象还没实例化就要求依赖)、prototype 作用域的循环依赖无法解决、Spring Boot 2.6 起默认 spring.main.allow-circular-references=false,遇到就直接启动报错。与其依赖三级缓存,不如从设计上打破循环(抽第三个类、用 @Lazy 延迟注入、事件解耦)。
AOP 失效的常见场景:①同类内部自调用(this.method())不走代理,是最常见的坑,解法是拆类、注入自身或 AopContext.currentProxy();②方法不是 public(JDK 动态代理基于接口,CGLIB 也无法代理 private/final/static 方法);③对象不是 Spring 容器管理的 Bean(自己 new 出来的);④类是 final 或方法是 final,CGLIB 无法继承;⑤切点表达式没匹配上(包名、注解、参数不匹配);⑥@Transactional 默认只对 RuntimeException 回滚,捕获异常没抛出、或异常被 catch 吞掉导致不回滚;⑦在同一个事务方法内调用另一个事务方法(传播行为不生效);⑧异步/多线程中调用,事务上下文不传递。
慢 SQL 的处理:先发现——开 slow_query_log(long_query_time=1)配合 pt-query-digest 或 performance_schema.events_statements_summary_by_digest 排 Top SQL。再分析——EXPLAIN 看 type(出现 ALL/index 全扫是重点)、key、rows、filtered、Extra(Using filesort、Using temporary)。优化手段按性价比排序:①加合适的联合索引(遵循最左前缀,尽量做覆盖索引减少回表);②改写 SQL——避免索引列上的函数和隐式类型转换、大分页改游标、OR 改 UNION ALL、子查询改 JOIN、只查需要的列;③减少回表/减少扫描行数、去掉 SELECT *;④大表结构变更用 gh-ost/pt-online-schema-change;⑤架构层做读写分离、加缓存、冷热数据归档、分库分表。最后一定要有回归验证:优化前后对比执行计划与耗时。
微服务交互与中间件:同步交互用 REST(HTTP/JSON,通用、易调试)或 RPC(Dubbo、gRPC,长连接 + 二进制协议,性能高、强类型);异步交互用消息队列解耦。一套完整的微服务体系还包括注册中心(Nacos、Eureka、ZooKeeper)、配置中心、负载均衡、网关(Spring Cloud Gateway)、熔断限流降级(Sentinel、Hystrix)、分布式链路追踪(SkyWalking)、分布式事务(Seata 的 AT/TCC/Saga 模式)。Dubbo 的核心是「接口即契约」:消费方持有接口的代理,通过注册中心发现提供者,走自定义的二进制协议 + Netty 长连接调用,支持多种负载均衡策略与集群容错(Failover/Failfast/Forking),并可通过 SPI 扩展 Filter 做鉴权、限流、埋点。消息中间件选型:Kafka(吞吐最高,日志与流处理)、RocketMQ(事务消息、延迟消息、顺序消息,电商场景多)、RabbitMQ(AMQP,路由灵活,中小规模)、Pulsar(存算分离,多租户)。