互联网大厂 Java 面试实录:谢飞机的三轮攻防战
互联网大厂 Java 面试实录:谢飞机的三轮攻防战
场景:某互联网大厂终面现场。面试官表情严肃,谢飞机抱着水杯坐得笔直,嘴上说自己“准备得很充分”,手心却全是汗。
第一轮:Java 核心与集合
面试官:先别紧张,讲讲 Java 中==和equals()的区别。
谢飞机:==比较的是是不是同一个人,equals()比较的是是不是同一个灵魂。比如两个new String("飞").new String("机"),==不行,equals()就行。
面试官:这个回答不错,说明基础还没还给老师。那hashCode()为什么要和equals()一起重写?
谢飞机:因为……它俩是绑定套餐。你要是只重写equals(),HashMap可能以为你是陌生人;重写了hashCode(),它才能先按门牌号找,再按身份证确认。
面试官:行。ArrayList和LinkedList有什么区别,什么场景用哪个?
谢飞机:ArrayList像地铁,查找快,插入中间会挤得慌;LinkedList像接力赛,插入删除方便,但查找得一个个问。平时大多数场景我先用ArrayList,别跟内存过不去。
面试官:那HashMap底层为什么快?JDK 8 做了什么优化?
谢飞机:它先算hash,然后定位桶位,再在桶里找。JDK 8 之后,链表太长会变红黑树,听说这样能把“排队买奶茶”的人群变成“有秩序的排队系统”。
面试官:最后一个,final、finally、finalize()分别是什么?
谢飞机:final是不让你改,finally是无论如何都要执行,finalize()是对象临走前最后看一眼世界,不过这个现在基本不推荐用了。
第二轮:并发、JUC、JVM 与线程池
面试官:好,基础还行。现在说说synchronized和ReentrantLock的区别。
谢飞机: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+Tree和BTree有什么区别?
谢飞机:索引本质是减少扫描范围。B+Tree的数据都在叶子节点,叶子之间还有链表,适合范围查询;BTree不是这么典型的叶子链表结构。MySQL 常用B+Tree,因为既能快速定位,也方便顺序遍历。
面试官:最后,Docker 你会怎么理解?项目里怎么用?
谢飞机:Docker 就是把应用和运行环境打包成镜像,像把一碗面连锅一起端走,到哪儿都能煮。项目里常用来统一环境、快速部署、做 CI/CD,避免“我电脑上能跑”的经典甩锅。
面试官:好了,今天先到这里。你回去等通知吧。
谢飞机:好的老师,我回去再把“缓存三兄弟”和“线程池四件套”背熟一点,争取下次别把volatile说成“高贵的变量”。
面试题标准答案详解
1. Java 核心
==和equals()
==比较的是引用是否相同;对于基本类型比较的是值是否相同。equals()默认也是比较引用,但很多类会重写它用于比较内容,比如String、Integer。- 约定:如果重写了
equals(),通常也要重写hashCode(),否则在哈希容器中会出现逻辑不一致。
hashCode()为什么和equals()配套
HashMap先通过hashCode()定位桶,再通过equals()比较是否为同一个键。- 如果两个对象
equals()相等,它们的hashCode()必须相等。 - 反过来不一定:
hashCode()相等只能说明可能冲突。
ArrayList和LinkedList
ArrayList基于动态数组,随机访问快,尾部追加通常效率高。LinkedList基于双向链表,插入和删除某些场景更有优势,但随机访问慢。- 业务上大多数情况优先
ArrayList,因为读多写少很常见。
HashMap底层原理
- JDK 8 之后底层是数组 + 链表/红黑树。
- 通过
hash()定位数组下标,冲突时用链表或红黑树存储。 - 当链表长度超过阈值且数组容量足够大时会树化,提高查找效率。
- 扩容会触发重新分布元素,因此要关注初始化容量,避免频繁扩容。
final、finally、finalize()
final:修饰变量、方法、类,表示不可修改、不可重写、不可继承。finally:异常处理中的代码块,无论是否抛异常通常都会执行。finalize():对象被回收前可能调用的方法,但不可靠,已不推荐使用。
2. 并发、JUC、JVM
synchronized和ReentrantLock
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 GC或Full 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:适合排行榜、延迟队列。- 其他结构如
Bitmap、HyperLogLog、Geo、Stream也很常见。
缓存穿透、击穿、雪崩
- 穿透:查询不存在数据,数据库和缓存都没有,流量直接打到数据库。
- 解决:布隆过滤器、缓存空值、接口参数校验。
- 击穿:热点 key 过期瞬间,大量请求打到数据库。
- 解决:互斥锁、逻辑过期、热点预热。
- 雪崩:大量 key 同时失效。
- 解决:过期时间加随机值、分批过期、限流、降级。
MySQL 为什么用B+Tree
B+Tree叶子节点存储数据,非叶子节点只存索引信息。- 叶子节点有链表,范围查询和排序更高效。
- 树高更低,磁盘 IO 次数更少,适合数据库场景。
Docker 的核心价值
- 把应用和依赖一起封装成镜像,保证环境一致性。
- 容器启动快、资源占用低,适合微服务和自动化部署。
- 常用于开发测试环境统一、CI/CD、快速扩缩容。
4. 面试小结
- 基础题要回答准确、简洁、抓重点。
- 并发和 JVM 题要说明原理、风险和适用场景。
- 框架和中间件题要结合业务场景,讲清“为什么这样设计”。
- 面试时如果不会,先说出你知道的部分,再补充思路,比胡乱猜更稳。