复习定位
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