Java 基础面试题
HashMap 的底层原理是什么?JDK 8 做了哪些优化?
难度:中等 | 标签:集合, HashMap, 数据结构
HashMap 基于「数组 + 链表 + 红黑树」实现,通过哈希函数把 key 映射到数组下标:
- 计算 key 的
hash:(h = key.hashCode()) ^ (h >>> 16),让高位参与运算,减少碰撞; - 通过
(n - 1) & hash定位桶下标,n 是数组长度且必须是 2 的幂; - 发生哈希冲突时,JDK 8 先以链表存储,当链表长度达到 8 且数组长度不小于 64 时,转为红黑树,查询复杂度从 O(n) 降为 O(log n);
- 当元素数量超过
容量 × 负载因子(默认 0.75)时触发扩容,容量翻倍并重新分布元素。
JDK 8 的主要优化:
- 引入红黑树,缓解严重哈希冲突下的性能退化;
- 扩容时不再重新计算全部 hash,而是利用容量为 2 的幂的特性,把节点拆分为「原位置」和「原位置 + 旧容量」两条链,提高扩容效率;
- 头插法改为尾插法,避免并发扩容时形成环形链表。
说一下 JVM 的运行时数据区
难度:中等 | 标签:JVM, 内存模型
JVM 运行时数据区分为线程私有和线程共享两部分:
线程私有:
- 程序计数器:记录当前线程执行的字节码行号,是唯一不会 OOM 的区域;
- 虚拟机栈:每个方法调用对应一个栈帧,保存局部变量表、操作数栈、动态链接、方法返回地址等;
- 本地方法栈:为 Native 方法服务。
线程共享:
- 堆:对象实例和数组的主要分配区域,也是 GC 的主要区域,通常分为新生代(Eden + Survivor)和老年代;
- 方法区:存储类元信息、常量、静态变量等,JDK 8 以后由元空间(Metaspace)实现,使用本地内存。
此外还有直接内存(Direct Memory),不受堆大小限制,NIO 的 DirectByteBuffer 会使用它。
线程池的核心参数和执行流程
难度:中等 | 标签:并发, 线程池, ThreadPoolExecutor
ThreadPoolExecutor 的核心参数:
| 参数 | 含义 |
|---|---|
| corePoolSize | 核心线程数 |
| maximumPoolSize | 最大线程数 |
| keepAliveTime | 非核心线程空闲存活时间 |
| workQueue | 任务阻塞队列 |
| threadFactory | 线程工厂 |
| handler | 拒绝策略 |
任务提交后的执行流程:
- 当前线程数小于
corePoolSize,创建核心线程执行; - 核心线程已满,任务进入
workQueue排队; - 队列已满且线程数小于
maximumPoolSize,创建非核心线程执行; - 队列已满且线程数达到最大值,执行拒绝策略(AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy)。
java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, 8, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200),
new ThreadPoolExecutor.AbortPolicy()
);生产环境不建议直接使用 Executors 的快捷方法,因为 newFixedThreadPool 和 newSingleThreadExecutor 使用无界队列,可能堆积大量任务导致 OOM。
synchronized 和 ReentrantLock 有什么区别?
难度:中等 | 标签:并发, 锁, synchronized
| 对比项 | synchronized | ReentrantLock |
|---|---|---|
| 层级 | JVM 关键字,基于 monitor 实现 | JDK 类,基于 AQS 实现 |
| 释放 | 代码块结束或异常时自动释放 | 必须在 finally 中手动 unlock() |
| 可中断 | 不支持 | 支持 lockInterruptibly() |
| 超时获取 | 不支持 | 支持 tryLock(timeout) |
| 公平锁 | 非公平 | 可选择公平或非公平 |
| 条件变量 | 只有一个等待队列 | 可创建多个 Condition |
JDK 6 之后 synchronized 引入了偏向锁、轻量级锁、重量级锁的升级机制,性能已经和 ReentrantLock 接近。一般优先使用 synchronized,需要超时、可中断、公平锁或多条件队列时再使用 ReentrantLock。
Spring Bean 的生命周期是怎样的?
难度:中等 | 标签:Spring, Bean, IOC
Spring Bean 的生命周期可以概括为:
- 实例化:根据 BeanDefinition 调用构造方法创建对象;
- 属性填充:依赖注入,处理
@Autowired等; - Aware 回调:
BeanNameAware、BeanFactoryAware、ApplicationContextAware等; - 初始化前:
BeanPostProcessor#postProcessBeforeInitialization; - 初始化:
@PostConstruct→InitializingBean#afterPropertiesSet→ 自定义init-method; - 初始化后:
BeanPostProcessor#postProcessAfterInitialization,AOP 代理通常在这一步生成; - 使用:Bean 被业务代码调用;
- 销毁:容器关闭时执行
@PreDestroy→DisposableBean#destroy→ 自定义destroy-method。
其中 BeanPostProcessor 是 Spring 扩展的核心,AOP、事务代理等都是通过它实现的。