Java 进阶核心知识点总结 🚀
本文将深入探讨 Java 进阶知识,包括 JVM 内存模型、垃圾回收机制、并发编程、集合源码、设计模式等核心内容。如果你已经掌握了 Java 基础,那么进阶之路从这里开始。
📖 目录
JVM 内存模型
垃圾回收机制
类加载机制
并发编程
集合源码解析
设计模式
常用工具类
JVM 内存模型 为什么需要了解 JVM? 很多初学者可能会问:我写 Java 代码又不需要直接操作内存,JVM 自动管理不就好了吗?
这个想法没错,但如果你想写出高性能 、资源利用率高 的代码,就必须了解 JVM。比如:
什么时候对象会被回收?
为什么代码没问题但内存一直涨?
如何调优 JVM 参数?
如何排查 OOM(OutOfMemoryError)问题?
这些问题都需要对 JVM 有深入了解。
JVM 内存划分
flowchart TD
subgraph JVM 运行时数据区
A[JVM 内存模型] --> B[线程共享区]
A --> C[线程私有区]
B --> D[堆 Heap]
B --> E[方法区 Method Area]
C --> F[虚拟机栈 VM Stack]
C --> G[本地方法栈 Native Stack]
C --> H[程序计数器 PC]
end
style A fill:#fff3e0
style B fill:#e3f2fd
style C fill:#e8f5e9
各区域详解 1. 程序计数器(PC Register) 作用 :当前线程所执行的字节码的行号指示器。
特点 :
线程私有,每个线程都有自己的程序计数器
唯一一个不会发生 OutOfMemoryError 的区域
执行 Java 方法时记录字节码指令地址,执行 Native 方法时为空
1 2 3 4 5 6 7 8 9 public class PCRDemo { public static void main (String[] args) { int a = 1 ; int b = 2 ; int c = a + b; } }
2. 虚拟机栈(VM Stack) 作用 :存储方法调用时的局部变量、操作数栈、动态链接等信息。
特点 :
线程私有,生命周期与线程相同
每个方法调用都会创建一个栈帧(Stack Frame)
方法执行完毕,栈帧出栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class StackDemo { public static void main (String[] args) { methodA(); } public static void methodA () { int a = 10 ; methodB(a); } public static void methodB (int num) { int result = num * 2 ; System.out.println(result); } }
flowchart TD
subgraph 虚拟机栈
A[main 栈帧] --> B[methodA 栈帧]
B --> C[methodB 栈帧]
style A fill:#e3f2fd
style B fill:#e8f5e9
style C fill:#fce4ec
end
常见错误 :
StackOverflowError:栈溢出,通常是递归调用没有正确终止
OutOfMemoryError:内存溢出,栈内存不足
1 2 3 4 5 6 7 8 9 10 public class StackOverflowDemo { public static void recursive () { recursive(); } public static void main (String[] args) { recursive(); } }
3. 本地方法栈(Native Stack) 作用 :为 JVM 调用本地方法(Native Method)服务。
与虚拟机栈的区别 :
虚拟机栈为 Java 方法服务
本地方法栈为 Native 方法服务
HotSpot 虚拟机将两者合二为一
4. 堆(Heap) 作用 :几乎所有对象实例和数组都在堆上分配内存。
特点 :
线程共享,是 JVM 管理内存中最大的一块
垃圾收集器(GC)主要管理的就是堆内存
分为年轻代、老年代、永久代(Java 8+ 变为元空间)
flowchart TD
subgraph 堆内存
A[堆 Heap] --> B[年轻代 Young Generation]
A --> C[老年代 Old Generation]
B --> D[Eden 区]
B --> E[S0 区 Survivor From]
B --> F[S1 区 Survivor To]
C --> G[Tenured 区]
end
style A fill:#fff3e0
style B fill:#e3f2fd
style C fill:#e8f5e9
1 2 3 4 5 6 7 8 9 10 11 12 public class HeapDemo { public static void main (String[] args) { List<byte []> list = new ArrayList <>(); while (true ) { list.add(new byte [1024 * 1024 ]); System.out.println("已分配:" + list.size() + " MB" ); } } }
5. 方法区(Method Area) 作用 :存储类信息(类的字节码、版本号)、常量、静态变量、即时编译器编译后的代码等。
特点 :
线程共享
在 HotSpot JVM 中,Java 8 之前用永久代实现,Java 8 之后用元空间(Metaspace)实现
元空间使用本地内存,不受 JVM 堆大小限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MethodAreaDemo { private static int staticVar = 100 ; public static final String CONSTANT = "Hello" ; static { System.out.println("类加载时执行" ); } public void method () { } }
内存溢出示例 1 2 3 4 5 6 7 8 9 10 11 12 public class OOMDemo { public static void main (String[] args) { List<String> list = new ArrayList <>(); int i = 0 ; while (true ) { list.add(String.valueOf(i++).intern()); } } }
垃圾回收机制 什么是垃圾回收? 垃圾回收(Garbage Collection,简称 GC)是 JVM 自动管理内存的机制,自动回收不再使用的对象 ,防止内存泄漏。
为什么需要 GC?
在 C/C++ 中,内存需要程序员手动分配和释放,容易出现:
内存泄漏 :分配后忘记释放,内存越来越少
野指针 :释放后继续使用,导致程序崩溃
双重释放 :同一块内存释放两次
Java 通过 GC 解决了这些问题,程序员只需要关心对象的创建,不用关心释放。
判断对象是否可回收
flowchart TD
A[判断对象是否可回收] --> B[引用计数法]
A --> C[可达性分析]
B --> B1[每个对象一个引用计数器]
B1 --> B2[有引用+1,删除-1]
B2 --> B3[计数器为0可回收]
B3 --> B4[无法处理循环引用]
C --> C1[GC Roots]
C1 --> C2[从 GC Roots 向下搜索]
C2 --> C3[走过的路径叫引用链]
C3 --> C4[不在引用链上=可回收]
style A fill:#fff3e0
1. 引用计数法 给对象添加一个引用计数器,每当有一个地方引用它时,计数器加 1;引用失效时,计数器减 1。任何时刻计数器为 0 的对象就是不可再使用的。
优点 :实现简单,判定效率高。
缺点 :无法解决循环引用问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class CircleReference { public CircleReference ref; public static void main (String[] args) { CircleReference a = new CircleReference (); CircleReference b = new CircleReference (); a.ref = b; b.ref = a; a = null ; b = null ; } }
2. 可达性分析(JVM 使用) 通过一系列称为 “GC Roots” 的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,说明这个对象是不可达的,可以被回收。
GC Roots 包括 :
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中 JNI(Native 方法)引用的对象
Java 虚拟机内部的引用(Class 对象、异常对象等)
所有被同步锁(synchronized)持有的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class GC RootsDemo { private static GCRootsDemo instance; private byte [] data; public static void main (String[] args) { GCRootsDemo demo = new GCRootsDemo (); demo.data = new byte [10 * 1024 * 1024 ]; instance = demo; demo = null ; } }
垃圾收集算法 1. 标记-清除算法(Mark-Sweep)
flowchart LR
subgraph 标记-清除算法
A[初始状态] --> B[标记阶段]
B --> C[清除阶段]
C --> D[回收后]
end
style A fill:#e3f2fd
style D fill:#e8f5e9
步骤 :
标记 :遍历所有对象,标记出所有需要回收的对象
清除 :统一回收所有被标记的对象
缺点 :
2. 复制算法(Copying) 将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块内存用完了,就将还存活的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。
flowchart LR
A[内存区域] --> B[From 区 使用中]
A --> C[To 区 空闲]
B -->|存活对象复制| C
B -->|清空| D[From 区清空]
C -->|交换角色| E[To 变 From]
D -->|交换角色| F[From 变 To]
优点 :实现简单,运行高效,不会产生内存碎片。
缺点 :可用内存缩小为原来的一半,浪费空间。
应用 :年轻代使用此算法,因为年轻代对象存活率低。
3. 标记-整理算法(Mark-Compact) 针对老年代的特点,在标记-清除算法的基础上增加了”整理”步骤。标记过程与标记-清除算法一样,但后续不是直接清理可回收对象,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
优点 :不会产生内存碎片。
缺点 :整理过程需要移动对象,效率较低。
4. 分代收集算法(Generational Collection) 当前商业 JVM 普遍采用这种算法。根据对象存活周期的不同将内存划分为几块,一般把 Java 堆分为年轻代和老年代,然后根据各年代的特点采用最适当的收集算法。
flowchart TD
A[分代收集算法] --> B[年轻代]
A --> C[老年代]
B --> B1[ Eden 区]
B --> B2[Survivor 区]
C --> C1[标记-整理算法]
B1 --> B3[复制算法]
B2 --> B3
style A fill:#fff3e0
style B fill:#e3f2fd
style C fill:#e8f5e9
垃圾收集器
flowchart LR
subgraph 垃圾收集器
A[年轻代收集器] --> B1[Serial]
A --> B2[ParNew]
A --> B3[Parallel Scavenge]
A --> B4[G1]
A --> B5[ZGC]
A --> B6[Shenandoah]
C[老年代收集器] --> C1[Serial Old]
C --> C2[Parallel Old]
C --> C3[CMS]
C --> C4[G1]
C --> C5[ZGC]
C --> C6[Shenandoah]
end
Serial 收集器 最基本、历史最悠久的收集器。是一个单线程收集器,在进行垃圾收集时,必须暂停所有其他工作线程(Stop The World)。
特点 :简单高效,Client 模式下默认的年轻代收集器。
ParNew 收集器 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为与 Serial 完全一样。
特点 :Server 模式下首选的年轻代收集器,可以与 CMS 配合使用。
Parallel Scavenge 收集器 关注点是吞吐量(Throughput),即吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。
特点 :自适应调节策略,GC 自适应调节参数。
1 2 3 4 5 6 -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:GCTimeRatio=99
CMS 收集器 Concurrent Mark Sweep,以获取最短回收停顿时间为目标的收集器。
收集过程 :
初始标记(Initial Mark):标记 GC Roots 能直接关联到的对象
并发标记(Concurrent Mark):进行 GC Roots Tracing
重新标记(Remark):修正并发标记期间因用户程序继续运作而导致标记产生变动
并发清除(Concurrent Sweep):清理垃圾
sequenceDiagram
participant STW as Stop The World
participant GC as CMS GC
GC->>STW: 1. 初始标记(STW)
Note over STW: 标记 GC Roots 直接关联对象
STW-->>GC: 完成
GC->>GC: 2. 并发标记(并发)
Note over GC: 遍历引用链,标记所有存活对象
GC->>STW: 3. 重新标记(STW)
Note over STW: 修正并发标记期间变动的对象
STW-->>GC: 完成
GC->>GC: 4. 并发清除(并发)
Note over GC: 清除已死亡的对象
缺点 :
对 CPU 资源敏感,会占用 CPU 资源
无法处理浮动垃圾(并发清理阶段产生的垃圾)
产生内存碎片
1 2 3 4 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=68
G1 收集器 Garbage First,将整个堆划分为多个大小相等的 Region,跟踪各个 Region 里面的垃圾堆积价值(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
特点 :
并行与并发
分代收集
空间整合(不产生内存碎片)
可预测的停顿
1 2 3 4 5 6 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=mb
类加载机制 类的生命周期
flowchart TD
A[类的生命周期] --> B[加载 Loading]
B --> C[验证 Verification]
C --> D[准备 Preparation]
D --> E[解析 Resolution]
E --> F[初始化 Initialization]
F --> G[使用 Using]
G --> H[卸载 Unloading]
style A fill:#fff3e0
类加载的过程 1. 加载(Loading) “加载”是”类加载”过程的一个阶段,两者不可混淆。
此阶段完成的事情 :
通过类的全限定名获取定义此类的二进制字节流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class LoadingDemo { public static void main (String[] args) { Class<?> clazz1 = Class.forName("java.lang.String" ); Class<?> clazz2 = String.class; String str = "hello" ; Class<?> clazz3 = str.getClass(); System.out.println(clazz1 == clazz2); System.out.println(clazz2 == clazz3); } }
2. 验证(Verification) 确保 Class 文件的字节流中包含的信息符合当前 JVM 要求,不会危害 JVM 安全。
验证阶段包括 :
文件格式验证:验证字节流是否符合 Class 文件格式规范
元数据验证:对字节码描述的信息进行语义分析
字节码验证:通过数据流和控制流分析,确保程序语义的合法性
符号引用验证:确保解析动作能正常执行
3. 准备(Preparation) 正式为类变量分配内存并设置类变量初始值的阶段 ,这些变量所使用的内存都将在方法区中进行分配。
1 2 3 4 5 6 7 8 9 10 public class PreparationDemo { private static int staticVar = 100 ; private static final int CONSTANT = 200 ; }
注意 :
这时候进行内存分配的仅包括类变量(static 修饰的变量),不包括实例变量
实例变量会在对象实例化时随对象一起分配在堆中
初始值通常是零值,而非代码中赋予的值
类型
零值
int
0
long
0L
short
(short) 0
byte
(byte) 0
char
‘\u0000’
boolean
false
reference
null
float
0.0f
double
0.0d
4. 解析(Resolution) 将常量池内的符号引用替换为直接引用的过程 。
符号引用 :以一组符号来描述所引用的目标,符号可以是任何形式的字面量
直接引用 :直接指向目标的指针、相对偏移量或能间接定位到目标的句柄
1 2 3 4 5 6 7 8 9 10 11 12 public class ResolutionDemo { public static void main (String[] args) throws Exception { Class clazz = Class.forName("java.lang.String" ); java.lang.reflect.Field field = clazz.getDeclaredField("value" ); } }
5. 初始化(Initialization) 类加载过程的最后一步 ,真正执行类中定义的 Java 代码。
触发初始化的场景(主动引用) :
遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时
使用 java.lang.reflect 包的方法对类进行反射调用时
当初始化一个类时,发现其父类还没初始化,需要先触发父类初始化
JVM 启动时,会先初始化包含 main() 方法的那个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class InitializationDemo { static { System.out.println("InitializationDemo 被初始化!" ); } public static void main (String[] args) { System.out.println("main 方法执行" ); } } class A { static { System.out.println("A 被初始化!" ); } }
类加载器
flowchart TD
A[类加载器] --> B[Bootstrap ClassLoader 启动类加载器]
A --> C[Extension ClassLoader 扩展类加载器]
A --> D[Application ClassLoader 应用类加载器]
B --> C
C --> D
B -.->|最顶层| E[C++ 实现]
C -.->|Java 实现| F[ExtClassLoader]
D -.->|Java 实现| G[AppClassLoader]
style A fill:#fff3e0
三种默认类加载器
Bootstrap ClassLoader(启动类加载器)
最顶层的类加载器
由 C++ 实现,负责加载 JAVA_HOME/lib 目录中的类库
无法被 Java 程序直接引用
Extension ClassLoader(扩展类加载器)
由 Java 实现(ExtClassLoader)
负责加载 JAVA_HOME/lib/ext 目录中的类库
开发者可以把自己写的类打包成 jar 放到此目录
Application ClassLoader(应用类加载器)
由 Java 实现(AppClassLoader)
负责加载 classpath 上指定的类库
一般情况下这是程序中默认的类加载器
双亲委派模型
flowchart TD
A[Application ClassLoader] --> B[Extension ClassLoader]
B --> C[Bootstrap ClassLoader]
C -->|找不到类| D[向下查找]
D --> E[Extension ClassLoader]
E -->|找不到类| F[向下查找]
F --> G[Application ClassLoader]
G -->|找不到类| H[抛出 ClassNotFoundException]
style A fill:#e3f2fd
style C fill:#fff3e0
工作过程 :如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
为什么需要双亲委派模型?
安全性 :防止核心类被篡改。比如用户自己写一个 java.lang.String 类,由于双亲委派模型,这个类永远不会被加载,因为会优先使用父加载器加载的 java.lang.String。
避免重复加载 :父加载器已加载的类,子加载器不需要再次加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader (String classPath) { this .classPath = classPath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte [] classData = loadClassData(name); if (classData == null ) { throw new ClassNotFoundException (name); } return defineClass(name, classData, 0 , classData.length); } private byte [] loadClassData(String className) { String fileName = classPath + File.separatorChar + className.replace('.' , File.separatorChar) + ".class" ; try (InputStream is = new FileInputStream (fileName); ByteArrayOutputStream baos = new ByteArrayOutputStream ()) { int data; while ((data = is.read()) != -1 ) { baos.write(data); } return baos.toByteArray(); } catch (IOException e) { return null ; } } }
并发编程 为什么要并发?
flowchart LR
A[单线程] --> B[CPU 空闲 等待 I/O]
A -->|I/O 操作| C[阻塞]
D[多线程] --> E[线程1 等待 I/O]
D --> F[线程2 处理业务]
style B fill:#ffcdd2
style E fill:#fff9c4
style F fill:#c8e6c9
单线程的问题 :
多核 CPU 环境下,只用一个核太浪费
I/O 等待时 CPU 空闲,效率低
多线程的优势 :
提高 CPU 利用率
提高程序吞吐量
便于异步处理
提升用户体验
并发编程三大概念 1. 原子性(Atomicity) 一个操作或多个操作要么全部执行,要么全部不执行。
示例 :i++ 不是原子操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class AtomicityDemo implements Runnable { private int count = 0 ; @Override public void run () { for (int i = 0 ; i < 10000 ; i++) { count++; } } public static void main (String[] args) throws InterruptedException { AtomicityDemo demo = new AtomicityDemo (); Thread t1 = new Thread (demo); Thread t2 = new Thread (demo); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("最终结果:" + demo.count); } }
count++ 实际上分为三步:
读取 count 的值
将 count 加 1
写入新的 count 值
多线程下可能导致数据不一致。
解决方案 :使用 AtomicInteger
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.concurrent.atomic.AtomicInteger;public class AtomicDemo implements Runnable { private AtomicInteger count = new AtomicInteger (0 ); @Override public void run () { for (int i = 0 ; i < 10000 ; i++) { count.incrementAndGet(); } } public int getCount () { return count.get(); } }
2. 可见性(Visibility) 一个线程对共享变量的修改,其他线程能够立即看到。
示例 :可见性问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class VisibilityDemo extends Thread { private volatile boolean running = true ; @Override public void run () { while (running) { } System.out.println("线程结束了" ); } public void stopRunning () { running = false ; } public static void main (String[] args) throws InterruptedException { VisibilityDemo demo = new VisibilityDemo (); demo.start(); Thread.sleep(1000 ); demo.stopRunning(); } }
3. 有序性(Ordering) 程序执行的顺序按照代码的先后顺序执行。
问题 :编译器和处理器可能会对指令进行重排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class OrderingDemo { private int a = 0 ; private boolean flag = false ; public void writer () { a = 1 ; flag = true ; } public void reader () { if (flag) { int i = a; System.out.println(i); } } }
指令重排后可能的执行顺序:
flag = true
a = 1
读取 a(此时 a = 1)
读取 flag(此时 flag = true)
导致 i 的值可能不是我们期望的 1。
volatile 关键字 volatile 是 Java 中最轻量的同步机制,它有两个作用:
保证可见性 :被 volatile 修饰的变量,修改后会立即写回主内存,并让其他线程的缓存失效
禁止指令重排序 :volatile 变量操作前后的代码不能被重排序
1 2 3 4 5 6 7 8 public class VolatileDemo { private volatile int count = 0 ; public void increment () { count++; } }
volatile 的适用场景 :
状态标志位(如 running)
双重检查锁定(Double Checked Locking)
观察量(observer)
volatile 的局限性 :
不能保证原子性(如 count++)
不能替代 synchronized
synchronized 关键字 synchronized 是 Java 中最基础的同步机制,可以保证:
用法 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class SynchronizedDemo { public synchronized void method1 () { } public static synchronized void method2 () { } public void method3 () { synchronized (this ) { } } public void method4 () { synchronized (SynchronizedDemo.class) { } } }
Synchronized 原理 :
flowchart TD
A[进入 synchronized] --> B{对象是否被占用}
B -->|否| C[获取锁 设置 Mark Word]
B -->|是| D{是否同一线程}
D -->|是| E[重入 计数器+1]
D -->|否| F[进入等待队列 阻塞]
C --> G[执行同步代码]
E --> G
G --> H{正常退出 异常退出}
H --> I[释放锁 计数器-1]
I -->|计数器=0| J[唤醒等待线程]
style C fill:#c8e6c9
style F fill:#ffcdd2
线程池 线程池是并发编程中最重要的组件之一,它避免了频繁创建和销毁线程的开销。
flowchart TD
A[提交任务] --> B{核心线程池 是否满}
B -->|否| C[创建新线程 执行任务]
B -->|是| D{任务队列 是否满}
D -->|否| E[任务入队等待]
D -->|是| F{最大线程数 是否满}
F -->|否| G[创建新线程 执行任务]
F -->|是| H[执行拒绝策略]
C --> I[任务执行完成]
E --> I
G --> I
style A fill:#e3f2fd
style H fill:#ffcdd2
线程池参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class ThreadPoolDemo { public static void main (String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor ( 2 , 5 , 60L , TimeUnit.SECONDS, new LinkedBlockingQueue <>(3 ), Executors.defaultThreadFactory(), new ThreadPoolExecutor .AbortPolicy() ); for (int i = 1 ; i <= 8 ; i++) { final int taskId = i; executor.submit(() -> { System.out.println("任务 " + taskId + " 由线程 " + Thread.currentThread().getName() + " 执行" ); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } }); } executor.shutdown(); } }
拒绝策略
策略
行为
AbortPolicy
抛出 RejectedExecutionException(默认)
CallerRunsPolicy
由调用者线程执行
DiscardPolicy
静默丢弃任务
DiscardOldestPolicy
丢弃队列中最老的任务
Executors 工具类创建线程池 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class ExecutorsDemo { public static void main (String[] args) { ExecutorService fixedPool = Executors.newFixedThreadPool(5 ); ExecutorService singlePool = Executors.newSingleThreadExecutor(); ExecutorService cachedPool = Executors.newCachedThreadPool(); ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3 ); ExecutorService workStealingPool = Executors.newWorkStealingPool(); } }
JUC 并发工具类 CountDownLatch 门闩,等待一组线程完成后再执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class CountDownLatchDemo { public static void main (String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch (3 ); for (int i = 1 ; i <= 3 ; i++) { final int playerId = i; new Thread (() -> { System.out.println("选手 " + playerId + " 准备完成" ); latch.countDown(); }).start(); } latch.await(); System.out.println("所有选手准备完毕,开始比赛!" ); } }
CyclicBarrier 循环栅栏,让一组线程互相等待,达到某个点后全部放行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class CyclicBarrierDemo { public static void main (String[] args) { CyclicBarrier barrier = new CyclicBarrier (3 , () -> { System.out.println("所有玩家都准备好了,游戏开始!" ); }); for (int i = 1 ; i <= 3 ; i++) { final int playerId = i; new Thread (() -> { System.out.println("玩家 " + playerId + " 加载中..." ); try { Thread.sleep((long ) (Math.random() * 1000 )); System.out.println("玩家 " + playerId + " 准备完成,等待其他人..." ); barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
Semaphore 信号量,控制同时访问某个资源的线程数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class SemaphoreDemo { private static final int SPOTS = 3 ; private static final Semaphore semaphore = new Semaphore (SPOTS); public static void main (String[] args) { for (int i = 1 ; i <= 5 ; i++) { final int carId = i; new Thread (() -> { try { semaphore.acquire(); System.out.println("车辆 " + carId + " 进入停车场" ); Thread.sleep((long ) (Math.random() * 3000 )); System.out.println("车辆 " + carId + " 离开停车场" ); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } } }
ConcurrentHashMap 线程安全的 HashMap,高并发场景首选。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ConcurrentHashMapDemo { public static void main (String[] args) { ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap <>(); map.put("A" , 1 ); map.putIfAbsent("B" , 2 ); map.get("A" ); map.remove("A" ); map.size(); map.putIfAbsent("counter" , 0 ); map.replace("counter" , map.get("counter" ), map.get("counter" ) + 1 ); map.compute("counter" , (k, v) -> v == null ? 1 : v + 1 ); map.computeIfPresent("counter" , (k, v) -> v + 1 ); map.computeIfAbsent("newCounter" , k -> 10 ); } }
集合源码解析 HashMap 底层实现 HashMap 是 Java 中最常用的 Map 实现,了解其源码对于深入理解数据结构至关重要。
JDK 1.7 vs JDK 1.8
flowchart LR
A[JDK 1.7] --> B[数组 + 链表 Entry[] table]
A --> C[头插法 可能导致死循环]
D[JDK 1.8] --> E[数组 + 链表 + 红黑树 Node[] table]
D --> F[尾插法 避免死循环]
D --> G[链表长度>8转红黑树]
style B fill:#e3f2fd
style E fill:#c8e6c9
HashMap 基本结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class HashMap <K, V> extends AbstractMap <K, V> implements Map <K, V>, Cloneable, Serializable { static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ; static final int MAXIMUM_CAPACITY = 1 << 30 ; static final float DEFAULT_LOAD_FACTOR = 0.75f ; static final int TREEIFY_THRESHOLD = 8 ; static final int UNTREEIFY_THRESHOLD = 6 ; static final int MIN_TREEIFY_CAPACITY = 64 ; transient Node<K, V>[] table; transient int size; }
hash() 方法详解 1 2 3 4 5 6 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
flowchart LR
A[key.hashCode] --> B[高16位不变 低16位与高16位异或]
B --> C[让hash分布更均匀]
C --> D[计算数组下标 hash & (length-1)]
style A fill:#e3f2fd
style D fill:#c8e6c9
为什么用 (n-1) & hash 而不是 hash % n?
n 必须是 2 的幂次方(如 16)
n - 1 的二进制是低位全为 1
& 操作比 % 操作快得多
1 2 3 4 5 6 int index = hash(key) & (capacity - 1 );
put() 方法流程
flowchart TD
A[put(key, value)] --> B{key 是否为 null}
B -->|是| C[处理 key 为 null 的情况]
B -->|否| D[计算 hash(key)]
D --> E[计算数组下标]
E --> F{该位置是否为空}
F -->|是| G[直接插入 Node]
F -->|否| H{该位置是否是红黑树}
H -->|是| I[插入红黑树节点]
H -->|否| J{链表是否已存在该 key}
J -->|是| K[覆盖 value]
J -->|否| L[尾插法添加到链表]
L --> M{链表长度是否>8}
M -->|是| N{是否满足树化条件}
N -->|是| O[链表转为红黑树]
N -->|否| P[继续使用链表]
G --> Q[检查是否需要扩容]
I --> Q
K --> Q
O --> Q
P --> Q
Q --> R{size 是否 > threshold}
R -->|是| S[扩容 resize]
R -->|否| T[返回]
style S fill:#ffcdd2
扩容机制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 Node<K, V>[] resize() { newCap = oldCap << 1 ; newThr = oldThr << 1 ; Node<K, V>[] newTable = new Node [newCap]; for (int j = 0 ; j < oldCap; j++) { Node<K, V> e; if ((e = oldTab[j]) != null ) { oldTab[j] = null ; if (e.next == null ) { newTab[e.hash & (newCap - 1 )] = e; } else if (e instanceof TreeNode) { ((TreeNode<K, V>) e).split(this , newTable, j, oldCap); } else { Node<K, V> loHead = null , loTail = null ; Node<K, V> hiHead = null , hiTail = null ; Node<K, V> next; do { next = e.next; if ((e.hash & oldCap) == 0 ) { if (loTail == null ) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null ) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null ); if (loTail != null ) { loTail.next = null ; newTab[j] = loHead; } if (hiTail != null ) { hiTail.next = null ; newTab[j + oldCap] = hiHead; } } } } return newTable; }
扩容后元素位置规律 :
(e.hash & oldCap) == 0 → 留在原位置
(e.hash & oldCap) != 0 → 移动到原位置 + oldCap
flowchart LR
subgraph 扩容示意
A[oldCap=16] --> B[oldCap=32]
A --> C[元素位置不变 hash & 16 == 0]
A --> D[位置 +16 hash & 16 != 0]
end
style C fill:#c8e6c9
style D fill:#e3f2fd
ConcurrentHashMap 源码解析 JDK 1.7 分段锁
flowchart TD
A[ConcurrentHashMap] --> B[Segment[] 数组]
B --> C[Segment 继承 ReentrantLock]
C --> D[HashEntry[] table]
D --> E[链表节点]
style A fill:#fff3e0
style B fill:#e3f2fd
每个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表节点。并发度由 Segment 数组大小决定,默认 16。
JDK 1.8 CAS + synchronized
flowchart TD
A[ConcurrentHashMap JDK 1.8] --> B[Node[] table volatile 数组]
A --> C[CAS 操作 保证原子性]
A --> D[synchronized 锁桶头节点]
C --> E[putVal]
D --> E
E --> F{是否需要初始化}
F -->|是| G[initTable CAS 保证单线程]
E --> H{key 是否存在}
H -->|是| I[覆盖 value]
H -->|否| J{链表长度>8}
J -->|是| K[转为红黑树]
J -->|否| L[尾插法]
L --> M[检查扩容]
K --> M
style A fill:#fff3e0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ConcurrentHashMapDemo { public static void main (String[] args) { ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap <>(); map.put("A" , 1 ); map.putIfAbsent("B" , 2 ); map.putIfAbsent("counter" , 0 ); map.put("counter" , map.get("counter" ) + 1 ); map.compute("counter" , (k, v) -> v == null ? 1 : v + 1 ); } }
设计模式 设计模式分类
flowchart TD
A[设计模式] --> B[创建型模式]
A --> C[结构型模式]
A --> D[行为型模式]
B --> B1[单例模式]
B --> B2[工厂模式]
B --> B3[建造者模式]
B --> B4[原型模式]
C --> C1[代理模式]
C --> C2[装饰器模式]
C --> C3[适配器模式]
C --> C4[组合模式]
D --> D1[策略模式]
D --> D2[观察者模式]
D --> D3[责任链模式]
D --> D4[模板方法模式]
style A fill:#fff3e0
单例模式 确保一个类只有一个实例,并提供一个全局访问点。
饿汉式(线程安全) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class HungrySingleton { private static final HungrySingleton INSTANCE = new HungrySingleton (); private HungrySingleton () { if (INSTANCE != null ) { throw new RuntimeException ("单例对象已存在!" ); } } public static HungrySingleton getInstance () { return INSTANCE; } }
懒汉式(线程不安全) 1 2 3 4 5 6 7 8 9 10 11 12 public class LazySingleton { private static LazySingleton instance; private LazySingleton () {} public static LazySingleton getInstance () { if (instance == null ) { instance = new LazySingleton (); } return instance; } }
双重检查锁定(线程安全,推荐) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class DoubleCheckSingleton { private static volatile DoubleCheckSingleton instance; private DoubleCheckSingleton () {} public static DoubleCheckSingleton getInstance () { if (instance == null ) { synchronized (DoubleCheckSingleton.class) { if (instance == null ) { instance = new DoubleCheckSingleton (); } } } return instance; } }
静态内部类(线程安全,推荐) 1 2 3 4 5 6 7 8 9 10 11 12 13 public class StaticInnerClassSingleton { private StaticInnerClassSingleton () {} private static class SingletonHolder { private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton (); } public static StaticInnerClassSingleton getInstance () { return SingletonHolder.INSTANCE; } }
工厂模式 简单工厂 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 interface Phone { void call () ; } class IPhone implements Phone { @Override public void call () { System.out.println("用 iPhone 打电话" ); } } class AndroidPhone implements Phone { @Override public void call () { System.out.println("用安卓手机打电话" ); } } class SimplePhoneFactory { public static Phone createPhone (String type) { switch (type) { case "iPhone" : return new IPhone (); case "Android" : return new AndroidPhone (); default : throw new IllegalArgumentException ("未知类型" ); } } }
工厂方法模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 interface PhoneFactory { Phone createPhone () ; } class IPhoneFactory implements PhoneFactory { @Override public Phone createPhone () { return new IPhone (); } } class AndroidFactory implements PhoneFactory { @Override public Phone createPhone () { return new AndroidPhone (); } } public class FactoryMethodDemo { public static void main (String[] args) { PhoneFactory factory = new IPhoneFactory (); Phone phone = factory.createPhone(); phone.call(); } }
抽象工厂模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 interface Laptop { void display () ; } interface Phone { void call () ; } class MacBook implements Laptop { @Override public void display () { System.out.println("MacBook 显示" ); } } class iPhone implements Phone { @Override public void call () { System.out.println("iPhone 打电话" ); } } interface DeviceFactory { Laptop createLaptop () ; Phone createPhone () ; } class AppleFactory implements DeviceFactory { @Override public Laptop createLaptop () { return new MacBook (); } @Override public Phone createPhone () { return new iPhone (); } }
策略模式 定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 interface SortStrategy { void sort (int [] array) ; } class BubbleSort implements SortStrategy { @Override public void sort (int [] array) { System.out.println("使用冒泡排序" ); } } class QuickSort implements SortStrategy { @Override public void sort (int [] array) { System.out.println("使用快速排序" ); } } class Sorter { private SortStrategy strategy; public void setStrategy (SortStrategy strategy) { this .strategy = strategy; } public void sort (int [] array) { strategy.sort(array); } } public class StrategyDemo { public static void main (String[] args) { int [] array = {3 , 1 , 4 , 1 , 5 , 9 , 2 , 6 }; Sorter sorter = new Sorter (); sorter.setStrategy(new BubbleSort ()); sorter.sort(array); sorter.setStrategy(new QuickSort ()); sorter.sort(array); } }
代理模式 为其他对象提供一种代理以控制对这个对象的访问。
静态代理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 interface Image { void display () ; } class RealImage implements Image { private String filename; public RealImage (String filename) { this .filename = filename; loadFromDisk(); } @Override public void display () { System.out.println("显示图片:" + filename); } private void loadFromDisk () { System.out.println("从磁盘加载:" + filename); } } class ImageProxy implements Image { private RealImage realImage; private String filename; public ImageProxy (String filename) { this .filename = filename; } @Override public void display () { if (realImage == null ) { realImage = new RealImage (filename); } realImage.display(); } } public class ProxyDemo { public static void main (String[] args) { Image image = new ImageProxy ("photo.jpg" ); System.out.println("图片代理已创建" ); image.display(); image.display(); } }
JDK 动态代理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class JDKDynamicProxy { public static void main (String[] args) { RealImage realImage = new RealImage ("photo.jpg" ); Image proxy = (Image) Proxy.newProxyInstance( RealImage.class.getClassLoader(), RealImage.class.getInterfaces(), new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置操作" ); Object result = method.invoke(realImage, args); System.out.println("后置操作" ); return result; } } ); proxy.display(); } }
常用工具类 StringUtils 常用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import org.apache.commons.lang3.StringUtils;public class StringUtilsDemo { public static void main (String[] args) { String str = " hello world " ; StringUtils.isEmpty(str); StringUtils.isBlank(str); StringUtils.isBlank("" ); StringUtils.isBlank(null ); StringUtils.trim(str); StringUtils.trimToNull(str); StringUtils.trimToEmpty(str); StringUtils.equals("abc" , "abc" ); StringUtils.equalsIgnoreCase("abc" , "ABC" ); StringUtils.contains("abc" , "b" ); StringUtils.containsIgnoreCase("ABC" , "b" ); StringUtils.substring("hello world" , 6 ); StringUtils.substring("hello world" , 0 , 5 ); StringUtils.split("a,b,c" , "," ); StringUtils.join(["a" , "b" , "c" ], "-" ); StringUtils.repeat("ab" , 3 ); StringUtils.reverse("hello" ); StringUtils.upperCase("hello" ); StringUtils.lowerCase("HELLO" ); StringUtils.capitalize("hello" ); } }
CollectionUtils 常用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import org.apache.commons.collections4.CollectionUtils;public class CollectionUtilsDemo { public static void main (String[] args) { List<String> list1 = Arrays.asList("A" , "B" , "C" ); List<String> list2 = Arrays.asList("B" , "C" , "D" ); CollectionUtils.isEmpty(list1); CollectionUtils.isNotEmpty(list1); List<String> intersection = new ArrayList <>(CollectionUtils.intersection(list1, list2)); CollectionUtils.union(list1, list2); CollectionUtils.subtract(list1, list2); CollectionUtils.containsAny(list1, list2); List<String> filtered = list1.stream() .filter(s -> s.equals("A" )) .collect(Collectors.toList()); List<String> safeList = CollectionUtils.isEmpty(list1) ? Collections.emptyList() : list1; } }
Objects 常用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import java.util.Objects;public class ObjectsDemo { public static void main (String[] args) { Objects.equals("abc" , "abc" ); Objects.equals(null , "abc" ); Objects.equals(null , null ); Objects.isNull(null ); Objects.nonNull(null ); public void method (String param) { Objects.requireNonNull(param, "参数不能为空" ); } Objects.toString(null ); Objects.toString(null , "默认值" ); Objects.hashCode(null ); } }
Optional 最佳实践 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import java.util.Optional;public class OptionalDemo { public static void main (String[] args) { Optional<String> empty = Optional.empty(); Optional<String> of = Optional.of("hello" ); Optional<String> nullable = Optional.ofNullable(null ); of.isPresent(); of.isEmpty(); of.get(); nullable.orElse("default" ); nullable.orElseGet(() -> "computed" ); of.ifPresent(System.out::println); of.map(String::toUpperCase); of.flatMap(s -> Optional.of(s.trim())); of.filter(s -> s.length() > 3 ); String result = Optional.ofNullable(user) .map(User::getName) .map(String::toUpperCase) .orElse("匿名" ); } }
📌 总结
mindmap
root((Java 进阶))
JVM
内存模型
程序计数器
虚拟机栈
本地方法栈
堆
方法区
垃圾回收
标记-清除
复制算法
标记-整理
分代收集
类加载
双亲委派
自定义加载器
并发编程
三大概念
原子性
可见性
有序性
volatile
synchronized
线程池
JUC 工具类
集合源码
HashMap
ConcurrentHashMap
设计模式
单例
工厂
策略
代理
进阶学习建议
多读源码 :JDK 源码、Spring 源码、MyBatis 源码
手写实现 :自己实现一遍 HashMap、线程池等
性能调优 :学习 JVM 调优、SQL 调优
分布式 :学习 Redis、RocketMQ、Kafka
微服务 :Spring Cloud、Dubbo、Service Mesh
向下扎根,向上生长 🌱
📅 本文首次发布于 2026 年 5 月 20 日