复习定位

JavaSE 八股不是背 API 名字,而是把“语言语义、集合容器、并发模型、JVM 运行机制”串起来。面试回答尽量按:是什么 -> 为什么 -> 怎么用 -> 有什么坑。

基础语义

Java 是值传递还是引用传递?

Java 只有值传递。基本类型传的是值副本;引用类型传的是引用地址的副本。方法里可以通过这个引用修改对象内部状态,但不能让外部变量指向一个新对象。

一句话回答:对象引用本身也是值,传进去的是引用值的拷贝。

equals 和 hashCode 的关系

equals 用来判断对象逻辑相等,hashCode 用来辅助哈希结构定位桶。

规则:

  • 两个对象 equals 为 true,hashCode 必须相同。
  • 两个对象 hashCode 相同,equals 不一定为 true。
  • 重写 equals 通常必须重写 hashCode

面试坑:如果对象作为 HashMap 的 key,参与相等判断的字段最好不要在放入 map 后再变,否则可能找不到。

String、StringBuilder、StringBuffer

String 不可变,适合少量拼接和常量复用。StringBuilder 可变、非线程安全,适合单线程大量拼接。StringBuffer 方法带同步,线程安全但开销更高。

String 不可变的意义:

  • 可以安全放入字符串常量池。
  • 可以缓存 hash 值。
  • 适合作为 map key。
  • 多线程共享风险小。

集合

ArrayList 和 LinkedList

ArrayList 底层是动态数组,随机访问快,尾部追加通常快,扩容需要数组复制。LinkedList 底层是双向链表,按下标访问慢,已定位节点后的插入删除快,但对象节点额外占内存。

实际开发里,大多数场景优先 ArrayList

HashMap 的核心过程

HashMap 通过 key 的 hash 定位桶,桶里可能是链表,也可能在冲突较多时转成红黑树。

JDK 8 之后的关键点:

  • 数组 + 链表 + 红黑树。
  • 默认负载因子 0.75,容量到阈值后扩容。
  • 链表长度达到阈值且数组容量足够时树化。
  • 扩容时容量翻倍,元素会按旧容量的高位 bit 分流到原位置或新位置。

回答 HashMap 时别只背“数组链表红黑树”,要能说清楚:hash 定位、冲突处理、扩容、树化、线程不安全。

ConcurrentHashMap 为什么比 Hashtable 更常用?

Hashtable 基本靠整表同步,粒度粗。ConcurrentHashMap 在 JDK 8 里主要通过 CAS + synchronized 控制桶级别并发,读操作多数情况下不加锁,吞吐更好。

注意:ConcurrentHashMap 不允许 key 或 value 为 null,因为并发场景下 null 会让“没有这个 key”和“value 就是 null”变得难以区分。

并发

synchronized 和 volatile

synchronized 解决互斥和可见性,进入和退出同步块会建立 happens-before 关系。适合保护临界区。

volatile 解决可见性和禁止指令重排,不保证复合操作原子性。i++ 即使用 volatile 修饰,也不是原子操作。

一句话:volatile 管“看得见”和“别乱排”,synchronized 还能管“同一时间只有一个线程改”。

线程池核心参数

ThreadPoolExecutor 重点看:

  • corePoolSize:核心线程数。
  • maximumPoolSize:最大线程数。
  • keepAliveTime:非核心线程空闲存活时间。
  • workQueue:任务队列。
  • threadFactory:线程工厂。
  • rejectedExecutionHandler:拒绝策略。

执行顺序:核心线程未满先创建核心线程;核心线程满后进队列;队列满后创建非核心线程;线程到最大且队列也满则拒绝。

常见拒绝策略:

  • AbortPolicy:抛异常。
  • CallerRunsPolicy:调用者线程执行。
  • DiscardPolicy:直接丢弃。
  • DiscardOldestPolicy:丢弃队列最老任务。

ThreadLocal 的风险

ThreadLocal 给每个线程保存一份变量副本,常用于用户上下文、链路追踪、事务上下文。

在线程池里必须及时 remove,否则线程复用会导致旧数据串到新请求里,也可能造成内存泄漏。

JVM

JVM 内存区域

常见回答可以拆成:

  • 堆:对象主要分配区域,是 GC 重点管理对象。
  • 虚拟机栈:方法调用栈帧,包含局部变量表、操作数栈等。
  • 本地方法栈:服务 native 方法。
  • 程序计数器:记录当前线程执行位置。
  • 方法区 / 元空间:类元数据、常量、静态字段等相关信息。

类加载过程

加载 -> 验证 -> 准备 -> 解析 -> 初始化。

双亲委派模型的价值:优先让父加载器加载核心类,避免核心类被随意替换,也避免类重复加载。

GC 回收对象怎么判断?

主流 JVM 通过可达性分析判断对象是否还活着。从 GC Roots 出发,能走到的对象是可达的,走不到的对象可被回收。

常见 GC Roots 包括:栈中引用的对象、静态字段引用对象、常量引用对象、JNI 引用对象等。

面试收束

JavaSE 的回答别散。集合题落到数据结构和扩容;并发题落到可见性、原子性、有序性;JVM 题落到内存、类加载、对象存活和 GC。

参考资料

  • JavaGuide:https://javaguide.cn/
  • JavaGuide GitHub:https://github.com/Snailclimb/JavaGuide