你打破过双亲委派模型吗? 说一下Tomcat是怎么做的
视频讲解了 Java 面试中关于双亲委派模型及 Tomcat 打破该模型的核心知识点。
双亲委派模型定义:类加载器收到加载请求时,先委托父类加载器依次向上加载,父类无法加载时再自行加载,可防止用户自定义类篡改 Java 核心类库。
打破的核心场景:同一 Tomcat 容器部署多应用时,各应用可能依赖不同版本第三方库,若遵循双亲委派会出现版本冲突;应用热部署时,需通过自定义类加载器卸载旧类、加载新类。
Tomcat 打破逻辑:Tomcat 的 WebAppClassLoader 不遵循双亲委派,优先自行加载类,加载失败再委托父类加载;但核心类仍强制由父类加载,既实现应用间类隔离,又能共享容器公共类。
打破的实现方式:一是重写 loadClass 方法,自定义类加载器改变委托顺序;二是使用线程上下文类加载器,让上层框架加载应用层实现类;三是 OSGi 模块化,每个模块有独立类加载器形成网状结构。
打破双亲委派模型是 Java 面试的高级考察点,核心考察类隔离、热部署等原理的理解。
InnoDB里面的锁给我梳理下?
视频讲解了 InnoDB 锁的分类、应用场景及设计原因。
多维度锁分类:按锁模式分共享锁、排它锁;按锁粒度分表锁、行锁;按锁类型分记录锁、间隙锁、临键锁;另有意向锁、MDL 锁、自增锁等,各类锁可配合使用。
锁的自动分配:主键或唯一索引等值查询自动加记录锁,范围查询自动加临键锁,该过程无法人工干预。
粒度维度设计:表锁粒度粗、开销小,适合 DDL 操作或全表扫描;行锁粒度细、开销大,支持高并发读写,InnoDB 默认使用行锁;MDL 锁是 MySQL Server 层面的表锁,用于防止 DML 和 DDL 冲突,保证数据字典一致性。
模式维度设计:共享锁允许并发读、禁止写,用于一致性读;排它锁独占资源,写操作必须加排它锁;意向锁为表级别协调机制,可快速判断表内是否有行锁冲突,避免遍历所有行,提升效率。
类型维度设计:记录锁锁定单条记录,用于主键或唯一索引等值查询;间隙锁锁定记录间隙,防止范围插入新数据,解决幻读;临键锁同时锁定记录和间隙,是 InnoDB 默认行锁形式,用于范围查询和部分等值查询。
InnoDB 的锁设计旨在在并发性能、数据一致性和资源利用率间达成平衡。
HashMap你真的理解了吗?
小哲讲解 Java 中 HashMap 在 JDK1.7 与 1.8 版本的底层逻辑差异。
数据结构优化:JDK1.7 的 HashMap 采用数组 + 链表结构,若大量数据哈希值相同,会形成超长链表,导致查询性能急剧下降;JDK1.8 新增红黑树兜底方案,当链表长度超 8 且数组容量超 64 时,链表自动转为红黑树,极端场景下查询性能大幅提升。
插入方式调整:JDK1.7 采用头插法,并发扩容时会因链表顺序反转形成死循环;JDK1.8 改为尾插法,扩容时链表顺序不变,杜绝了并发扩容死循环问题,但仍不保证线程安全,多线程运行可能出现数据覆盖。
扩容机制升级:JDK1.7 扩容时需重新计算每个元素的哈希值来确定新位置,计算量大;JDK1.8 利用扩容为原容量 2 倍的特性,通过判断元素哈希值二进制新增位是 0 或 1,直接确定元素留原位置或移至原位置加旧容量的新位置,效率大幅提升。
视频结尾提出思考题:为何红黑树需等链表长度超 8 才转换,而非初始就使用。
虚拟线程用了吗?
面试官通过虚拟线程相关问题考察后端开发者的系统资源边界思维。
虚拟线程坑点说明:4 年后端开发者称将 Tomcat 换成虚拟线程后性能翻倍,但面对数据库慢查询引发的问题无法解答。数据库响应从 50ms 变为 2 秒时,旧平台 200 请求占满线程池,数据库可承载;换虚拟线程后 Tomcat 能处理 10 万请求,而数据库连接池仅 20 个连接,10 万线程等待导致数据库崩溃,同时旧 JDBC 驱动的 synchronized 会导致虚拟线程 pinned、载体线程被占死。
适用边界讲解:虚拟线程仅将调度从 OS 内核移到 JVM 用户态,创建和切换成本低,但执行仍占用载体线程,IO 操作本身仍占资源,下游物理资源才是瓶颈;适合 IO 密集型、下游可水平扩展、无状态、可无限并发的场景,不适合抢占稀缺资源或 CPU 密集型场景。
三层防御方案:
理解本质局限:明确虚拟线程的调度逻辑、资源占用特点及适用场景。
信号量限流保护:为下游资源设独立信号量限流,采用舱壁隔离不同业务,设置超时与降级策略。
避坑与监控:将 synchronized 替换为 ReentrantLock,加启动参数检测 Pinning 问题,监控虚拟线程、载体线程及连接池使用率,压测用真实流量模拟。
这道题的核心是考察开发者从 "会用工具" 到 "理解系统" 的工程思维升级。
8000万订单查不动,一定要分库分表吗?
美团三面中 8000 万订单查询慢的问题,视频给出了无需分库分表的轻量化解决方案。
问题根源:单表 B + 树超四层致磁盘 IO 激增;冷数据占用 Buffer Pool 挤走热数据,内存不足。
异构解耦:Canal 监听 MySQL Binlog 同步至 ES;新订单 5 分钟内走 MySQL 主库,超时后路由到 ES 处理复杂查询。
冷热分离:用 pt-archiver 工具低流量搬运冷数据到 HBase,控制热表行数在 500 万以内。
数据安全:归档逻辑为先入冷库再删热库;每日凌晨跑 Spark 任务核对热库、冷库与 ES 的数据总量。
视频强调大厂技术人需保持核心数据库的轻盈与纯粹。
既然Redis是单线程,那它为什么还能这么快?
面试官详解 Redis 单线程高性能的底层逻辑及版本演进。
核心性能逻辑:Redis 核心瓶颈为网络 IO,而非 CPU;其单线程模型(网络 IO 与键值对读写由主线程完成)依托 IO 多路复用机制,单个线程可同时监听多客户端连接读写事件,无事件时线程阻塞或处理其他任务,非阻塞 IO 能高效处理海量并发连接,还可避免多线程共享数据的锁机制开销。
6.0 版本演进:因网络硬件延迟降低、Redis 存储数据变复杂,部分操作 CPU 消耗显现,单线程处理慢操作会阻滞后续请求;Redis 6.0 仅将网络 IO 读写改为多线程处理,核心命令执行仍为单线程,既利用多核 CPU 加速网络包解析,又保障核心数据结构线程安全。
性能全面评估:拖垮 Redis 性能的关键是慢查询命令(如大集合交集运算),会让单线程长时间阻滞;生产环境中,内存不足引发的内存碎片、写时复制性能抖动、频繁淘汰策略,以及 RDB 和 AOF 持久化(尤其是 AOF 刷盘策略)的磁盘 IO 开销,均是常见性能瓶颈。
回答 Redis 高性能问题需结合底层网络模型、版本演进及全面性能评估,才能达到大厂面试要求。
所以redis之所以快是因为,io的多路复用,单线程处理海量连接,6.0的升级优化是引入的多线程是处理网络io,但是核心命令执行仍是单线程保证数据安全
财务小妹导了个千万级报表,线上核心服务居然直接OOM宕机了
财务小妹导出千万级报表导致线上核心服务 OOM 宕机,视频讲解了该问题的排查与解决全流程。
问题排查定位:服务 OOM 宕机后,先用 gmall 工具导出服务挂掉时的堆内存快照,再用 eclipse mat 或 jprofiler 工具分析快照,找出占用内存的核心对象及问题根源。
内存泄露修复:若排查出是内存泄露,及时关闭未关闭的文件流、数据库连接、网络连接等资源,清理用完未清空的 hashmap、list 等集合对象。
大数据量处理:若为数据量过大导致,采用流式处理或分批处理方式,避免一次性加载全部数据;对报表导出这类非实时任务,改为后台异步处理,完成后通知用户下载。
JVM 调优监控:理解 JVM 内存模型,根据应用特点配置堆内存大小、新生代与老年代比例、GC 收集器等参数;引入 skywalking、pinpoint 等 APM 工具,实时监控 GC 情况并设置内存阈值预警。
经上述四步处理后,系统可恢复稳定运行,性能得到提升。
二阶段提交,听完这个故事你就悟了
MySQL 通过二阶段提交解决主从库数据一致性问题。
账本作用说明:MySQL 有两类核心账本,内部账本 redo log 用于意外断电后恢复数据,外部账本 binlog 用于从库同步或数据备份,二者需保持一致,否则主从库数据会混乱。
顺序写入弊端:若先写 redo log 再写 binlog,中途断电会导致主库恢复数据、从库无对应数据;若先写 binlog 再写 redo log,中途断电会导致从库同步数据、主库无对应数据。
二阶段流程:第一阶段为准备阶段,redo log 写入修改内容并标记为待定;第二阶段先完成 binlog 的写入保存,再将 redo log 的待定标记改为正式提交。
异常处理逻辑:数据库重启时,若 redo log 为待定且无对应 binlog 记录,会回滚操作;若 redo log 为待定但有对应完整 binlog 记录,会将 redo log 改为正式提交。
视频结尾提出思考题,询问二阶段提交对数据库写入性能的影响。
kafka如何保证消息不重复消费?
视频讲解了 Kafka 消息重复消费的成因与生产端、消费端的对应解决方法。
重复场景说明:存在两类重复场景,一是 Producer 发送消息后因网络抖动,误以为发送失败重试,导致 Kafka 收到重复消息;二是 Consumer 处理消息中途报错,未提交 offset,重启后 Kafka 重发消息引发重复消费。
生产端解决方法:开启 Kafka 的 enable.idempotence 配置,Producer 发送消息时会携带虚拟 id,Kafka Broker 会存储 pid、target、partition 与虚拟号的组合,收到相同虚拟号的消息会直接忽略,可解决生产发送层面的重复。
消费端解决方法:从业务层面做幂等处理,分三步执行:给消息设置唯一标识,如订单 id、消息 id 或 MD5 值;消费前查询 Redis 或数据库,确认该标识未被处理再执行消费;消费完成后将处理标记写入 Redis 或数据库,同时可借助共享锁、Redis 分布式锁避免并发问题。
视频明确 Kafka 仅能减少重复,最终需依赖业务幂等保证无重复消费。
如果你的连接池满了,你的锁能自动释放嘛?
面试官通过后端面试场景,讲解连接池耗尽引发线程阻塞问题的排查与防御方案。
面试场景还原:面试官询问 4 年后端开发者线上问题排查经验,开发者称靠看日志、用 Arthas 或重启解决;面试官再问核心接口 RT 飙升、线程卡在扣库存 synchronized 方法的场景,开发者误判为数据库问题,面试结束。
问题根因拆解:该场景的问题并非数据库或 synchronized 内代码执行慢,而是 SQL 执行时数据库连接池已满,导致线程阻塞。
现场排查方案:间隔 3 秒打两次 jstack 线程栈对比,结合 show processlist 确认数据库无问题,再通过连接池监控查看 active 连接数是否等于 maxActive,判断连接池是否耗尽。
事前防御设计:压测需模拟瞬间流量,监控连接池 waiting 线程数等指标,设置 1-3 秒的 connection-timeout 实现快速失败。
代码层优化方案:缩小锁粒度,不在 synchronized 块内做连接池操作;调优连接池参数,开启泄漏检测;连接池耗尽时触发熔断降级。
该问题考察开发者从单一日志排查升级到全链路排查的能力。