Java面试核心知识点 深度拆解+高频易错
一、集合框架进阶
1. HashMap的负载因子为什么是0.75?
答案:
- 空间与时间的权衡:负载因子0.75时,扩容阈值(容量*0.75)在哈希冲突概率和空间利用率之间达到平衡。
- 数学依据:泊松分布计算表明,当负载因子为0.75时,链表长度超过8的概率极低(约千万分之一)。
避坑:
- 高并发场景避免使用HashMap,选择ConcurrentHashMap。
2. ConcurrentHashMap在JDK 8中的优化
答案:
- 锁粒度细化:JDK 7使用Segment分段锁(16段),JDK 8改为对每个哈希桶的头节点加锁(synchronized + CAS)。
- 红黑树优化:链表长度≥8时转为红黑树,查询效率从O(n)提升到O(log n)。
源码片段:
java
// JDK8 putVal方法核心逻辑
if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
synchronized (f) {
// 桶内链表或红黑树操作
}
}
二、并发编程深度
3. 线程池的饱和策略有哪些?适用场景?
答案:
- AbortPolicy(默认):抛出RejectedExecutionException,适合严格要求任务不丢失的场景。
- CallerRunsPolicy:由提交任务的线程执行任务,适用于异步任务可降级的场景。
- DiscardOldestPolicy:丢弃队列中最旧的任务,适合实时性要求高的场景。
- DiscardPolicy:静默丢弃新任务,适用于监控完备且允许丢任务的场景。
调优建议:
- IO密集型任务可设置较大的队列容量(如LinkedBlockingQueue)。
4. AQS(AbstractQueuedSynchronizer)的核心原理
答案:
- CLH队列:通过双向链表管理等待线程,避免饥饿问题。
- state变量:通过CAS操作修改state(如ReentrantLock中state=0表示未锁定,state>0表示重入次数)。
- 模板方法设计:子类需实现tryAcquire/tryRelease方法定义获取/释放锁的逻辑。
源码解析:
java
// ReentrantLock.NonfairSync尝试获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
setState(c + acquires); // 重入计数
return true;
}
return false;
}
三、JVM与性能调优实战
5. G1垃圾收集器的Region分配策略
答案:
- Region划分:将堆划分为多个等大小Region(默认2048个),每个Region可能是Eden、Survivor或Old区。
- 回收逻辑:
- Young GC:回收Eden和Survivor区。
- Mixed GC:回收部分Old区(优先回收垃圾比例高的Region)。
- 参数调优:
-XX:MaxGCPauseMillis=200
:设定最大停顿时间目标。-XX:G1NewSizePercent=5
:新生代最小占比。
避坑:
- 避免Region过小(导致内存碎片)或过大(影响回收效率)。
6. 如何诊断线程死锁?
答案:
- 命令行工具:
jstack <pid>
:查看线程栈,搜索"deadlock"。jconsole
:图形化查看线程状态。
- Arthas命令:
thread -b
:直接定位死锁线程。
示例输出:
Found one Java-level deadlock:
"Thread-2" waiting to lock monitor 0x000000001ac3e558 (object 0x000000076b08d2d0)
"Thread-1" waiting to lock monitor 0x000000001ac3e558 (object 0x000000076b08d2d0)
四、Spring框架核心
7. Spring事务传播机制PROPAGATION_REQUIRES_NEW的实现原理
答案:
- 行为:始终启动新事务,挂起当前事务(如果存在)。
- 源码逻辑:
TransactionAspectSupport.invokeWithinTransaction()
中通过TransactionManager.suspend()
挂起当前事务。- 新事务提交后恢复原事务。
- 适用场景:日志记录(即使主事务回滚,日志仍需提交)。
避坑:
- 嵌套事务过多可能导致数据库连接耗尽。
8. @Autowired和@Resource的区别
答案:
- 来源:
@Autowired
是Spring注解,默认按类型注入。@Resource
是JSR-250规范,默认按名称注入。
- 依赖查找顺序:
@Autowired
+@Qualifier("name")
→ 按名称注入。@Resource(name="name")
→ 按名称注入,失败则按类型。
代码示例:
java
@Autowired
@Qualifier("mysqlDao")
private UserDao userDao;
@Resource(name="oracleDao")
private UserDao userDao;
五、系统设计题
9. 如何设计一个短链生成系统?
答案:
- 核心步骤:
- 哈希算法:将长链MD5后取前6位(冲突时追加随机盐)。
- 发号器方案:数据库自增ID转62进制(0-9a-zA-Z)。
- 存储设计:Redis缓存短链-长链映射,MySQL持久化。
- 跳转逻辑:HTTP 302重定向,统计访问量。
优化点:
- 使用布隆过滤器预判短链是否存在。
10. 如何实现接口的限流?
答案:
- 算法对比:
- 令牌桶(Guava RateLimiter):允许突发流量,适合秒杀场景。
- 漏桶:恒定速率处理,适合平滑流量。
- 滑动窗口(Sentinel):精准控制QPS。
- 实战代码:
java
// Guava令牌桶 RateLimiter limiter = RateLimiter.create(100); // 100 QPS if (limiter.tryAcquire()) { // 处理请求 } else { throw new RateLimitException(); }
六、陷阱题
11. 为什么foreach循环中删除元素会抛异常?
答案:
- 根本原因:foreach基于Iterator实现,删除元素导致modCount变化,触发
ConcurrentModificationException
。 - 正确写法:
java
Iterator<Integer> it = list.iterator(); while (it.hasNext()) { if (it.next() == 2) { it.remove(); // 安全删除 } }
12. 静态方法调用非静态成员为何编译失败?
答案:
- 原理:非静态成员依赖对象实例,而静态方法属于类,无隐含的
this
引用。 - 错误示例:
java
public class Util { private int count = 0; public static void print() { System.out.println(count); // 编译错误 } }
七、开放性问题
13. 如何优化接口的响应时间?
答案:
- 分层优化:
- 前端:CDN缓存、资源合并压缩。
- 网络:HTTP/2、长连接复用。
- 服务:异步化、缓存、索引优化。
- 数据库:读写分离、分库分表。
- 工具链:
- APM工具(SkyWalking)定位慢调用链。
- Profiler(Async-Profiler)分析CPU热点。