一、Java核心基础深度剖析
集合框架与底层数据结构
在Java中级面试中,集合框架不仅是基础,更是考察候选人对内存管理、数据结构理解程度的试金石。面试官不再满足于“HashMap和HashTable的区别”,而是会深入询问HashMap在JDK1.8后的红黑树转换机制、扩容逻辑以及并发下的安全隐患。
1.1 HashMap源码级解析
HashMap是Java中最常用的数据结构之一。理解其底层实现是Java中级面试重点中的重中之重。
⚡ 底层结构
JDK1.7采用数组+链表,JDK1.8引入数组+链表+红黑树。当链表长度超过8且数组长度超过64时,链表转换为红黑树,将查询时间复杂度从O(n)优化至O(log n)。
⚡ 哈希冲突解决
采用链地址法。通过hashCode计算数组索引,若冲突则通过next指针形成链表或树结构。JDK1.8优化了hash算法,减少高位参与运算,降低冲突概率。
⚡ 扩容机制
默认容量16,负载因子0.75。扩容时容量翻倍,并重新计算元素位置。JDK1.8采用位运算优化,元素要么在原位置,要么在原位置+旧容量处,无需重新hash。
1.2 ConcurrentHashMap实现原理
相比Hashtable和Collections.synchronizedMap,ConcurrentHashMap提供了更高的并发度。JDK1.7使用分段锁(Segment),JDK1.8则采用CAS + synchronized保证线程安全,锁粒度更细,性能更优。
// 示例:简单的线程安全计数器
// 中级面试常问:为什么不用AtomicInteger?
// 答:AtomicInteger适合单字段原子操作,若涉及复合操作(如check-then-act),
// 仍需使用ConcurrentHashMap或显式锁。
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
}
二、主流框架源码与扩展
Spring IoC/AOP与事务管理
掌握Spring框架不仅是会用注解,更要理解其设计模式和生命周期。Spring的核心在于IoC容器管理和AOP切面编程。
Spring Bean的生命周期
这是面试中的高频考点,流程复杂且细节众多。
- ① 实例化:通过构造器或工厂方法创建Bean实例。
- ② 属性赋值:填充Bean的所有属性,执行set方法。
- ③ 初始化:
- 检查Aware接口(如BeanNameAware)。
- 执行BeanPostProcessor的postProcessBeforeInitialization。
- 执行InitializingBean的afterPropertiesSet或自定义init-method。
- 执行BeanPostProcessor的postProcessAfterInitialization(AOP代理在此阶段生成)。
- ④ 销毁:执行DisposableBean或自定义destroy-method。
事务传播行为详解
| 传播行为 | 说明 | 场景示例 |
|---|---|---|
| REQUIRED | 默认值。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。 | 大多数业务操作,如用户注册。 |
| REQUIRES_NEW | 创建一个新事务,如果当前存在事务,则把当前事务挂起。 | 日志记录,无论主业务是否成功,日志必须保存。 |
| NESTED | 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与REQUIRED类似的操作。 | 复杂业务中的子模块,支持部分回滚。 |
| NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,则把当前事务挂起。 | 耗时且不需要事务的操作,如统计报表生成。 |
AOP代理机制
Spring AOP默认使用JDK动态代理(基于接口),如果目标对象没有实现接口,则使用CGLIB(基于子类)。
在Spring Boot 2.x之后,默认倾向于使用CGLIB,可通过spring.aop.proxy-target-class=true强制启用。
// AOP切面示例
@Aspect
@Component
public class LogAspect {
@Around("@annotation(com.example.annotation.BusinessLog)")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("方法执行耗时: " + (endTime - startTime) + "ms");
return result;
}
}
三、Java并发编程实战
JUC包与线程池参数
并发编程是区分初级与中级工程师的关键分水岭。理解线程池参数、锁机制以及内存模型是必备技能。
3.1 线程池七大参数详解
创建线程池时,必须明确指定参数,避免使用Executors工具类(可能导致OOM)。
核心线程数
线程池中保持存活的线程数量,即使它们是空闲的。除非设置allowCoreThreadTimeOut,否则不会被回收。
最大线程数
线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则创建新线程执行任务。
存活时间
线程空闲超过此时间后,非核心线程将被回收。对于核心线程,若allowCoreThreadTimeOut=true,同样适用。
任务队列
用于存储等待执行的任务。常用有ArrayBlockingQueue(有界)、LinkedBlockingQueue(无界,慎用)、SynchronousQueue(不存储,直接移交)。
3.2 volatile关键字与内存屏障
volatile保证了变量的可见性和有序性,但不保证原子性。它通过插入内存屏障防止指令重排。
// 双重检查锁定(DCL)单例模式
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 非原子操作,需volatile防止重排
}
}
}
return instance;
}
}
四、MySQL性能优化与索引
B+树与事务隔离级别
中级Java工程师必须深入理解数据库底层原理。索引结构、事务隔离级别以及慢查询优化是日常开发和面试的核心。
4.1 为什么使用B+树而不是B树或Hash?
- 【B+树】:非叶子节点只存储键,不存储数据,单个节点能容纳更多索引项,降低树高,减少IO次数。所有数据存储在叶子节点,且叶子节点之间通过双向链表连接,利于范围查询。
- 【B树】:非叶子节点也存储数据,导致节点容纳索引变少,树高增加,IO次数增多。
- 【Hash】:仅支持等值查询,不支持范围查询和排序。
4.2 索引失效场景
❌ 模糊查询左通配符
LIKE '%abc' 会导致全表扫描,因为最左匹配原则被破坏。
❌ 类型隐式转换
字符串字段不加引号,如 WHERE name = 123,会导致索引失效。
❌ 函数计算
WHERE YEAR(create_time) = 2023,对索引列进行函数操作会导致失效。
4.3 事务隔离级别与MVCC
MySQL InnoDB引擎默认采用可重复读(Repeatable Read)。通过undo log和Read View实现MVCC(多版本并发控制),保证在RR级别下,事务内多次读取结果一致,并通过Next-Key Lock解决幻读问题。
五、JVM内存模型与调优
GC算法与参数配置
理解JVM内存结构是解决线上OOM(OutOfMemoryError)和CPU飙高的基础。
5.1 内存区域划分
- 【线程私有】:程序计数器、虚拟机栈、本地方法栈。
- 【线程共享】:堆(Heap)、方法区(Metaspace)。
5.2 常见GC算法对比
| 算法 | 优点 | 缺点 |
|---|---|---|
| 标记-清除 | 实现简单 | 产生大量内存碎片,效率较低 |
| 复制算法 | 无内存碎片,效率高 | 内存利用率仅50%(适用于新生代) |
| 标记-整理 | 无内存碎片 | 移动对象成本高(适用于老年代) |
5.3 常用JVM参数
-Xms1g -Xmx1g # 初始堆和最大堆大小一致,避免动态扩容
-Xmn512m # 新生代大小
-XX:+UseG1GC # 使用G1垃圾收集器
-XX:MaxGCPauseMillis=200 # 最大GC停顿时间目标
-XX:+HeapDumpOnOutOfMemoryError # OOM时导出堆快照
六、分布式架构与中间件
Redis、MQ与分布式锁
在微服务架构下,Redis和RabbitMQ/Kafka是解决高并发、高可用的关键组件。
6.1 Redis缓存穿透、击穿、雪崩
- 【穿透】:查询不存在的数据。对策:布隆过滤器、缓存空对象。
- 【击穿】:热点Key过期。对策:互斥锁、逻辑过期。
- 【雪崩】:大量Key同时过期或Redis宕机。对策:随机过期时间、集群部署、限流降级。
6.2 分布式锁实现
基于Redis的分布式锁通常使用SETNX命令,并配合EXPIRE设置超时时间,防止死锁。更推荐使用Redisson客户端,它实现了看门狗(Watchdog)机制,自动续期。
// Redisson分布式锁示例
RLock lock = redisson.getLock("myLock");
try {
lock.lock();
// 业务逻辑
} finally {
lock.unlock();
}