Java基础与集合
1. HashMap的底层数据结构是什么?(JDK 1.7 vs 1.8)
考察点:数据结构演进、哈希冲突解决、扩容死循环问题。
参考答案:
HashMap在JDK 1.7和1.8中有着本质的区别,主要体现在底层结构和扩容机制上:
-
底层数据结构
- JDK 1.7 :采用数组 + 链表 。当发生哈希冲突时,使用链表存储,且采用头插法插入新节点。
- JDK 1.8 :采用数组 + 链表 + 红黑树 。
- 优化:当链表长度大于8且数组长度大于64时,链表会转换为红黑树。
- 目的:将查询时间复杂度从 O(n)O(n) 降低到 O(logn)O(logn) ,解决了哈希冲突严重时查询效率低的问题。
- 插入方式 :改为尾插法,保持了元素的顺序。
-
扩容机制(核心区别)
- JDK 1.7(头插法导致的死循环):在多线程环境下扩容时,由于头插法会反转链表顺序,可能导致链表形成环形结构(死循环),导致CPU占用率100%。
- JDK 1.8(尾插法解决死循环):扩容时保持链表顺序,虽然HashMap在1.8中依然是线程不安全的(存在数据覆盖问题),但彻底解决了死循环的问题。
- **扩容机制:**默认初始容量为16,负载因子为0.75。当元素个数超过容量 * 负载因子时,触发扩容,容量变为原来的2倍,并进行重新哈希(Rehash)。
-
哈希计算
- JDK 1.8:优化了哈希值的计算方式,减少了哈希碰撞的概率,且在扩容时不需要重新计算hash值,而是通过高位运算判断元素位置,效率更高。
2. ConcurrentHashMap如何保证线程安全?(JDK 1.7 vs 1.8)
考察点:锁粒度进化、分段锁 vs CAS+Synchronized。
参考答案:
ConcurrentHashMap(CHM)在JDK 1.7和1.8中采用了完全不同的并发控制策略,核心在于锁粒度的不断细化:
-
JDK 1.7:分段锁(Segment Lock)
- 结构 :采用Segment数组 + HashEntry数组。Segment继承自ReentrantLock,相当于一个小的HashMap。
- 原理:将数据分成一段一段存储,给每一段数据配一把锁(默认16个Segment)。
- 并发度:默认支持16个线程同时并发写(取决于Segment数量)。
- 缺点:锁粒度较粗(锁住整个Segment),且Segment数量初始化后不可变,内存开销较大。
-
JDK 1.8:CAS + synchronized(细粒度锁)
- 结构 :抛弃了Segment,结构与HashMap 1.8一致,即Node数组 + 链表 + 红黑树。
- 原理 :
- 空桶插入 :使用CAS(Compare-And-Swap)操作尝试无锁插入,性能极高。
- 冲突插入 :当发生哈希冲突时,使用synchronized 锁住当前桶(Bucket)的头节点。
- 并发度:理论上取决于数组长度(默认16,动态扩容),只要哈希不冲突,不同线程可以操作不同的桶,互不干扰。
- 优势 :锁粒度从"段"细化到了"节点/桶",并发性能大幅提升。同时,读操作(get)完全无锁,依靠
volatile保证可见性。
3. 为什么重写equals()必须重写hashCode()?
考察点:Object类规范、哈希集合原理。
参考答案 :
这是为了保证对象在哈希集合(如HashMap、HashSet)中的正确性。
- 规范 :Java规定,如果两个对象通过
equals()比较返回true,那么它们的hashCode()必须相同。 - 后果 :如果只重写
equals而不重写hashCode,两个逻辑上相等的对象可能会计算出不同的哈希值。在存入HashMap时,它们会被放入不同的桶中,导致无法正确获取或去重,违背了集合的设计原则。
并发编程
4. 线程池的核心参数有哪些?工作流程是怎样的?
考察点:ThreadPoolExecutor、生产环境配置。
参考答案 :
核心参数有7个,最关键的5个是:
- corePoolSize:核心线程数(常驻线程)。
- maximumPoolSize:最大线程数。
- workQueue:任务阻塞队列。
- keepAliveTime:非核心线程的空闲存活时间。
- RejectedExecutionHandler:拒绝策略。
工作流程 :
当提交一个新任务时:
- 如果当前线程数 <
corePoolSize,创建新核心线程执行。 - 如果核心线程已满,任务加入
workQueue。 - 如果队列也满了,且线程数 <
maximumPoolSize,创建非核心线程执行。 - 如果队列满了且线程数已达最大值,执行
RejectedExecutionHandler(默认AbortPolicy抛异常)。
5. synchronized和ReentrantLock的区别?
考察点:锁的实现、功能特性。
参考答案:
- 实现层面 :
synchronized是JVM层面的关键字(内置锁),自动加锁释放;ReentrantLock是JDK API层面的类(java.util.concurrent.locks),需要手动lock()和unlock()。 - 功能特性 :
synchronized:操作简单,JDK 1.6后进行了大量优化(偏向锁、轻量级锁),不可中断,非公平锁。ReentrantLock:功能更强大,支持可中断 、尝试获取锁 (tryLock)、公平锁 以及多条件变量(Condition)。
- 选择 :代码简洁性优先选
synchronized,需要高级功能(如公平锁、中断)选ReentrantLock。
6. ThreadLocal的底层原理及内存泄漏问题?
考察点:线程隔离、弱引用。
参考答案:
- 原理 :每个Thread内部维护了一个
ThreadLocalMap。ThreadLocal作为Key(弱引用),存储的数据作为Value(强引用)。获取数据时,从当前线程的Map中通过ThreadLocal实例取出Value。 - 内存泄漏 :
- 原因 :
ThreadLocalMap中的Key是弱引用,容易被GC回收;但Value是强引用。如果线程复用(如在线程池中),且没有手动移除数据,Key被回收后,Value将无法访问也无法回收,导致内存泄漏。 - 解决 :使用完ThreadLocal后,务必调用
remove()方法清理数据。
- 原因 :
JVM与Java新特性
7. JDK 21的虚拟线程(Virtual Threads)是什么?
考察点:2026年面试热点、高并发新方案。
参考答案 :
虚拟线程是JDK 21引入的重磅特性(Project Loom),是一种轻量级线程。
- 核心:它是用户态线程,由JVM调度,而不是直接映射到操作系统内核线程(OS Thread)。
- 优势:创建成本极低(内存占用极小),可以轻松创建百万级并发线程。它解决了传统线程"一请求一线程"模型在高并发IO场景下的资源瓶颈。
- 应用:彻底取代了WebFlux等响应式编程的复杂写法,可以用同步代码写出异步的高性能服务。
8. JVM的垃圾回收算法有哪些?G1收集器的特点?
考察点:内存管理、调优基础。
参考答案:
- 常见算法:标记-清除(碎片化)、复制算法(新生代常用)、标记-整理(老年代常用)。
- G1收集器 :
- 特点:面向服务端,将堆内存划分为多个独立的Region。
- 优势:可预测的停顿时间模型。它能根据用户设定的停顿时间目标,动态调整回收的Region数量。
- 适用:适合大内存(6GB+)、多核CPU且对停顿时间有要求的场景。
Spring框架
9. Spring Boot的自动配置原理是什么?
考察点:框架源码、约定优于配置。
参考答案 :
核心在于@EnableAutoConfiguration注解。
- 加载 :通过
@Import导入AutoConfigurationImportSelector。 - 读取 :扫描classpath下的
META-INF/spring.factories文件(或新版META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)。 - 筛选 :读取所有候选配置类,并利用
@Conditional系列注解(如@ConditionalOnClass、@ConditionalOnMissingBean)进行按需加载。 - 生效:只有当类路径下存在特定类(如RedisTemplate)且容器中不存在相关Bean时,自动配置才会生效。
10. Spring事务失效的常见场景有哪些?
考察点:AOP代理机制、实战避坑。
参考答案:
- 方法非public:Spring事务只支持public方法。
- 自调用 :同一个类中,非事务方法调用事务方法(
this.method()),绕过了代理对象,导致事务失效。 - 异常被捕获 :在方法内部
try-catch了异常,没有抛出,事务管理器无法感知异常,从而无法回滚。 - 数据库引擎:数据库表引擎不支持事务(如MySQL的MyISAM)。
- 异常类型不匹配 :默认只回滚
RuntimeException和Error,如果抛出Checked Exception(如IOException)且未配置rollbackFor,事务不会回滚。
这10道题覆盖了Java后端开发最核心的知识体系,建议你结合自己的项目经验进行记忆。