互联网大厂 Java 面试实录:谢飞机的三轮攻防战

📅 2026/7/6 4:18:19 👁️ 阅读次数 📝 编程学习
互联网大厂 Java 面试实录:谢飞机的三轮攻防战

互联网大厂 Java 面试实录:谢飞机的三轮攻防战

场景:某互联网大厂终面现场。面试官表情严肃,谢飞机抱着水杯坐得笔直,嘴上说自己“准备得很充分”,手心却全是汗。

第一轮:Java 核心与集合

面试官:先别紧张,讲讲 Java 中==equals()的区别。

谢飞机:==比较的是是不是同一个人,equals()比较的是是不是同一个灵魂。比如两个new String("飞").new String("机")==不行,equals()就行。

面试官:这个回答不错,说明基础还没还给老师。那hashCode()为什么要和equals()一起重写?

谢飞机:因为……它俩是绑定套餐。你要是只重写equals()HashMap可能以为你是陌生人;重写了hashCode(),它才能先按门牌号找,再按身份证确认。

面试官:行。ArrayListLinkedList有什么区别,什么场景用哪个?

谢飞机:ArrayList像地铁,查找快,插入中间会挤得慌;LinkedList像接力赛,插入删除方便,但查找得一个个问。平时大多数场景我先用ArrayList,别跟内存过不去。

面试官:HashMap底层为什么快?JDK 8 做了什么优化?

谢飞机:它先算hash,然后定位桶位,再在桶里找。JDK 8 之后,链表太长会变红黑树,听说这样能把“排队买奶茶”的人群变成“有秩序的排队系统”。

面试官:最后一个,finalfinallyfinalize()分别是什么?

谢飞机:final是不让你改,finally是无论如何都要执行,finalize()是对象临走前最后看一眼世界,不过这个现在基本不推荐用了。


第二轮:并发、JUC、JVM 与线程池

面试官:好,基础还行。现在说说synchronizedReentrantLock的区别。

谢飞机:synchronized是 JVM 帮你上锁,写法简单,出了门自动解锁;ReentrantLock更灵活,能尝试加锁、可中断、可公平锁。一个像保安大叔,一个像门禁系统。

面试官:那你知道volatile是干什么的吗?它能保证什么,不能保证什么?

谢飞机:volatile能保证可见性,线程改了值,别的线程能立刻看到;还能禁止部分重排序。它不能保证原子性,所以i++这种复合操作还是会翻车。

面试官:线程池为什么要用?核心参数有哪些?

谢飞机:因为线程创建和销毁都贵,频繁搞会像外卖骑手每单都重新买车。线程池可以复用线程,提高性能,还能控制并发。

面试官:说说ThreadPoolExecutor的核心参数。

谢飞机:corePoolSize是常驻工位,maximumPoolSize是最大编制,workQueue是待办队列,keepAliveTime是临时工待岗时间,threadFactory是招人部门,handler是队列满了以后怎么处理。

面试官:如果队列满了会怎样?拒绝策略有哪些?

谢飞机:AbortPolicy直接报错,CallerRunsPolicy让提交任务的人自己干,DiscardPolicy直接扔掉,DiscardOldestPolicy扔掉最老的任务。这个问题像“工位满了还要进人”,总得有人背锅。

面试官:好。JVM 里对象什么时候进入老年代?垃圾回收了解多少?

谢飞机:对象一般先在新生代,熬过几次 GC 还活着就晋升老年代。GC 有 Minor GC、Major GC、Full GC。年轻人死得快,老同志更难请走。

面试官:那你说下类加载过程。

谢飞机:加载、验证、准备、解析、初始化。加载是把 class 搬进来,验证是查户口,准备是分配静态变量内存,解析是把符号引用变成直接引用,初始化才开始真正赋值。


第三轮:框架、中间件、数据库与工程化

面试官:前面还算顺。现在聊聊 Spring,什么是 IOC 和 AOP?

谢飞机:IOC 是把对象创建和依赖注入交给容器,不用自己满世界new。AOP 是把日志、事务、权限这些和业务无关但到处都要用的逻辑抽出来,统一织进去。

面试官:Spring 事务失效的场景你知道吗?

谢飞机:大概知道一些。比如同类内部方法调用没走代理、异常被吞了、方法不是public、数据库引擎不支持事务、事务传播行为不对……反正事务就像承诺书,得按规则签,不能私下口头说算数。

面试官:MyBatis 的一级缓存和二级缓存区别是什么?

谢飞机:一级缓存是SqlSession级别,默认开启,生命周期短;二级缓存是Mapper级别,跨会话共享,得配置后才有。简单说,一个是办公室抽屉,一个是部门公共柜子。

面试官:说说 Redis 常见数据结构和缓存穿透、击穿、雪崩。

谢飞机:Redis 有 String、Hash、List、Set、ZSet,还有 Bitmap、HyperLogLog、Geo、Stream。穿透是查不存在的数据一直打到数据库;击穿是热点 key 突然失效;雪崩是一大批 key 同时失效。

面试官:怎么解决?

谢飞机:穿透可以用布隆过滤器、缓存空值;击穿可以加互斥锁、逻辑过期;雪崩可以给 key 加随机过期时间、分批预热、限流降级。

面试官:MySQL 索引为什么能快?B+TreeBTree有什么区别?

谢飞机:索引本质是减少扫描范围。B+Tree的数据都在叶子节点,叶子之间还有链表,适合范围查询;BTree不是这么典型的叶子链表结构。MySQL 常用B+Tree,因为既能快速定位,也方便顺序遍历。

面试官:最后,Docker 你会怎么理解?项目里怎么用?

谢飞机:Docker 就是把应用和运行环境打包成镜像,像把一碗面连锅一起端走,到哪儿都能煮。项目里常用来统一环境、快速部署、做 CI/CD,避免“我电脑上能跑”的经典甩锅。

面试官:好了,今天先到这里。你回去等通知吧。

谢飞机:好的老师,我回去再把“缓存三兄弟”和“线程池四件套”背熟一点,争取下次别把volatile说成“高贵的变量”。


面试题标准答案详解

1. Java 核心

==equals()

  • ==比较的是引用是否相同;对于基本类型比较的是值是否相同。
  • equals()默认也是比较引用,但很多类会重写它用于比较内容,比如StringInteger
  • 约定:如果重写了equals(),通常也要重写hashCode(),否则在哈希容器中会出现逻辑不一致。

hashCode()为什么和equals()配套

  • HashMap先通过hashCode()定位桶,再通过equals()比较是否为同一个键。
  • 如果两个对象equals()相等,它们的hashCode()必须相等。
  • 反过来不一定:hashCode()相等只能说明可能冲突。

ArrayListLinkedList

  • ArrayList基于动态数组,随机访问快,尾部追加通常效率高。
  • LinkedList基于双向链表,插入和删除某些场景更有优势,但随机访问慢。
  • 业务上大多数情况优先ArrayList,因为读多写少很常见。

HashMap底层原理

  • JDK 8 之后底层是数组 + 链表/红黑树。
  • 通过hash()定位数组下标,冲突时用链表或红黑树存储。
  • 当链表长度超过阈值且数组容量足够大时会树化,提高查找效率。
  • 扩容会触发重新分布元素,因此要关注初始化容量,避免频繁扩容。

finalfinallyfinalize()

  • final:修饰变量、方法、类,表示不可修改、不可重写、不可继承。
  • finally:异常处理中的代码块,无论是否抛异常通常都会执行。
  • finalize():对象被回收前可能调用的方法,但不可靠,已不推荐使用。

2. 并发、JUC、JVM

synchronizedReentrantLock

  • synchronized是 JVM 层面的内置锁,语法简单,自动释放锁。
  • ReentrantLock是 JUC 提供的显式锁,功能更强大。
  • ReentrantLock支持公平锁、非公平锁、尝试加锁、可中断、条件变量等。
  • 选择上,如果需求简单优先synchronized;需要高级特性再用ReentrantLock

volatile

  • 保证变量对所有线程的可见性。
  • 可以防止指令重排序带来的部分问题。
  • 不能保证复合操作的原子性,比如count++不是线程安全的。
  • 常用于状态标识、单例双重检查中的实例引用等。

为什么要用线程池

  • 避免频繁创建和销毁线程带来的开销。
  • 控制系统最大并发量,防止资源耗尽。
  • 统一管理线程生命周期,提高任务执行效率。

ThreadPoolExecutor参数

  • corePoolSize:核心线程数,常驻线程数量。
  • maximumPoolSize:最大线程数。
  • workQueue:任务队列,用于缓存等待执行的任务。
  • keepAliveTime:非核心线程空闲后存活时间。
  • threadFactory:创建线程的工厂。
  • handler:拒绝策略。

拒绝策略

  • AbortPolicy:直接抛异常。
  • CallerRunsPolicy:由提交任务的线程执行任务。
  • DiscardPolicy:直接丢弃任务。
  • DiscardOldestPolicy:丢弃队列中最老的任务。

JVM 与垃圾回收

  • Java 对象通常先分配在堆的新生代。
  • 对象经过多次 GC 仍存活,可能晋升到老年代。
  • Minor GC主要回收新生代,Major GCFull GC影响范围更大,停顿时间也通常更长。
  • 需要根据对象生命周期特征合理设计代码,减少大对象和短命对象混用带来的回收压力。

类加载过程

  • 加载:读取 class 文件并生成Class对象。
  • 验证:确保字节码合法、安全。
  • 准备:为静态变量分配内存并设置默认值。
  • 解析:把符号引用替换为直接引用。
  • 初始化:执行静态变量赋值和静态代码块。

3. Spring、MyBatis、Redis、MySQL、Docker

IOC 和 AOP

  • IOC:控制反转,把对象创建和依赖管理交给容器。
  • DI:依赖注入,是 IOC 的具体实现方式。
  • AOP:面向切面编程,把日志、事务、权限等横切逻辑统一处理。
  • Spring 通过代理机制实现 AOP,常见有 JDK 动态代理和 CGLIB。

Spring 事务失效常见原因

  • 同类内部调用,未经过代理对象。
  • 异常被捕获后未继续抛出,事务无法回滚。
  • 方法修饰符不符合事务要求,例如非public
  • 数据库引擎不支持事务,如 MyISAM。
  • 传播行为、隔离级别配置不合理。

MyBatis 一级缓存和二级缓存

  • 一级缓存:SqlSession级别,默认开启,同一个会话内有效。
  • 二级缓存:Mapper级别,需要显式开启,多个会话可共享。
  • 缓存适合读多写少的场景,但要注意一致性问题。

Redis 常见数据结构

  • String:最基础,适合缓存对象、计数器。
  • Hash:适合对象字段存储。
  • List:适合队列、消息流转。
  • Set:适合去重、标签。
  • ZSet:适合排行榜、延迟队列。
  • 其他结构如BitmapHyperLogLogGeoStream也很常见。

缓存穿透、击穿、雪崩

  • 穿透:查询不存在数据,数据库和缓存都没有,流量直接打到数据库。
    • 解决:布隆过滤器、缓存空值、接口参数校验。
  • 击穿:热点 key 过期瞬间,大量请求打到数据库。
    • 解决:互斥锁、逻辑过期、热点预热。
  • 雪崩:大量 key 同时失效。
    • 解决:过期时间加随机值、分批过期、限流、降级。

MySQL 为什么用B+Tree

  • B+Tree叶子节点存储数据,非叶子节点只存索引信息。
  • 叶子节点有链表,范围查询和排序更高效。
  • 树高更低,磁盘 IO 次数更少,适合数据库场景。

Docker 的核心价值

  • 把应用和依赖一起封装成镜像,保证环境一致性。
  • 容器启动快、资源占用低,适合微服务和自动化部署。
  • 常用于开发测试环境统一、CI/CD、快速扩缩容。

4. 面试小结

  • 基础题要回答准确、简洁、抓重点。
  • 并发和 JVM 题要说明原理、风险和适用场景。
  • 框架和中间件题要结合业务场景,讲清“为什么这样设计”。
  • 面试时如果不会,先说出你知道的部分,再补充思路,比胡乱猜更稳。