Skip to content

Java 基础面试题 ​

HashMap 的底层原理是什么?JDK 8 做了哪些优化? ​

难度:中等 | 标签:集合, HashMap, 数据结构

HashMap 基于「数组 + 链表 + 红黑树」实现,通过哈希函数把 key 映射到数组下标:

  1. 计算 key 的 hash:(h = key.hashCode()) ^ (h >>> 16),让高位参与运算,减少碰撞;
  2. 通过 (n - 1) & hash 定位桶下标,n 是数组长度且必须是 2 的幂;
  3. 发生哈希冲突时,JDK 8 先以链表存储,当链表长度达到 8 且数组长度不小于 64 时,转为红黑树,查询复杂度从 O(n) 降为 O(log n);
  4. 当元素数量超过 容量 × 负载因子(默认 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拒绝策略

任务提交后的执行流程:

  1. 当前线程数小于 corePoolSize,创建核心线程执行;
  2. 核心线程已满,任务进入 workQueue 排队;
  3. 队列已满且线程数小于 maximumPoolSize,创建非核心线程执行;
  4. 队列已满且线程数达到最大值,执行拒绝策略(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

对比项synchronizedReentrantLock
层级JVM 关键字,基于 monitor 实现JDK 类,基于 AQS 实现
释放代码块结束或异常时自动释放必须在 finally 中手动 unlock()
可中断不支持支持 lockInterruptibly()
超时获取不支持支持 tryLock(timeout)
公平锁非公平可选择公平或非公平
条件变量只有一个等待队列可创建多个 Condition

JDK 6 之后 synchronized 引入了偏向锁、轻量级锁、重量级锁的升级机制,性能已经和 ReentrantLock 接近。一般优先使用 synchronized,需要超时、可中断、公平锁或多条件队列时再使用 ReentrantLock。

Spring Bean 的生命周期是怎样的? ​

难度:中等 | 标签:Spring, Bean, IOC

Spring Bean 的生命周期可以概括为:

  1. 实例化:根据 BeanDefinition 调用构造方法创建对象;
  2. 属性填充:依赖注入,处理 @Autowired 等;
  3. Aware 回调:BeanNameAware、BeanFactoryAware、ApplicationContextAware 等;
  4. 初始化前:BeanPostProcessor#postProcessBeforeInitialization;
  5. 初始化:@PostConstruct → InitializingBean#afterPropertiesSet → 自定义 init-method;
  6. 初始化后:BeanPostProcessor#postProcessAfterInitialization,AOP 代理通常在这一步生成;
  7. 使用:Bean 被业务代码调用;
  8. 销毁:容器关闭时执行 @PreDestroy → DisposableBean#destroy → 自定义 destroy-method。

其中 BeanPostProcessor 是 Spring 扩展的核心,AOP、事务代理等都是通过它实现的。