一、Java基础必考三连击
1.1 面向对象特性新解(必考!)
别以为"封装、继承、多态"这老三样背出来就完事了!面试官现在喜欢问:
- 继承的"is-a"关系在项目中的实际应用(举个栗子:支付系统的多种支付方式)
- 多态在框架设计中的妙用(比如Spring的依赖注入)
- 封装在微服务数据隔离中的实践(重要数据怎么藏?)
1.2 String的不可变性暗藏玄机
"String为什么设计成不可变?"这个经典问题,2024年要这么答才够味:
- 安全性(重要参数传递时防篡改)
- 哈希码缓存(HashMap性能优化关键)
- 字符串常量池内存优化(JVM层面的设计智慧)
- 最新变化:Java 15引入的文本块特性对String的影响
1.3 异常处理实战陷阱
try-catch-finally用不好直接扣分!注意这些坑:
// 典型错误示例(别学!)
try {
// 可能抛异常的代码
} catch (Exception e) {
throw new RuntimeException("包装异常"); // 吞掉原始堆栈!
} finally {
// 资源关闭可能被覆盖
}
正确姿势:
- 使用try-with-resources(Java7+)
- 异常包装要保留原始堆栈(构造器传参)
- 自定义异常要带上下文信息
二、集合框架进阶八问
2.1 HashMap的底层革命
JDK8的HashMap做了哪些重大改进?
- 红黑树化(链表长度>8时变身)
- 扰动函数优化(hash()方法简化的秘密)
- 并发问题:为什么仍然线程不安全?
2.2 ConcurrentHashMap的段位升级
对比不同版本的并发实现:
版本 | 实现方式 | 并发度 |
---|---|---|
JDK7 | Segment分段锁 | 固定分段 |
JDK8+ | CAS+synchronized | 动态扩容 |
2.3 流式编程实战技巧
Java8的Stream API使用禁忌:
List<String> list = Arrays.asList("a", "b", "c");
// 错误用法(多次消费流)
Stream<String> s = list.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); // 抛出异常!
正确打开方式:
- 链式调用(不要拆分中间操作)
- 注意并行流的线程安全问题
- 合理使用收集器(Collectors高级用法)
三、Spring框架灵魂拷问
3.1 循环依赖破解之道
Spring的三级缓存解决循环依赖流程图:
1. 创建A对象(半成品)→ 放入三级缓存
2. 填充A的B属性 → 触发创建B对象
3. 创建B对象(半成品)→ 放入三级缓存
4. 填充B的A属性 → 从三级缓存获取A
5. 完成B初始化 → 放入一级缓存
6. 完成A初始化 → 放入一级缓存
致命缺陷:构造器注入无法解决循环依赖!
3.2 AOP实现原理新认知
动态代理的两种实现对比:
JDK动态代理 | CGLIB | |
---|---|---|
代理方式 | 接口代理 | 子类继承 |
性能 | 调用快,创建慢 | 创建快,调用稍慢 |
局限性 | 必须实现接口 | 不能代理final类/方法 |
Spring选择策略 | 默认JDK,强制CGLIB需配置 | 自动根据目标选择 |
3.3 Spring Boot自动配置黑魔法
自动配置的触发机制:
- @SpringBootApplication组合注解
- @EnableAutoConfiguration触发加载
- spring.factories中的配置类
- @Conditional条件注解过滤
- 最终生效的配置类集合
调试技巧:启动时添加–debug参数查看匹配日志
四、并发编程夺命连环问
4.1 线程池的7大参数详解
面试官最爱问的线程池创建参数:
ThreadPoolExecutor(
int corePoolSize, // 核心线程数(常驻员工)
int maximumPoolSize, // 最大线程数(临时工+正式工)
long keepAliveTime, // 闲置线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列(候客区)
ThreadFactory threadFactory, // 线程创建工厂
RejectedExecutionHandler handler // 拒绝策略(客满处理)
)
四种拒绝策略实战场景:
- AbortPolicy:直接抛异常(默认)
- CallerRunsPolicy:让提交线程自己执行
- DiscardPolicy:默默丢弃
- DiscardOldestPolicy:丢弃队列最老任务
4.2 volatile关键字的三重境界
- 可见性保证(MESI缓存一致性协议)
- 禁止指令重排序(内存屏障)
- 不保证原子性(需要配合CAS)
4.3 ThreadLocal的内存泄漏陷阱
正确使用姿势:
try {
threadLocal.set(someValue);
// 使用threadLocal...
} finally {
threadLocal.remove(); // 必须清理!!
}
底层原理:
- ThreadLocalMap使用弱引用防止内存泄漏
- key是弱引用,value是强引用的设计问题
五、JVM调优终极挑战
5.1 内存区域划分新认知(JDK17+)
最新内存结构:
- 堆(分代收集依然存在)
- 元空间(取代永久代)
- 栈区(线程私有)
- 直接内存(NIO使用)
- 代码缓存(JIT编译产物)
5.2 GC算法实战选择
常用收集器对比表:
收集器 | 工作模式 | 适用场景 | 停顿时间 |
---|---|---|---|
Serial | 单线程 | 客户端应用 | 长 |
Parallel | 多线程 | 吞吐量优先 | 中等 |
CMS | 并发标记 | 低延迟要求 | 短(但不稳定) |
G1 | 分区收集 | 平衡型应用 | 可预测 |
ZGC | 并发转移 | 超大堆内存 | 10ms以内 |
5.3 线上OOM排查四板斧
- 快速保存现场:内存快照(-XX:+HeapDumpOnOutOfMemoryError)
- 分析工具:MAT/JProfiler/VisualVM
- 常见原因:
- 内存泄漏(未释放对象)
- 不合理的缓存设计
- 大对象分配失败
- 预防措施:
- 合理的JVM参数配置
- 压力测试
- 监控报警系统
六、设计模式高频三剑客
6.1 单例模式的五种写法
双重检查锁的volatile必要性:
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 没有volatile可能指令重排序
}
}
}
return instance;
}
}
枚举单例为什么是最佳实践?(防反射攻击)
6.2 工厂模式的Spring实践
BeanFactory vs ApplicationContext:
- 工厂方法模式的具体应用
- 延迟加载与预加载的区别
- 如何扩展自定义Bean工厂
6.3 动态代理模式源码级理解
Spring AOP的两种实现:
- JDK动态代理(基于接口)
- CGLIB字节码增强(基于继承)
手写简易代理框架:
public class MyProxy implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method");
Object result = method.invoke(target, args);
System.out.println("After method");
return result;
}
}
七、Java新特性速递(2024版)
7.1 记录类(Records)妙用
public record User(Long id, String name) {
// 自动生成:
// 1. 全参构造
// 2. equals/hashCode
// 3. toString
}
适用场景:DTO对象、不可变数据载体
7.2 模式匹配的三大进化
- instanceof模式匹配:
if (obj instanceof String s) {
// 直接使用s变量
}
- switch模式匹配:
switch (obj) {
case Integer i -> System.out.println(i);
case String s -> System.out.println(s);
default -> System.out.println("Unknown");
}
- 空case处理:
switch (str) {
case null -> System.out.println("null啦!");
case "hello" -> System.out.println("你好!");
default -> System.out.println("其他");
}
7.3 虚拟线程(协程)实战
创建虚拟线程的两种方式:
// 方式1:Thread.startVirtualThread()
Thread vt = Thread.startVirtualThread(() -> {
System.out.println("Hello from virtual thread!");
});
// 方式2:ExecutorService
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
System.out.println("Task running in virtual thread");
});
}
性能对比:
- 传统线程:1:1内核线程
- 虚拟线程:M:N映射,轻松创建百万级线程
八、面试实战技巧总结
- 项目经验要准备3个技术难点及解决方案
- 算法准备:LeetCode Hot 100至少要刷两遍
- 设计题常用方法:先说暴力解,再逐步优化
- 遇到不会的问题:诚实回答+后续学习计划
- 反问环节准备:问团队技术栈/晋升机制
最后划重点:2024年Java面试越来越注重底层原理+实战经验的结合,死记硬背已经不够用了!建议大家通过:
- GitHub找优质开源项目学习(比如Spring源码)
- 搭建个人技术博客记录学习心得
- 参与线上编程挑战(比如Codeforces)
- 定期模拟技术面试
记住:面试是双向选择,展示真实的你最重要!祝各位攻城狮面一家过一家,offer拿到手软~