# 《Java核心知识点整理》知识点全解 > AI生成|2026年10月29日|codebuddy|原书:AlexWang《Java核心知识点整理》,— > **我的阅读**:进度 0%|累计 2小时43分钟|开始 —|最近 2023-07-29|读完 2023-07-04 **一句话总结**:把 Java 面试与工程里反复被问、反复被踩的四块硬骨头——JVM、集合、并发、语言基础——压成一条可背诵、可对照排查的提纲,用"机制 + 参数 + 失效场景"替代零散结论。 **适合谁 / 前置知识**:适合已能写 Java、准备中高级面试或需要给线上问题定位的人;也适合把散落的博客笔记整理成体系的人。前置要求:熟悉基本语法、面向对象、常用集合 API,写过多线程代码(哪怕只是 `new Thread`),知道栈/堆这类操作系统常识。**不适合**零基础读者——本书是提纲式整理,几乎不给完整示例代码,跳过语法直接读会得到一堆名词;也不适合想学框架用法的人,全书不涉及 Spring、MyBatis 等应用层内容。 ## 一、全书地图 目录只覆盖第 2–5 章,第 1 章未出现在目录中(**待核对**,推测为语言概述或开篇,不作展开)。 | 章 | 主题 | 主要子块(依目录) | 回答的问题 | |---|---|---|---| | 2 | JVM | 线程、内存区域、运行时内存、GC 算法、四种引用、收集器、IO 模型、类加载机制 | 字节码跑起来之后,内存怎么分、垃圾怎么收、类怎么进内存 | | 3 | Java 集合 | List、Set(TreeSet)、Map(JDK8 实现、并发度、HashTable) | 同一份数据,选哪种容器,代价在哪 | | 4 | Java 多线程并发 | 线程创建与状态、终止、start/run、各类锁、上下文切换、线程池与拒绝策略、阻塞队列、并发工具、volatile、ThreadLocal、原子类 | 多线程为什么出错,怎么用现成工具不出错 | | 5 | Java 基础 | 异常处理、反射 | 语言层两条主线:错误怎么流转、类型信息怎么在运行时拿到 | **逻辑关系**:第 2 章是运行时底座(内存与 GC 决定了第 3、4 章里"对象多大、什么时候死");第 3 章是数据结构的落地(`HashMap` 的红黑树、`ConcurrentHashMap` 的分段/桶锁,直接连着第 4 章的锁);第 4 章是本书重心,篇幅约占目录一半,锁与线程池是高频中的高频;第 5 章的反射是框架的地基,异常是工程规范。**读法**:先 2.2/2.3/2.4 建立内存与 GC 框架,再读 3、4 两章,最后用第 5 章收口。 目录本身有两处编号异常:2.2.1 之后直接跳到 2.2.3;2.8.2 同时出现在"非阻塞 IO 模型"和"JAVA NIO"下(**待核对**)。本文按主题组织,不沿用这些编号。 ## 二、核心知识点 **1. 运行时数据区的五分法** 线程私有:程序计数器、虚拟机栈、本地方法栈;线程共享:堆、方法区(JDK8 起由元空间实现,位于本地内存)。程序计数器记录当前字节码行号,是唯一不会抛 `OutOfMemoryError` 的区域,执行 native 方法时值为空。虚拟机栈以栈帧为单位(局部变量表、操作数栈、动态链接、返回地址),递归过深抛 `StackOverflowError`,栈无法申请到内存则抛 OOM。**为什么重要**:所有"内存溢出"要先分清是哪块区域溢出,堆溢出调堆、元空间溢出查动态生成类、栈溢出查递归。**误区**:把"堆外内存"等同于"不受管",元空间和直接内存同样受 `MaxDirectMemorySize` 等约束。 **2. 对象分配与晋升** 新对象优先在 Eden 分配,Eden 满触发 Minor GC,存活对象复制到一个 Survivor,年龄加 1,在两个 Survivor 间轮转;年龄达阈值(默认 15)晋升老年代。大对象可直接进老年代;当 Survivor 中同年龄对象总和超过一半,大于等于该年龄的对象直接晋升(动态年龄判定);Minor GC 前要做空间分配担保,老年代放不下则退化为 Full GC。**怎么用**:`-Xmn`、`-XX:SurvivorRatio`(默认 8:1:1)、`-XX:MaxTenuringThreshold`、`-XX:PretenureSizeThreshold` 是调这几个动作的旋钮。**误区**:以为 Survivor 越大越好——两个 Survivor 各占新生代 1/10,调大挤压 Eden,Minor GC 反而更频繁。 **3. 四种 GC 算法与分代组合** 标记清除:标记可达对象、清除其余,实现简单但产生碎片、效率随对象数下降,CMS 用它。复制:把存活对象整体搬到另一半空间,无碎片但浪费空间、存活率高时复制成本大,新生代用它。标记整理:标记后向一端移动,无碎片但要多一次移动停顿,老年代用它。分代收集不是独立算法,而是"按存活周期分而治之"的策略组合。**为什么重要**:看到"碎片""浮动垃圾""停顿"这些词,先回推它属于哪种算法。**误区**:把分代收集当成第四种具体算法去背。 **4. 四种引用** 强引用:只要可达就不回收,OOM 也不让。软引用:内存不足时才回收,适合做缓存。弱引用:下一次 GC 必回收,`WeakHashMap`、`ThreadLocal` 的 key 依赖它。虚引用:无法通过它取到对象,只用于对象被回收时收到通知(配合 `ReferenceQueue`),常见于直接内存的清理。**怎么用**:缓存用软引用或 `WeakHashMap`,但两者都不可靠,生产缓存应上 Caffeine/Redis。**误区**:以为弱引用能"手动清缓存"——清理时机由 GC 决定,不可预期。 **5. 收集器的演进与选择** Serial(单线程复制,STW,客户端场景)、ParNew(多线程版 Serial,配合 CMS)、Parallel Scavenge(吞吐量优先,`-XX:MaxGCPauseMillis`/`GCTimeRatio`,自适应调参)、Serial Old、Parallel Old(多线程标记整理)。CMS 分四步:初始标记(STW)→ 并发标记 → 重新标记(STW,修正在并发标记期间的变动)→ 并发清除;它追求低停顿,代价是 CPU 敏感、产生浮动垃圾、需要预留空间,否则 Concurrent Mode Failure 退化成串行 Full GC,且标记清除留下碎片。G1 把堆切成等大 Region,按收益优先回收,用可预测停顿模型(`-XX:MaxGCPauseMillis`)代替固定分代边界。**怎么用**:吞吐优先选 Parallel,低延迟大堆选 G1,JDK9 起 G1 为默认。**误区**:以为 CMS 停顿为零——初始标记与重新标记都要 STW,只是短。 **6. 五种 IO 模型与 NIO 三件套** 阻塞 IO:一连接一线程,读不到就卡住。非阻塞 IO:读不到立刻返回,需要应用自己轮询,空转烧 CPU。多路复用:一个线程用 `select/poll/epoll` 盯着多路事件,就绪再处理,是 Netty、Redis 的基础。信号驱动:内核就绪后发 SIGIO 通知。异步 IO:内核把数据读完再通知应用,全程不阻塞。Java NIO 提供 Buffer(`capacity/position/limit/mark`,`flip` 切换读写模式)、Channel(双向,可读写,`FileChannel`/`SocketChannel`)、Selector(多路复用),**但 Java NIO 本质是同步非阻塞**,真正异步要看 NIO.2 的 `AsynchronousSocketChannel`。**误区**:把"NIO = 异步"当定理,这是最常见的错误等式。 **7. 类加载五阶段与双亲委派** 加载(按全限定名取二进制流,生成 Class 对象)→ 验证 → 准备(静态变量分配内存并置零值,`static final` 常量此时直接赋初值)→ 解析(符号引用转直接引用)→ 初始化(执行 ``,父类先于子类)。符号引用是用字面量描述目标(类的全限定名、字段名、方法描述符),与内存布局无关;直接引用才是指针或偏移量。双亲委派:Bootstrap → Platform(Extension) → Application → 自定义,先交给父加载器,父加载不了再自己加载。**为什么重要**:它保证 `java.lang.String` 不会被自定义类冒充,也避免类重复加载。**误区**:以为双亲委派是强制规范——它是 `loadClass` 的默认实现,SPI(JDBC 驱动)、OSGi、热部署都会打破它。 **8. List / Set / Map 的取舍** `ArrayList` 底层数组,首次添加分配 10,扩容 1.5 倍,随机访问 O(1)、中间插入 O(n)。`LinkedList` 双向链表,增删 O(1)(前提是已定位节点),随机访问 O(n),每节点额外两个指针。`Vector` 方法级 `synchronized`、扩容 2 倍,已基本淘汰。`TreeSet`/`TreeMap` 底层红黑树,按 `Comparable`/`Comparator` 有序,操作 O(log n),天然去重且可排序,代价是每次操作都要比较。**怎么用**:查多用 `ArrayList`,频繁头尾增删用 `ArrayDeque`,需要排序去重用 `TreeSet`,需要并发用 `CopyOnWriteArrayList`(读多写极少)。**误区**:以为 `LinkedList` 插入一定比 `ArrayList` 快——定位到插入点的遍历成本常常更大。 **9. HashMap 与 ConcurrentHashMap** `HashMap`:数组 + 链表,JDK8 起链表长度达 8 且容量 ≥ 64 时转红黑树,退化阈值 6,默认容量 16、负载因子 0.75,扩容时按高低位拆链表(JDK8)而 JDK7 用头插,并发扩容会成环。JDK7 的 `ConcurrentHashMap` 用 Segment 数组 + HashEntry,分段锁,并发度等于 Segment 数(默认 16,可在构造时指定);JDK8 取消 Segment,改成 Node 数组 + CAS 建桶 + `synchronized` 锁桶头,扩容支持多线程协助迁移。**怎么用**:`ConcurrentHashMap` 不允许 null key/value(因为无法区分"没有"与"值为 null");`size()` 在 JDK8 用 baseCount + CounterCell 累加。**误区**:`HashTable` 全表一把锁,任何读写都串行,性能远低于 `ConcurrentHashMap`,新代码不应再用。 **10. 线程状态与 start/run** 状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。Java 的 RUNNABLE 合并了操作系统的"就绪"与"运行",正在等 IO 的线程也显示 RUNNABLE,所以看 jstack 时"RUNNABLE 却在等网络"是正常的。`start()` 由 JVM 启动新线程并回调 `run()`;直接调 `run()` 只是当前线程里的一次普通方法调用,没有并发。同一个 Thread 对象只能 `start()` 一次,否则 `IllegalThreadStateException`。**误区**:调 `run()` 测试通过就以为线程逻辑对——它根本没验证并发。 **11. 终止线程与中断协作** 正常结束、`volatile` 标志位、`interrupt()`、`stop()`(已废弃)。`stop()` 会立刻释放该线程持有的所有锁,导致被保护的数据处于中间状态,且不给清理机会,所以禁用。`interrupt()` 只是设置中断标志,不强制终止:目标线程在阻塞方法(`sleep`/`wait`/`join`)中会抛 `InterruptedException` 并清除标志,在运行中则需自己检查 `isInterrupted()` 并退出。**怎么用**:`while (!Thread.currentThread().isInterrupted())` 配合可中断阻塞调用,是标准写法。**误区**:捕获 `InterruptedException` 后吞掉不处理,或只打日志不恢复中断标志,取消信号就此丢失。 **12. synchronized 的锁升级与 ReentrantLock / AQS** `synchronized` 经历无锁 → 偏向锁 → 轻量级锁(CAS 自旋,自适应自旋)→ 重量级锁(ObjectMonitor,涉及用户态/内核态切换,未抢到锁的线程进入 `_entryList` 阻塞)。偏向锁在较新 JDK 已默认关闭并最终移除(版本细节**待核对**)。`ReentrantLock` 基于 AQS:`volatile int state` 表示资源状态,独占模式下 state 为重入计数,抢不到就进 CLH 变体的 FIFO 队列;`Semaphore` 用共享模式,state 是剩余许可数;`CountDownLatch` 的 state 是倒计数。**怎么用**:需要可中断、可超时、公平、多条件队列时用 `ReentrantLock`,否则 `synchronized` 更省心(自动释放)。**误区**:`lock()` 后忘记在 `finally` 里 `unlock()`,一次异常就永久锁死。 **13. 公平/非公平、Condition 与并发工具** 公平锁严格按队列顺序获取(`hasQueuedPredecessors`),避免饥饿但吞吐低;非公平锁允许新来的线程插队,吞吐高但可能饿死。`Condition` 的 `await/signal` 对应 `Object` 的 `wait/notify`,但一个 Lock 可以创建多个 Condition,把不同等待原因分队列,且支持超时与中断;`wait/notify` 必须持有 monitor,`wait` 释放锁、`notify` 不释放锁,且必须用 `while` 而非 `if` 防虚假唤醒。`CountDownLatch` 一次性、计数到零放行;`CyclicBarrier` 可循环、等齐再一起走,还能指定 barrierAction;`Semaphore` 控制同时访问的线程数。**误区**:把 `CountDownLatch` 当可复用屏障,倒计数归零后无法重置。 **14. volatile、ThreadLocal 与原子类** `volatile` 保证可见性(写刷主存、读从主存)与禁止重排序(内存屏障),**不保证原子性**,`i++` 依然不安全;适合状态标志与双重检查锁定的实例字段。`ThreadLocal` 为每个线程维护一份副本,底层是 `Thread` 持有的 `ThreadLocalMap`,key 为弱引用的 ThreadLocal、value 强引用,key 被回收后 value 会滞留到线程结束,线程池场景必须 `remove()`;与 `synchronized` 的区别是"空间换时间"对"时间换空间"。原子类(`AtomicInteger` 等)靠 CAS + `volatile` 实现无锁更新,CAS 有 ABA 问题,用 `AtomicStampedReference` 加版本号解决;高并发累加用 `LongAdder` 分散热点。**误区**:用 `volatile` 修饰计数器当线程安全,或在线程池里用 ThreadLocal 存请求上下文却不清理。 **15. 线程池与阻塞队列** 七个参数:核心线程数、最大线程数、空闲存活时间、时间单位、工作队列、线程工厂、拒绝策略。提交顺序:核心线程未满则建核心线程 → 满了进队列 → 队列满则建非核心线程 → 到最大线程数还满则走拒绝策略。四种拒绝策略:AbortPolicy(默认,抛 `RejectedExecutionException`)、CallerRunsPolicy(让提交者自己跑,形成反压)、DiscardPolicy(静默丢)、DiscardOldestPolicy(丢队列最老的)。`Executors` 的四个工厂各有陷阱:`newFixedThreadPool`/`newSingleThreadExecutor` 用无界队列会 OOM,`newCachedThreadPool` 最大线程数为 `Integer.MAX_VALUE` 会线程爆炸,`newScheduledThreadPool` 的延迟队列同样无界——这也是阿里规约要求手写 `ThreadPoolExecutor` 的原因。阻塞队列的 `ArrayBlockingQueue` 用一把锁 + 两个 Condition(非空/非满),可设公平;`SynchronousQueue` 不存储元素,`put` 必须等到对应的 `take`,正是 `CachedThreadPool` 的队列;其余还有 `LinkedBlockingQueue`、`PriorityBlockingQueue`、`DelayQueue`。队列方法分四组:抛异常(`add/remove/element`)、返回特殊值(`offer/poll/peek`)、阻塞(`put/take`)、超时(`offer/poll` 带时间)。**误区**:以为线程池会无限排队——队列选错,任务在队列里等到天荒地老也不会扩容。 **16. 上下文切换与反射** 上下文切换是 CPU 保存/恢复线程的寄存器与栈,开销在保存现场、缓存失效、调度本身。触发原因:时间片耗尽、线程阻塞(IO、锁、`wait`、`join`)、更高优先级线程抢占、GC 线程介入。减少手段:降低锁竞争(减小锁粒度、锁分离、无锁 CAS)、控制线程数(CPU 密集型接近核数,IO 密集型可适当放大)、用协程或异步回调替代阻塞等待。反射:`Class` 对象三种获取方式(`类名.class`、`obj.getClass()`、`Class.forName`),编译期只知道父类型、拿不到具体方法,运行时通过 `getDeclaredMethods` 等可枚举全部成员并 `setAccessible(true)` 调用。**怎么用**:Spring 的 IOC、MyBatis 的映射、各类序列化框架都靠它;代价是跳过内联优化、破坏封装,JDK9 模块化后跨模块 `setAccessible` 会被拒。**误区**:在热路径上反复反射,应缓存 `Method` 或改用 `MethodHandle`。 ## 三、关键概念速查表 | 概念 | 一句话解释 | 用在哪儿 | |---|---|---| | 程序计数器 | 线程私有的字节码行号指示器,唯一不 OOM 的区域 | 判断溢出类型 | | 元空间 | JDK8 起替代永久代的方法区实现,位于本地内存 | 排查 `Metaspace OOM` | | 分配担保 | Minor GC 前确认老年代放得下晋升对象 | Full GC 频繁的排查 | | 动态年龄判定 | Survivor 中同龄对象总和过半即晋升 | 短命大对象提前老化 | | 分代收集 | 按存活周期分代并组合不同算法 | 理解所有收集器的前提 | | 浮动垃圾 | 并发标记期间新产生的垃圾,本轮收不掉 | CMS 预留空间 | | Region | G1 的等大堆分区单位 | 可预测停顿回收 | | 符号引用 | 用字面量描述目标,与内存布局无关 | 类加载解析阶段 | | 双亲委派 | 先委派父加载器,加载不了再自己加载 | 类冲突、`ClassNotFound` | | fail-fast | 迭代中结构性修改即抛 `ConcurrentModificationException` | `ArrayList` 遍历删除 | | 树化阈值 | 链表长度 8 且容量 ≥ 64 转红黑树 | `HashMap` 性能拐点 | | 分段锁 | JDK7 `ConcurrentHashMap` 按 Segment 加锁 | 并发度设置 | | AQS | `volatile state` + FIFO 等待队列的同步器框架 | Lock、Semaphore、Latch | | 锁升级 | 偏向 → 轻量级(自旋)→ 重量级 | 判断 sync 开销 | | 虚假唤醒 | 无 notify 也被唤醒,必须 while 重查条件 | `wait` 的标准写法 | | 反压 | 用 `CallerRunsPolicy` 让提交者变慢 | 线程池过载保护 | | CAS / ABA | 比较并交换;值被改回原值导致误判 | 原子类、无锁栈 | ## 四、可复用的方法/框架 **线上问题定位四问** 1. 是内存问题吗?(`jstat` 看 GC 频率与老年代增长,`jmap` 看堆快照,先分清堆/元空间/直接内存) 2. 是 CPU 问题吗?(`top -H` 找线程,`jstack` 对照,区分"真忙"与"自旋/GC 线程") 3. 是锁问题吗?(`jstack` 搜 `BLOCKED` 与 `waiting to lock`,确认锁持有者栈) 4. 是线程模型问题吗?(线程数、队列长度、拒绝策略是否与流量匹配) **容器选型决策链**:是否需要并发 → 读多写多还是读多写少 → 是否需要有序/去重 → 是否需要按范围查询 → 再定 `ArrayList`/`LinkedList`/`TreeSet`/`ConcurrentHashMap`/`CopyOnWriteArrayList`。 **并发改造五步**:先确认共享可变状态 → 能消除共享就消除(ThreadLocal、不可变对象)→ 不能消除则选最小同步范围 → 优先用现成容器与工具类而非手写锁 → 最后才考虑无锁与 CAS,并验证 ABA 与自旋成本。 **背知识点的正确姿势**:每个结论都追一句"如果不这样会怎样"(比如"不分代会怎样""不用双亲委派会怎样"),答不出就说明只记住了名词。 ## 五、我的阅读轨迹 | 项目 | 数据 | |---|---| | 进度 | 0% | | 累计时长 | 2 小时 43 分钟 | | 开始阅读 | —(未记录) | | 最近阅读 | 2023-07-29 | | 读完时间 | 2023-07-04 | 数据有三处需要留意:进度显示 0%,但已有 2 小时 43 分钟累计时长;"读完时间 2023-07-04"早于"最近阅读 2023-07-29";开始阅读未记录。合理解释是曾快速通读一遍、系统未记录章节进度,之后又零散翻看(**待核对**)。按累计时长估算,大致相当于通读一遍提纲的量,远未到能复述的程度。 **复习建议**:书未真正读进去,按"先框架后细节"重来一遍。第一轮只读第 2 章的 2.2–2.4(内存区域 + GC 算法 + 分代),这是后面所有讨论的地基;第二轮攻第 4 章,重点顺序为线程池七参数与拒绝策略 → `synchronized` 锁升级与 AQS → `volatile` 与 `ThreadLocal` → 阻塞队列;第三轮补第 3 章的 `HashMap`/`ConcurrentHashMap` 与第 5 章反射。**标记为高优先级的章节**:2.3、2.4、2.7(收集器)、4.1.9(锁)、4.1.13–4.1.14(线程池与队列)、4.1.16(volatile)。**可略读**:2.9.4 OSGi(现代工程几乎不用,了解即可)、5.1 异常(常识性内容)。 ## 六、行动清单 1. 用 `jstat -gc 1000` 观察一次真实应用的 GC 曲线,标注 Eden/Survivor/老年代的每次跃迁。 2. 写一段把 `-Xmx` 调到极小的程序,分别制造堆溢出、元空间溢出、栈溢出,记录三种报错原文。 3. 手写一个 `ThreadPoolExecutor`,用无界队列与有界队列各跑一次,观察拒绝策略的触发点。 4. 用 `jstack` 抓一次死锁,找到互相等待的两个线程与锁地址。 5. 复现 `HashMap` 在 JDK7 下并发扩容成环(可用旧版本或阅读源码模拟),再对照 JDK8 的高低位拆分。 6. 为 `ThreadLocal` 写一个线程池下的泄漏 demo,验证 `remove()` 前后的 `ThreadLocalMap` 大小。 7. 把本文 16 个知识点各写一句"如果不这样会怎样",答不出的回读原章节。 8. 用 `ReentrantLock` + 两个 `Condition` 实现一个有界阻塞队列,与 `ArrayBlockingQueue` 源码对照。 ## 七、延伸阅读 1. **《深入理解Java虚拟机》(周志明)**——先读,作为本书第 2 章的正统教材。本书给结论,它给推导与调优案例,读完再回看提纲会有"原来如此"的对照感。 2. **《Java并发编程实战》(Brian Goetz 等)**——接着读,补第 4 章的理论缺口(内存模型、happens-before、任务取消与关闭),本书偏向"是什么",它偏向"为什么这样设计"。 3. **《Effective Java》(Joshua Bloch)**——最后读,把前面三块知识落成编码习惯(不可变对象、优先组合、避免过度同步),是从"知道"到"写对"的一步。