互联网大厂Java面试现场:当面试官遇到‘水货程序员‘谢飞机
互联网大厂Java面试现场当面试官遇到水货程序员谢飞机场景设定地点某互联网大厂面试间人物严肃资深面试官老张 vs 培训班刚出来、简历写3年经验的水货程序员谢飞机氛围一面是严肃正经的技术拷问一面是让人哭笑不得的神回复第一轮Java核心基础面试官请先做个自我介绍。谢飞机紧张搓手我叫谢飞机今年24岁Java开发经验3年...其实我...我是某培训机构出来的简历上写的3年经验实际上我刚毕业没多久。但是我很热爱编程面试官面无表情嗯那我们就直接开始吧。先说说 HashMap 的底层实现原理谢飞机眼睛一亮这题我背过HashMap 啊就是存键值对的嘛底层是数组链表JDK 8 之后还加了红黑树。具体来说当我们调用put(key, value)时先对 key 计算 hashCode然后通过扰动函数(h key.hashCode()) ^ (h 16)来减少碰撞然后通过(n - 1) hash找到数组桶的位置如果该位置为空直接放入如果已经有元素了就遍历链表或红黑树当链表长度超过8时会转化为红黑树当红黑树节点数小于6时转回链表当元素数量超过负载因子默认0.75数组容量* 时会触发扩容容量翻倍面试官微微点头不错讲得很清晰。那 ArrayList 和 LinkedList 有什么区别谢飞机这个简单| 对比维度 | ArrayList | LinkedList | |---------|-----------|------------| | 底层结构 | 动态数组 | 双向链表 | | 随机访问 | O(1) 通过索引直接获取 | O(n) 需要遍历 | | 插入删除 | 中间插入删除需移动元素 O(n)尾部插入 O(1) | 只需修改指针 O(1)已知位置 | | 内存占用 | 连续空间更紧凑 | 每个节点需额外存储前后指针 | | 适用场景 | 频繁查询、尾部插入 | 频繁增删、数据量不确定 |面试官很好那线程安全的集合有哪些ConcurrentHashMap 和 Hashtable 有什么区别谢飞机自信满满线程安全的 List 有Vector古老实现方法加 synchronized性能差Collections.synchronizedList()包装类也是方法级同步CopyOnWriteArrayList读写分离读不加锁写时复制原数组适合读多写少场景ConcurrentHashMap 和 Hashtable 的区别锁粒度不同Hashtable 直接在方法上加 synchronized锁住整个表ConcurrentHashMap 在 JDK 7 使用分段锁SegmentJDK 8 使用 CAS synchronized 锁住链表/红黑树的头节点粒度更细性能ConcurrentHashMap 并发性能远超 Hashtable迭代器ConcurrentHashMap 的迭代器是弱一致性的不会抛出 ConcurrentModificationExceptionHashtable 是 fail-fast 的null 值ConcurrentHashMap 不允许 key 或 value 为 nullHashtable 也不允许面试官露出赞赏表情非常好Java 基础很扎实第二轮框架与中间件面试官Spring 的 IOC 和 AOP 能详细说说吗谢飞机有点紧张IOC 就是控制反转嘛...本来是我们自己 new 对象现在是 Spring 容器帮我们管理 Bean 的生命周期。DI 依赖注入是 IOC 的具体实现方式。AOP 是面向切面编程可以在不修改源代码的情况下添加功能。底层用了动态代理如果类实现了接口就用 JDK 动态代理否则用 CGLIB。面试官那 SpringBoot 的自动配置原理是什么谢飞机开始支支吾吾呃...自动配置就是...SpringBoot 启动的时候会加载META-INF/spring.factories文件...里面配置了各种 AutoConfiguration 类...然后通过ConditionalOnClass、ConditionalOnMissingBean这些条件注解来判断是否要创建 Bean...嗯...反正就是约定大于配置...我们不用写 XML 了...面试官皱眉说得比较零散不太清晰。那 MyBatis 的一级缓存和二级缓存呢谢飞机挠头一级缓存是SqlSession 级别的默认开启。同一个 SqlSession 中执行相同的查询会直接从缓存返回不会再去查数据库。但执行增删改操作后会清空缓存。二级缓存是namespace 级别的也就是 Mapper 级别的需要手动开启。多个 SqlSession 可以共享二级缓存...嗯...二级缓存存的是...数据对象...但它有坑比如脏读问题...具体我记不太清了...面试官面无表情好吧那 RabbitMQ 和 XXL-JOB 呢谢飞机RabbitMQ 我用过是消息队列用来解耦、异步、削峰的。它有这些核心概念生产者发消息的消费者收消息的交换机Exchange路由消息到队列类型有 Direct、Topic、Fanout、Headers队列Queue存储消息的绑定Binding交换机和队列的关联规则XXL-JOB 是分布式任务调度框架有调度中心和执行器。调度中心统一管理任务配置和调度执行器负责实际执行任务逻辑。支持 Cron 表达式、分片广播、故障转移这些...嗯...具体怎么配置我忘了...面试官摇头看来你对中间件的理解还停留在表面啊。第三轮高级特性与系统设计面试官说说 JVM 的内存模型和垃圾回收算法。谢飞机咽口水JVM 内存分为堆Heap存放对象实例是 GC 的主要区域栈Stack存放局部变量、操作数栈、方法出口等线程私有方法区Method Area存放类信息、常量、静态变量程序计数器PC Register记录当前线程执行的字节码行号本地方法栈Native Method Stack执行 native 方法垃圾回收算法标记-清除先标记垃圾再清除。有碎片问题标记-复制把内存分为两块只使用一块满了就把存活对象复制到另一块。浪费空间标记-整理标记后把存活对象往一端移动没有碎片但效率低分代回收新生代用复制算法Eden:S0:S18:1:1老年代用标记-整理或标记-清除。垃圾收集器有 CMS、G1...嗯...G1 是区域化回收把堆分成多个 Region可以设定暂停时间目标...反正...就是回收垃圾嘛...面试官失望地摇头太笼统了。那 DDD 领域驱动设计和微服务拆分原则呢谢飞机大脑一片空白DDD 就是...领域驱动设计...把业务划分为不同的领域...有实体、值对象、聚合、领域服务、仓储这些概念...还有限界上下文...微服务拆分就是按业务拆...高内聚低耦合...每个微服务有自己独立的数据库...嗯...具体怎么拆要看业务...面试官叹气最后一个问题假如让你设计一个高并发秒杀系统你会怎么设计谢飞机强行镇定秒杀系统啊前端限流按钮置灰防止重复提交用验证码分散流量CDN加速静态资源放到 CDNNginx负载均衡把流量分发到多个服务器Redis预减库存先在 Redis 中扣减库存减少数据库压力MQ异步下单把下单请求放到消息队列后端慢慢消费处理数据库乐观锁用版本号或 CAS 机制防止超卖限流降级用令牌桶或漏桶算法限流超出直接返回已售罄大概...就是这样吧...面试官沉默片刻好吧谢先生你的回答我了解了。这样你先回去等通知吧我们后续还有几轮面试需要综合评估。谢飞机强颜欢笑好的好的谢谢面试官我回去等通知关上门后谢飞机掏出手机打开招聘软件继续投下一家...附技术点详细解析小白必看以下是对上述面试问题的详细技术解答每个面试者都应该掌握。1. HashMap 底层原理详解数据结构数组 链表 / 红黑树JDK 8| 版本 | 实现方式 | |-----|--------| | JDK 7 | 数组 链表头插法 | | JDK 8 | 数组 链表 红黑树尾插法 |put 流程计算 key 的 hashCode通过(h key.hashCode()) ^ (h 16)扰动计算数组索引(n - 1) hash如果桶为空直接放入如果桶不为空遍历链表/红黑树找到相同 key覆盖 value没找到插入尾部尾插法避免死循环链表长度超过 8 且数组长度超过 64转化为红黑树元素数量超过threshold capacity * loadFactor扩容为原来的 2 倍扩容机制新建数组长度为原来的 2 倍重新计算每个元素的索引位置JDK 8 优化元素在新数组中的位置要么在原位置要么在原位置 原容量为什么负载因子是 0.75这是时间和空间成本的权衡。负载因子越大空间利用率越高但冲突概率增加查询效率降低负载因子越小冲突少但浪费空间。0.75 是经过大量测试得出的平衡值。2. ArrayList vs LinkedList 深度对比| 维度 | ArrayList | LinkedList | |-----|-----------|------------| | 底层结构 | 动态数组Object[]| 双向链表NodeE| | 默认容量 | 10懒加载第一次 add 时才初始化 | 无固定容量 | | 扩容策略 | 1.5 倍扩容oldCapacity (oldCapacity 1) | 无需扩容节点动态分配 | | get(int index) | O(1) 直接寻址 | O(n) 需要遍历 | | add(E) 尾部 | 均摊 O(1)扩容时 O(n) | O(1) 直接挂到尾部 | | add(int index, E) | O(n) 需要移动元素 | O(1) 修改指针已知位置 | | remove(int index) | O(n) 需要移动元素 | O(1) 修改指针已知位置 | | 内存占用 | 连续空间只需存数据 | 每个节点存数据 prev next 指针额外 16 字节左右 | | 随机访问 | 支持通过索引 | 不支持需从头/尾遍历 |选型建议频繁查询 尾部增删 → ArrayList频繁头部/中间增删 数据量大 → LinkedList多线程环境下可用 CopyOnWriteArrayList 替代3. ConcurrentHashMap 深度解析JDK 7 版术Segment HashEntry ReentrantLock默认16个 Segment就是16个锁每个 Segment 独立加锁互不影响并发级别最多16JDK 8 版术Node CAS synchronized放弃了 Segment直接使用 Node 数组put 操作桶为空用 CAS 保证原子性桶不为空用 synchronized 锁住桶的头节点如果是红黑树同样锁住头节点并发级别支持更高的并发性size() 方法用 CounterCell sumCount 累加各桶的元素数不会锁整个表与 Hashtable 对比 | 对比项 | ConcurrentHashMap | Hashtable | |------------|------------------|----------| | 锁机制 | CAS 细粒度锁 | 方法级 synchronized | | 性能 | 高 | 低 | | null 值 | 不允许 | 不允许 | | 迭代器 | 弱一致性 | fail-fast | | 并发级别 | 更高 | 单锁 |4. Spring IOC 与 AOP 详解IOC 控制反转将对象的创建、关联、管理的权限交给 Spring 容器我们不再用new创建对象而是通过注解Autowired、Resource或 XML 配置来声明依赖容器启动时会扫描指定包创建 Bean 并管理其全生命周期Bean 的作用域singleton默认、prototype、request、session、application依赖注入方式构造器注入强制依赖、setter 注入可选依赖、接口注入AOP 切面编程不修改源代码的情况下添加额外功能日志、事务、权限校验等关键概念Aspect切面、JoinPoint连接点、Pointcut切点、Advice通矵Advice 类型Before、After、Around、AfterReturning、AfterThrowing底层实现JDK 动态代理要求目标类必须实现接口通过 Proxy.newProxyInstance() 创建CGLIB 动态代理目标类没实现接口时通过生成子类的方式实现依赖 CGLIB 库5. SpringBoot 自动配置原理核心注解SpringBootApplication包含Configuration声明为配置类EnableAutoConfiguration启动自动配置ComponentScan扫描当前包及子包自动配置流程SpringBoot 启动时加载META-INF/spring.factories文件该文件中配置了大量的AutoConfiguration类如 RedisAutoConfiguration、DataSourceAutoConfiguration 等每个 AutoConfiguration 类使用条件注解判断是否有效ConditionalOnClassclasspath 中是否存在某个类ConditionalOnMissingBean容器中是否已经有某个 BeanConditionalOnProperty配置文件中是否有某个属性ConditionalOnMissingClass、ConditionalOnBean等条件满足时创建对应的 Bean 并注入到 Spring 容器中配置属性来自application.properties或application.yml通过ConfigurationProperties绑定约定大于配置开箱即用大部分情况下不需要手动配置按照框架的约定来就可以直接使用。6. MyBatis 一级缓存与二级缓存一级缓存SqlSession 级别默认开启无需配置同一个 SqlSession 中执行相同的 SQL 查询从缓存返回不会再查数据库缓存的是查询结果的 Java 对象一但执行增删改操作一级缓存会被清空防止脏读不能跨 SqlSession 共享二级缓存namespace/Mapper 级别需要手动开启在 Mapper.xml 中驱动类名多个 SqlSession 可以共享二级缓存缓存的是纯粹的数据而非 Java 对象获取时需要反序列化同样执行增删改后会清空该 Mapper 的缓存注意问题不同的 Mapper 之间可能会产生脏读问题因为二级缓存无法感知其他 Mapper 的修改工作原理 查询时 - 二级缓存 - 一级缓存 - 数据库 缓存优先级二级缓存 一级缓存 数据库7. RabbitMQ 核心概念交换机类型 | 类型 | 工作原理 | 使用场景 | |------|--------------|--------------| | Direct | 完全匹配 routing key | 单点发送 | | Topic | 通配符匹配 routing key*. # | 多条件订阅 | | Fanout | 广播不管 routing key发给所有绑定的队列 | 发布/订阅 | | Headers | 根据 header 属性匹配 | 复杂路由 |高可用机制队列异步生产者绑定 confirm 回调确保消息发送到交换机消息持久化消息设置 delivery_mode 2保存到硬盘队列持久化声明队列时设置 durable true消费者确认开启手动 ack处理完毕后才确认消息队列镜像将队列复制到多个节点实现容灾8. XXL-JOB 分布式任务调度架构组件调度中心统一管理任务配置、触发调度、监控任务执行执行器实际执行任务逻辑的应用向调度中心注册任务最小的执行单元绑定到特定的执行器核心特性Cron 表达式灵活的时间触发策略分片广播任务同时发送给所有执行器每个执行器处理一部分数据如 N 个执行器每个处理 1/N 的数据故障转移一个执行器失败自动将任务调度到其他执行器执行日志完整的执行日志记录支持查看调度结果GLUE 模式支持在线编辑任务代码无需重启应用9. JVM 内存模型与垃圾回收JVM 运行时数据区域| 区域 | 线程共享 | 作用 | |--------|-----------|--------| | 程序计数器 | 线程私有 | 存储当前线程执行的字节码行号指示器 | | Java 堆栈 | 线程私有 | 存储局部变量、操作数栈、方法返回地址 | | 本地方法栈 | 线程私有 | 支持 native 方法的执行 | | 堆 | 线程共享 | 存储对象实例GC 主要区域 | | 方法区 | 线程共享 | 存储类信息、常量、静态变量、运行时常量池 |垃圾回收算法标记-清除Mark-Sweep原理先标记所有需要回收的对象然后统一回收优点实现简单缺点产生内存碎片扫描效率低标记-复制Mark-Copy原理将内存分为大小相等的两块只使用其中一块优点无内存碎片效率高缺点内存利用率低浪费一半空间应用新生代Eden:S0:S18:1:1只浪费10% 标记-整理Mark-Compact原理标记后将存活对象向一端移动然后清除边界外的内存优点无内存碎片缺点移动对象会导致 STWStop The World时间较长分代回收新生代大量对象滚论然死用复制算法Minor GCEden 区对象主要设置大小对象在 S0 和 S1 之间交换每次超过一定年龄默认15次进入老年代老年代存放长期存活的对象用标记-整理或标记-清除Major GC / Full GC元空间JDK 8替代了方法区使用本地内存常见垃圾回收器Serial单线程简单高效适合单 CPUParNewSerial 的多线程版本适合服务器Parallel Scavenge关注吞吐量适合后台计算CMS以最短暂停时间为目标并发回收但产生碎片G1Region 化可设定 STW 目标时间JDK 9默认10. DDD 领域驱动设计核心概念领域Domain业务问题的范畴如订单领域、支付领域子领域Subdomain区分核心子领域、支持子领域、通用子领域限界上下文Bounded Context统一语言和边界每个上下文独立定义概念实体Entity是有唯一标识的对象如 用户ID值对象Value Object由属性值定义的对象不可变如 地址聚合Aggregate一组相关实体和值对象的集合通过聚合根访问领域服务Domain Service处理不属于实体或值对象的业务逻辑领域事件Domain Event记录领域中发生的重要事件仓库Repository对聚合的持久化操作的抽象微服务拆分原则按照限界上下文拆分每个上下文对应一个微服务高内聚低耦合同一上下文内的业务逻辑高度相关每个微服务独立数据库不共享表结构微服务之间通过 API 网关或消息队列通信领域事件用于微服务之间的数据同步每个微服务包含完整的 DDD 层界面层、应用层、领域层、基础层11. 高并发秒杀系统设计架构图客户端 - CDN - Nginx负载均衡- 网关 - 限流组件 - 业务服务群 - Redis预扣库存- MQ异步下单- 数据库设计策略详解前端层限流按钮置灰防止重复提交经典的问题后端也要做配合验证码机制分散抢购流量动态页面静态化减少操作系统负担CDN NginxCDN 加速静态资源图片、CSS、JSNginx 负载均衡分发请求到后端服务器Nginx 层次无法实现 IP 级别的限流ngx_http_limit_req_module限流策略令牌桶算法以固定速率向桶中添加令牌请求消耗令牌令牌不足则等待或拒绝漏桶算法请求一律缓存在漏桶中以固定速率处理超出即丢弃可使用组件Sentinel、Hystrix、Resilience4jRedis 预扣库存将库存数据加载到 Redis 中如 key“seckill:stock:1001”, value100用 Lua 脚本原子性扣减判断库存0然后 DECRRedis 库存为空时直接返回“已售罄”可结合二次判断用 Redis 的 setbit 记录用户是否已抢购过防止 重复抢购MQ 异步下单Redis 预扣成功后将订单消息投递到 RabbitMQ/Kafka后端消费者按照自己的速度处理订单消费者缓冲池如果订单处理失败可以重试或补偿用户MQ 的作用消峰塑谷保护数据库数据库事务处理乐观锁用 version 号匹配CAS 方式更新库存粗粒度锁使用数据库行锁 SELECT ... FOR UPDATE先插入订单数据再扣减库存保证事务一致性兜底方案库存回滚如果用户下单后未支付超时自动释放库存限流降级超出系统承载能力的请求直接返回活动太火爆请稍后再试数据对账每天跑批对账确保库存和订单数据一致结尾面试结束后谢飞机虽然很多问题回答得不够好但这次经历让他明白了自己的差距。 他决定回去好好补课把上述技术点一个一个啃下来。 而屏幕前的你如果能把这篇详细解析中的内容都掌握 相信在真正的面试中一定能让面试官对你刮目相看祝各位面试顺利早日拿到心仪的 Offer