几十g的学习资料 让我喂给ai整理成了一份一眼就会的文档。
马士兵设计模式 & JVM 完整学习笔记
基于尚学堂马士兵视频教程整理,覆盖全部课程知识点
整理时间:2026-09-23
目录
第一部分:设计模式
模块一:OO思维热身 Thinking in OO
对应视频:Thinking_In_OO_01 ~ 04(共4集)
1.1 什么是面向对象思维?
面向对象(Object-Oriented)不是一门语言,而是一种思维方式。学会OO,意味着你从"面向过程"的命令式思维,转变为"找对象、发消息"的协作思维。
核心思想
面向过程思维:
"我要做这件事 → 第一步 → 第二步 → 第三步"
主语是"我",我在操控一切
面向对象思维:
"这件事该找谁来做?→ 找到对象 → 让它去做"
主语是"对象",对象自己负责自己的行为
1.2 OO的四大基本原则
① 抽象(Abstraction)
- 从复杂问题中提取出关键特征,忽略无关细节
- 例:设计一个"汽车"类,只需要关心颜色、速度、品牌,不需要关心轮胎的螺丝怎么拧
// 抽象:只暴露关键特征
public abstract class Vehicle {
protected String brand;
protected int speed;
public abstract void start();
public abstract void stop();
}
② 封装(Encapsulation)
- 把数据(属性)和操作数据的方法绑定在一起
- 隐藏内部实现细节,只暴露必要的接口
- 好处:安全、易维护、调用者无需了解内部
public class BankAccount {
private double balance; // 私有,外部无法直接修改
public void deposit(double amount) {
if (amount > 0) balance += amount; // 内部控制逻辑
}
public double getBalance() {
return balance; // 只读暴露
}
}
③ 继承(Inheritance)
- 子类继承父类的属性和方法,实现代码复用
- Java只支持单继承,但可以通过接口实现多重继承效果
- 核心原则:is-a 关系才用继承
public class Animal {
public void eat() { System.out.println("eating..."); }
}
public class Dog extends Animal {
public void bark() { System.out.println("woof!"); }
}
// Dog is-a Animal ✓
// Dog has-a Tail ✗(这种情况应该用组合)
④ 多态(Polymorphism)
- 同一个方法调用,因为对象不同而产生不同行为
- 实现方式:继承 + 方法重写 + 父类引用指向子类对象
Animal a = new Dog();
a.makeSound(); // "Woof!" ——运行时才知道是Dog
Animal b = new Cat();
b.makeSound(); // "Meow!" ——多态的威力
1.3 面向对象的分析与设计(OOA/OOD)
分析阶段(OOA)
- 找名词 → 这些是候选类
- 找动词 → 这些是方法
- 找形容词 → 这些是属性
例题:设计一个"学生选课系统"
- 名词:学生、课程、教师、成绩 → 候选类
- 动词:选课、退课、打分、查询 → 方法
- 形容词:学生姓名、课程学分 → 属性
设计阶段(OOD)
- 单一职责原则(SRP):一个类只负责一件事
- 开闭原则(OCP):对扩展开放,对修改关闭
- 里氏替换原则(LSP):子类必须能完全替代父类
- 接口隔离原则(ISP):接口要小而专,不要大而全
- 依赖倒置原则(DIP):依赖抽象,不依赖具体实现
1.4 组合 vs 继承
这是OO设计中最重要的决策之一:
// ❌ 错误使用继承
public class Car extends Engine {
// Car "是一个" Engine?不对!
}
// ✓ 正确使用组合
public class Car {
private Engine engine; // Car "有一个" Engine
public Car() {
this.engine = new Engine();
}
}
经验法则:
- is-a 关系 → 用继承(如:狗是动物)
- has-a 关系 → 用组合(如:汽车有引擎)
- 优先使用组合,组合更灵活
1.5 接口与抽象类的区别
| 对比项 | 接口(Interface) | 抽象类(Abstract Class) |
|---|---|---|
| 关键字 | interface |
abstract class |
| 实现方式 | implements |
extends |
| 多重继承 | 支持多实现 | 不支持 |
| 构造方法 | 没有 | 有 |
| 成员变量 | 默认 public static final |
任意 |
| 方法实现 | Java 8之前只能抽象方法 | 可以有具体方法 |
| 设计意图 | "能做什么"(能力规范) | "是什么"(本质分类) |
// 接口:定义能力规范
public interface Flyable {
void fly();
}
// 抽象类:定义本质分类
public abstract class Bird {
protected String name;
public abstract void sing();
}
// 类可以既是Bird,又能实现Flyable
public class Sparrow extends Bird implements Flyable {
public void sing() { System.out.println("chirp"); }
public void fly() { System.out.println("flying..."); }
}
模块二:工厂系列 Factory Patterns
对应视频:工厂系列_01 ~ 05(共5集)
2.1 为什么需要工厂模式?
// 没有工厂的世界:
public class CarFactory {
public void drive() {
Car car = new BMW(); // 直接new,耦合死了!
car.run();
}
}
问题: 如果想换成奔驰,必须修改源码,违反开闭原则。
2.2 简单工厂(Simple Factory)
严格来说不属于GoF23种设计模式,但非常常用
// 产品接口
public interface Car {
void run();
}
// 具体产品
public class BMW implements Car {
public void run() { System.out.println("BMW running..."); }
}
public class Benz implements Car {
public void run() { System.out.println("Benz running..."); }
}
// 简单工厂
public class CarFactory {
public static Car createCar(String type) {
switch(type) {
case "BMW": return new BMW();
case "Benz": return new Benz();
default: throw new IllegalArgumentException("Unknown type");
}
}
}
// 使用
Car car = CarFactory.createCar("BMW");
car.run();
优缺点:
- ✅ 调用者无需知道具体类名,只需传参数
- ❌ 每增加一种产品,工厂的switch就要修改(违反OCP)
2.3 工厂方法模式(Factory Method)⭐
GoF23种设计模式之一,最核心的工厂模式
// 产品接口
public interface Car {
void run();
}
// 具体产品
public class BMW implements Car {
public void run() { System.out.println("BMW running..."); }
}
public class Benz implements Car {
public void run() { System.out.println("Benz running..."); }
}
// 工厂接口
public interface CarFactory {
Car createCar();
}
// 具体工厂
public class BMWFactory implements CarFactory {
public Car createCar() { return new BMW(); }
}
public class BenzFactory implements CarFactory {
public Car createCar() { return new Benz(); }
}
// 使用
CarFactory factory = new BMWFactory();
Car car = factory.createCar();
car.run();
优点:
- ✅ 符合开闭原则:增加新产品只需加新工厂,不改旧代码
- ✅ 每个工厂只负责一个产品,职责单一
2.4 抽象工厂模式(Abstract Factory)⭐
创建一系列相关产品,而不是单个产品
// 产品族:汽车 + 发动机(同一品牌的配套产品)
public interface Car { void run(); }
public interface Engine { void start(); }
public class BMWCar implements Car {
public void run() { System.out.println("BMW car running"); }
}
public class BMWEngine implements Engine {
public void start() { System.out.println("BMW engine started"); }
}
public class BenzCar implements Car {
public void run() { System.out.println("Benz car running"); }
}
public class BenzEngine implements Engine {
public void start() { System.out.println("Benz engine started"); }
}
// 抽象工厂
public interface AbstractFactory {
Car createCar();
Engine createEngine();
}
// 具体工厂
public class BMWFactory implements AbstractFactory {
public Car createCar() { return new BMWCar(); }
public Engine createEngine() { return new BMWEngine(); }
}
// 使用:保证产品配套
AbstractFactory factory = new BMWFactory();
Car car = factory.createCar();
Engine engine = factory.createEngine(); // 保证是BMW发动机
适用场景:
- 需要创建一组相关的产品(产品族)
- 强调产品之间的配套关系
2.5 工厂模式对比总结
| 模式 | 产品数量 | 产品族 | 扩展方式 |
|---|---|---|---|
| 简单工厂 | 单产品 | 无 | 修改工厂(违反OCP) |
| 工厂方法 | 单产品 | 无 | 新增工厂类 |
| 抽象工厂 | 多产品 | 有 | 新增工厂类(整个产品族) |
2.6 实际应用案例
// Java标准库中的工厂模式
// 1. Iterator(工厂方法)
Collection<String> list = new ArrayList<>();
Iterator<String> it = list.iterator(); // Collection是工厂接口
// 2. Spring的BeanFactory
BeanFactory factory = new ClassPathXmlApplicationContext("beans.xml");
Object bean = factory.getBean("myBean");
// 3. JDBC
Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement(); // 抽象工厂
模块三:Strategy 策略模式
对应视频:Strategy_01 ~ 09(共9集)
3.1 问题引入:鸭子游戏
// 最初的设计:继承
public abstract class Duck {
public void quack() { System.out.println("嘎嘎叫"); }
public void swim() { System.out.println("游泳"); }
public abstract void display(); // 每种鸭子外观不同
}
public class MallardDuck extends Duck {
public void display() { System.out.println("绿头鸭"); }
}
// 需求变化:要让鸭子飞!
public abstract class Duck {
public void fly() { System.out.println("飞"); } // 加在父类
}
// ❌ 问题:橡皮鸭也会飞了!
3.2 策略模式核心思想
找出会变化的部分,把它封装起来,和不变的部分分离。
// 变化的部分:飞行行为和叫声行为 → 抽成接口
public interface FlyBehavior {
void fly();
}
public interface QuackBehavior {
void quack();
}
// 具体的飞行策略
public class FlyWithWings implements FlyBehavior {
public void fly() { System.out.println("用翅膀飞"); }
}
public class FlyNoWay implements FlyBehavior {
public void fly() { System.out.println("不会飞"); }
}
public class FlyRocketPowered implements FlyBehavior {
public void fly() { System.out.println("用火箭飞!"); }
}
// 具体的叫声策略
public class Quack implements QuackBehavior {
public void quack() { System.out.println("嘎嘎叫"); }
}
public class Squeak implements QuackBehavior {
public void quack() { System.out.println("吱吱叫"); }
}
3.3 策略模式结构
public abstract class Duck {
// 持有策略对象(组合优于继承)
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {}
// 委托给策略执行
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
// 运行时动态更换策略
public void setFlyBehavior(FlyBehavior fb) {
this.flyBehavior = fb;
}
public abstract void display();
}
// 绿头鸭
public class MallardDuck extends Duck {
public MallardDuck() {
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
public void display() { System.out.println("绿头鸭"); }
}
// 模型鸭(不会飞)
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Squeak();
}
public void display() { System.out.println("模型鸭"); }
}
3.4 动态更换策略的威力
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performFly(); // 用翅膀飞
mallard.performQuack(); // 嘎嘎叫
Duck model = new ModelDuck();
model.performFly(); // 不会飞
// 给模型鸭装火箭!——运行时动态更换策略
model.setFlyBehavior(new FlyRocketPowered());
model.performFly(); // 用火箭飞!
}
}
3.5 策略模式的通用结构
Context(上下文)
├── 持有 Strategy 引用
├── 调用 Strategy 执行算法
└── 可动态替换 Strategy
Strategy(策略接口)
├── ConcreteStrategyA
├── ConcreteStrategyB
└── ConcreteStrategyC
// 通用模板
public interface Strategy {
void algorithm();
}
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void execute() {
strategy.algorithm();
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
}
3.6 实际应用:排序策略
// 策略接口
public interface SortStrategy {
void sort(int[] arr);
}
// 冒泡排序策略
public class BubbleSort implements SortStrategy {
public void sort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++)
for (int j = 0; j < arr.length - 1 - i; j++)
if (arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
// 快速排序策略
public class QuickSort implements SortStrategy {
public void sort(int[] arr) {
// 快速排序实现...
Arrays.sort(arr); // 简化演示
}
}
// 上下文:根据数据量选择策略
public class Sorter {
private SortStrategy strategy;
public void sort(int[] arr) {
if (arr.length < 100) {
strategy = new BubbleSort(); // 小数据用冒泡
} else {
strategy = new QuickSort(); // 大数据用快排
}
strategy.sort(arr);
}
}
3.7 Java中的策略模式应用
// 1. Comparator接口就是策略模式
List<String> list = Arrays.asList("b", "a", "c");
Collections.sort(list, new Comparator<String>() {
public int compare(String a, String b) {
return a.compareTo(b); // 排序策略
}
});
// 2. Java 8 Lambda简化策略
Collections.sort(list, (a, b) -> a.compareTo(b));
// 3. ThreadPoolExecutor的拒绝策略
new ThreadPoolExecutor.AbortPolicy(); // 策略A:抛异常
new ThreadPoolExecutor.CallerRunsPolicy(); // 策略B:调用者执行
new ThreadPoolExecutor.DiscardPolicy(); // 策略C:丢弃
3.8 策略模式的优缺点
优点:
- ✅ 消除大量 if-else / switch 判断
- ✅ 符合开闭原则,新增策略不影响已有代码
- ✅ 运行时可动态切换算法
- ✅ 避免代码重复(公共逻辑在Context中)
缺点:
- ❌ 策略类增多,增加系统复杂度
- ❌ 客户端需要了解不同策略的区别
解决缺点: 结合工厂模式,由工厂根据条件自动选择策略。
模块四:Iterator 迭代器模式
对应视频:Iterator_01 ~ 06(共6集)
4.1 问题引入
// 有ArrayList,也有LinkedList,还有数组
// 如何统一遍历它们?
ArrayList<String> list = new ArrayList<>();
LinkedList<String> linkedList = new LinkedList<>();
String[] arr = new String[10];
// 每种集合遍历方式不同 → 调用者代码写死
// 如果集合类型变了,遍历代码也要改
4.2 迭代器模式定义
提供一种方法,顺序访问集合中的各个元素,而不暴露集合的内部表示。
4.3 手写一个迭代器
// 迭代器接口
public interface Iterator<E> {
boolean hasNext(); // 是否还有下一个元素
E next(); // 返回下一个元素
void remove(); // 移除当前元素(可选)
}
// 自定义集合接口
public interface Collection<E> {
void add(E e);
int size();
Iterator<E> iterator(); // 关键:返回一个迭代器
}
// 具体集合:ArrayList风格
public class ArrayList<E> implements Collection<E> {
private Object[] elements = new Object[10];
private int size = 0;
public void add(E e) {
if (size >= elements.length) {
// 扩容...
}
elements[size++] = e;
}
public int size() { return size; }
// 返回专门针对数组的迭代器
public Iterator<E> iterator() {
return new ArrayIterator();
}
// 内部类:迭代器
private class ArrayIterator implements Iterator<E> {
private int cursor = 0;
public boolean hasNext() {
return cursor < size;
}
public E next() {
return (E) elements[cursor++];
}
public void remove() {
// 移除逻辑...
}
}
}
// LinkedList风格的迭代器(内部结构完全不同)
public class LinkedList<E> implements Collection<E> {
private Node<E> head;
private int size = 0;
// ... add方法省略 ...
public Iterator<E> iterator() {
return new LinkedIterator();
}
private class LinkedIterator implements Iterator<E> {
private Node<E> current = head;
public boolean hasNext() {
return current != null;
}
public E next() {
E value = current.value;
current = current.next; // 链表遍历方式
return value;
}
}
}
4.4 统一遍历的威力
// 不管底层是什么结构,遍历方式完全一样!
public void printAll(Collection<String> col) {
Iterator<String> it = col.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
// ArrayList和LinkedList都能用同一个方法遍历
printAll(new ArrayList<>());
printAll(new LinkedList<>());
4.5 Java内置迭代器
// Java中的Iterable接口
public interface Iterable<E> {
Iterator<E> iterator();
}
// 增强for循环的本质
for (String s : list) {
// 编译器自动转为:
// Iterator<String> it = list.iterator();
// while(it.hasNext()) { String s = it.next(); ... }
}
// 手动使用Iterator
List<String> list = new ArrayList<>();
list.add("A"); list.add("B"); list.add("C");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if ("B".equals(s)) {
it.remove(); // 安全删除,不用自己管理索引
}
}
4.6 迭代器模式的优缺点
优点:
- ✅ 统一了不同集合的遍历方式
- ✅ 集合类和遍历逻辑分离,职责清晰
- ✅ 支持多种遍历方式(正向、反向、跳过等)
- ✅ 符合开闭原则,新增集合类型不影响遍历代码
缺点:
- ❌ 简单集合使用迭代器反而比直接遍历复杂
模块五:动态代理 Dynamic Proxy
对应视频:动态代理_01 ~ 09(共9集)
5.1 代理模式基础
代理模式:为其他对象提供一种代理,以控制对这个对象的访问。
客户端 → 代理对象 → 真实对象
↑
在这里加入额外逻辑(日志、权限、事务等)
5.2 静态代理
// 接口
public interface UserService {
void save(String username);
}
// 真实对象
public class UserServiceImpl implements UserService {
public void save(String username) {
System.out.println("保存用户:" + username);
}
}
// 静态代理
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
public void save(String username) {
System.out.println("【日志】开始保存用户"); // 前置增强
target.save(username); // 真实调用
System.out.println("【日志】保存用户完成"); // 后置增强
}
}
// 使用
UserService service = new UserServiceProxy(new UserServiceImpl());
service.save("张三");
静态代理的问题:
- 每个接口都需要一个对应的代理类
- 接口增加方法,代理类也要修改
- 代码大量重复!
5.3 JDK动态代理 ⭐
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 核心:InvocationHandler
public class LogHandler implements InvocationHandler {
private Object target; // 真实对象
public LogHandler(Object target) {
this.target = target;
}
// 所有代理方法都会调用这个invoke
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("【日志】" + method.getName() + " 开始");
// 反射调用真实方法
Object result = method.invoke(target, args);
System.out.println("【日志】" + method.getName() + " 结束");
return result;
}
}
// 创建代理对象
UserService realService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(), // 类加载器
realService.getClass().getInterfaces(), // 接口列表
new LogHandler(realService) // 调用处理器
);
proxy.save("张三"); // 自动触发invoke,加入日志逻辑
5.4 动态代理工作原理
Proxy.newProxyInstance() 做了什么?
1. 根据接口列表,动态生成一个代理类(字节码)
2. 代理类继承了 Proxy,实现了所有传入的接口
3. 每个方法都调用 InvocationHandler.invoke()
4. 返回这个动态生成的代理类实例
内存中的代理类长这样:
public class $Proxy0 extends Proxy implements UserService {
private InvocationHandler h;
public void save(String username) {
h.invoke(this, method, new Object[]{username});
}
}
5.5 手写模拟动态代理(理解原理)
// 自定义InvocationHandler接口
public interface MyInvocationHandler {
Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
// 自定义Proxy类(简化版)
public class MyProxy {
public static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
MyInvocationHandler handler) {
// 1. 动态生成Java源代码字符串
String src = generateSource(interfaces);
// 2. 编译成.class文件
// 3. 加载到JVM
// 4. 创建实例并返回
// (完整实现见源码文档)
}
}
5.6 CGLib动态代理(无接口代理)
// JDK动态代理要求目标类必须实现接口
// CGLib通过继承来生成代理类,不需要接口
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
public class CglibProxy implements MethodInterceptor {
public Object createProxy(Class<?> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("前置增强");
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("后置增强");
return result;
}
}
// 使用
UserService proxy = (UserService) new CglibProxy()
.createProxy(UserServiceImpl.class);
proxy.save("张三");
5.7 JDK动态代理 vs CGLib
| 对比项 | JDK动态代理 | CGLib |
|---|---|---|
| 实现方式 | 实现接口 | 继承目标类 |
| 要求 | 目标类必须有接口 | 不需要接口 |
| 性能 | 较快(反射优化后) | 生成类较慢,调用快 |
| 限制 | 只能代理接口方法 | 不能代理final类/方法 |
| Spring默认 | JDK动态代理 | 无接口时自动切换 |
5.8 动态代理在Spring中的应用
// Spring AOP的核心就是动态代理
// 声明式事务:
@Transactional
public void transfer(String from, String to, double amount) {
// 代理在调用前:开启事务
accountDao.deduct(from, amount);
accountDao.add(to, amount);
// 代理在调用后:提交事务(或回滚)
}
// Spring选择策略:
// 目标类有接口 → JDK动态代理
// 目标类无接口 → CGLib
// 强制CGLib:proxyTargetClass=true
模块六:责任链模式 Chain of Responsibility
对应视频:责任链_01 ~ 05(共5集)
6.1 问题场景:Web请求过滤
// 一个HTTP请求到达Servlet之前,需要经过:
// 1. 敏感词过滤
// 2. 登录验证
// 3. 日志记录
// 4. 权限检查
// 5. 字符编码转换
// 如果全写在一个方法里:
public void service(Request req, Response res) {
// 过滤敏感词
String msg = req.getMessage().replace("敏感词", "***");
req.setMessage(msg);
// 检查登录
if (req.getSession().getAttribute("user") == null) {
res.sendRedirect("/login");
return;
}
// 检查权限...
// 记录日志...
// 编码转换...
// 最后才处理业务
processRequest(req, res);
}
// ❌ 耦合严重,难以维护,难以复用
6.2 责任链模式结构
// 过滤器接口
public interface Filter {
void doFilter(Request req, Response res, FilterChain chain);
}
// 过滤器链(核心!)
public class FilterChain {
private List<Filter> filters = new ArrayList<>();
private int pos = 0;
public FilterChain addFilter(Filter filter) {
filters.add(filter);
return this; // 链式调用
}
public void doFilter(Request req, Response res) {
if (pos < filters.size()) {
Filter f = filters.get(pos);
pos++;
f.doFilter(req, res, this); // 调用当前过滤器,并把chain传给它
}
}
}
// 具体过滤器
public class SensitiveWordFilter implements Filter {
public void doFilter(Request req, Response res, FilterChain chain) {
// 前置处理
String msg = req.getMessage().replace("敏感词", "***");
req.setMessage(msg);
// 传递给下一个过滤器(关键!)
chain.doFilter(req, res);
// 后置处理(可选)
}
}
public class LoginFilter implements Filter {
public void doFilter(Request req, Response res, FilterChain chain) {
if (req.getSession().getAttribute("user") == null) {
res.sendRedirect("/login");
return; // 不调用chain,请求终止
}
chain.doFilter(req, res); // 登录通过,继续下一个
}
}
public class LogFilter implements Filter {
public void doFilter(Request req, Response res, FilterChain chain) {
long start = System.currentTimeMillis();
chain.doFilter(req, res); // 继续处理
long time = System.currentTimeMillis() - start;
System.out.println("处理耗时:" + time + "ms");
}
}
6.3 使用责任链
FilterChain chain = new FilterChain();
chain.addFilter(new SensitiveWordFilter())
.addFilter(new LoginFilter())
.addFilter(new LogFilter());
chain.doFilter(request, response);
// 执行顺序:
// SensitiveWordFilter前置 → LoginFilter前置 → LogFilter前置
// → 业务处理
// → LogFilter后置 → LoginFilter后置 → SensitiveWordFilter后置
6.4 责任链的执行顺序(洋葱模型)
请求进入方向 →
┌─────────────────────────────────────┐
│ SensitiveWordFilter(前置) │
│ ┌─────────────────────────────┐ │
│ │ LoginFilter(前置) │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ LogFilter(前置) │ │ │
│ │ │ ┌──────────────┐ │ │ │
│ │ │ │ 业务处理 │ │ │ │
│ │ │ └──────────────┘ │ │ │
│ │ │ LogFilter(后置) │ │ │
│ │ └─────────────────────┘ │ │
│ │ LoginFilter(后置) │ │
│ └─────────────────────────────┘ │
│ SensitiveWordFilter(后置) │
└─────────────────────────────────────┘
6.5 Java Web中的实际应用
// Servlet Filter
public class MyFilter implements javax.servlet.Filter {
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) {
// 前置处理
chain.doFilter(req, res); // 传递
// 后置处理
}
}
// Spring Interceptor(拦截器)
public class MyInterceptor implements HandlerInterceptor {
public boolean preHandle(...) { return true; } // 前置
public void postHandle(...) {} // 后置
public void afterCompletion(...) {} // 完成后
}
// Struts2 Interceptor
// Netty Pipeline
// 都是责任链模式的实现!
6.6 Filter vs Interceptor vs Aspect
| 对比项 | Filter | Interceptor | Aspect |
|---|---|---|---|
| 所属规范 | Servlet | Spring MVC | AOP |
| 拦截范围 | 所有请求 | Controller方法 | 任意方法 |
| 触发时机 | 容器级别 | Spring容器 | 切面代理 |
| 能否访问Spring上下文 | 否 | 是 | 是 |
模块七:追美眉的技术
对应视频:追美眉的技术_1 ~ 8(共8集)
这个系列通过"追女生"的有趣场景,讲解观察者模式(Observer)和装饰器模式(Decorator)
7.1 观察者模式(Observer Pattern)
场景: 你追一个女生,你需要知道她的状态变化(开心/生气/无聊),然后做出相应反应。
// 被观察者(Subject)- 女生
public interface Subject {
void registerObserver(Observer o); // 注册观察者
void removeObserver(Observer o); // 移除观察者
void notifyObservers(); // 通知所有观察者
}
// 观察者(Observer)- 追求者
public interface Observer {
void update(String mood); // 收到状态变化通知
}
// 具体被观察者
public class Girl implements Subject {
private List<Observer> observers = new ArrayList<>();
private String mood;
public void registerObserver(Observer o) {
observers.add(o);
}
public void removeObserver(Observer o) {
observers.remove(o);
}
public void setMood(String mood) {
this.mood = mood;
notifyObservers(); // 状态变化,自动通知
}
public void notifyObservers() {
for (Observer o : observers) {
o.update(mood);
}
}
}
// 具体观察者
public class Pursuer implements Observer {
private String name;
public Pursuer(String name) {
this.name = name;
}
public void update(String mood) {
if ("开心".equals(mood)) {
System.out.println(name + ":她开心,我去约她!");
} else if ("生气".equals(mood)) {
System.out.println(name + ":她生气了,我先躲远点...");
} else if ("无聊".equals(mood)) {
System.out.println(name + ":她无聊了,我陪她聊天!");
}
}
}
// 使用
Girl girl = new Girl();
Pursuer zhangsan = new Pursuer("张三");
Pursuer lisi = new Pursuer("李四");
girl.registerObserver(zhangsan);
girl.registerObserver(lisi);
girl.setMood("开心");
// 输出:
// 张三:她开心,我去约她!
// 李四:她开心,我去约她!
girl.setMood("生气");
// 张三:她生气了,我先躲远点...
// 李四:她生气了,我先躲远点...
7.2 观察者模式的通用结构
Subject(被观察者/主题)
├── 维护观察者列表
├── 注册/注销观察者
└── 状态变化时通知所有观察者
Observer(观察者接口)
└── update() 方法
ConcreteSubject(具体主题)
└── 状态变化,调用notifyObservers()
ConcreteObserver(具体观察者)
└── 实现update(),响应状态变化
7.3 Java内置观察者支持
// Java内置(已过时,了解即可)
java.util.Observable
java.util.Observer
// 推荐使用自定义接口或事件机制
// Swing中的观察者模式
button.addActionListener(e -> {
System.out.println("按钮被点击!");
});
// ActionListener就是观察者,Button是被观察者
7.4 装饰器模式(Decorator Pattern)
场景: 追女生要送礼物,裸礼物太单调,要加包装、加丝带、加贺卡……层层装饰。
// 组件接口
public interface Gift {
String getDescription();
double cost();
}
// 具体组件
public class Flower implements Gift {
public String getDescription() { return "一束鲜花"; }
public double cost() { return 50.0; }
}
public class Chocolate implements Gift {
public String getDescription() { return "一盒巧克力"; }
public double cost() { return 30.0; }
}
// 装饰器基类
public abstract class GiftDecorator implements Gift {
protected Gift gift; // 持有被装饰对象
public GiftDecorator(Gift gift) {
this.gift = gift;
}
}
// 具体装饰器
public class RibbonDecorator extends GiftDecorator {
public RibbonDecorator(Gift gift) { super(gift); }
public String getDescription() {
return gift.getDescription() + ",系着丝带";
}
public double cost() {
return gift.cost() + 10.0; // 丝带加10元
}
}
public class CardDecorator extends GiftDecorator {
public CardDecorator(Gift gift) { super(gift); }
public String getDescription() {
return gift.getDescription() + ",附带贺卡";
}
public double cost() {
return gift.cost() + 5.0;
}
}
public class BoxDecorator extends GiftDecorator {
public BoxDecorator(Gift gift) { super(gift); }
public String getDescription() {
return gift.getDescription() + ",装在礼盒里";
}
public double cost() {
return gift.cost() + 15.0;
}
}
7.5 装饰器的使用
// 送一束花,层层装饰
Gift gift = new Flower();
System.out.println(gift.getDescription() + " ¥" + gift.cost());
// 一束鲜花 ¥50.0
// 加丝带
gift = new RibbonDecorator(gift);
System.out.println(gift.getDescription() + " ¥" + gift.cost());
// 一束鲜花,系着丝带 ¥60.0
// 再加礼盒
gift = new BoxDecorator(gift);
System.out.println(gift.getDescription() + " ¥" + gift.cost());
// 一束鲜花,系着丝带,装在礼盒里 ¥75.0
// 再加贺卡
gift = new CardDecorator(gift);
System.out.println(gift.getDescription() + " ¥" + gift.cost());
// 一束鲜花,系着丝带,装在礼盒里,附带贺卡 ¥80.0
7.6 装饰器 vs 继承
继承方式(爆炸问题):
FlowerWithRibbon
FlowerWithCard
FlowerWithBox
FlowerWithRibbonAndCard
FlowerWithRibbonAndBox
FlowerWithCardAndBox
FlowerWithRibbonAndCardAndBox
... 2^n 个子类!
装饰器方式(组合):
只需 4 个类:Flower + RibbonDecorator + CardDecorator + BoxDecorator
任意组合:new CardDecorator(new RibbonDecorator(new Flower()))
7.7 Java中的装饰器模式
// Java I/O 是装饰器模式的经典实现
InputStream in = new FileInputStream("file.txt");
// 层层装饰
BufferedInputStream bis = new BufferedInputStream(in); // 加缓冲
DataInputStream dis = new DataInputStream(bis); // 加数据类型读取
// 一行代码搞定
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("file.txt")
)
);
// BufferedReader也是装饰器
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("file.txt"), "UTF-8"
)
);
7.8 观察者 + 装饰器:综合场景
// 用观察者追踪送礼物的效果,用装饰器组装礼物
// 女生(被观察者)
Girl girl = new Girl();
// 追求者(观察者)
Pursuer me = new Pursuer("我");
girl.registerObserver(me);
// 组装礼物(装饰器)
Gift gift = new CardDecorator(
new RibbonDecorator(
new BoxDecorator(new Flower())
)
);
// 送出礼物,观察女生反应
girl.setMood("收到礼物很开心!");
// 我:她收到礼物很开心!我要继续努力!
第二部分:JVM从入门到精通
第80节:JVM入门与Class文件格式
80.1 什么是JVM?
Java程序执行过程:
.java源码 → (javac编译) → .class字节码 → (JVM解释/编译执行) → 机器指令
JVM = Java Virtual Machine
它是一个"虚拟的计算机",运行在物理机器之上
它理解的是字节码,而不是Java源码
80.2 JVM的三个特性
- 跨平台:字节码在任何平台的JVM上都能运行(Write Once, Run Anywhere)
- 自动内存管理:垃圾回收(GC)自动清理不再使用的对象
- 字节码指令集:JVM有自己的指令集(200+条指令)
80.3 Class文件结构(十六进制视角)
每个.class文件都以魔数开头:
CA FE BA BE (咖啡宝贝!Java的彩蛋)
完整的Class文件结构:
┌─────────────────────────────────┐
│ magic (4字节) CA FE BA BE │
│ minor_version (2字节) │
│ major_version (2字节) Java版本 │
│ constant_pool_count (2字节) │
│ constant_pool[] 常量池 │
│ access_flags 访问标志 │
│ this_class 当前类索引 │
│ super_class 父类索引 │
│ interfaces_count 接口数量 │
│ interfaces[] 接口索引 │
│ fields_count 字段数量 │
│ fields[] 字段信息 │
│ methods_count 方法数量 │
│ methods[] 方法信息 │
│ attributes_count 属性数量 │
│ attributes[] 属性信息 │
└─────────────────────────────────┘
80.4 用javap查看Class文件
# 编译
javac Hello.java
# 查看class文件详情
javap -verbose Hello.class
# 输出:常量池、方法签名、字节码指令等
# 只看方法
javap -c Hello.class
# 输出:反汇编的字节码指令
80.5 常量池详解
常量池是Class文件中最重要的部分,存储:
- 字面量:字符串、数字常量
- 符号引用:类名、方法名、字段名
public class Hello {
public static final String MSG = "Hello World"; // 存入常量池
public void say() {
System.out.println(MSG); // MSG是对常量池的引用
}
}
第86节:详解Class加载过程
86.1 类的完整生命周期
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
\______链接______/
86.2 类加载(Loading)
将.class文件读入内存,生成Class对象
// 类加载器层次结构
Bootstrap ClassLoader(启动类加载器)
↑ null(没有父加载器)
Extension ClassLoader(扩展类加载器)
↑
Application ClassLoader(应用类加载器)
↑
Custom ClassLoader(自定义类加载器)
双亲委派模型:
加载请求 → 先问父加载器能不能加载
→ 父加载器不行 → 自己再尝试
Bootstrap → rt.jar (Java核心类)
Extension → ext/*.jar (扩展类)
Application → classpath下的类
为什么用双亲委派?
- 防止重复加载(父加载器已加载的,子加载器不会再加载)
- 保护核心API不被篡改(自定义ClassLoader不能替换String.class)
86.3 验证(Verification)
确保字节码符合JVM规范,不会危害JVM:
- 文件格式验证(魔数、版本号)
- 元数据验证(语义分析)
- 字节码验证(指令流分析)
- 符号引用验证
86.4 准备(Preparation)
为类的静态变量分配内存,并设置零值:
public class Test {
static int a = 10; // 准备阶段:a = 0(零值,不是10!)
static final int b = 10; // 准备阶段:b = 10(final常量直接赋值)
// 10这个值在初始化阶段才赋给a
}
86.5 解析(Resolution)
将符号引用替换为直接引用(内存地址)
86.6 初始化(Initialization)⭐
执行类中的静态代码块和静态变量赋值:
public class Init {
static int x = 10; // 初始化阶段执行
static {
System.out.println("静态代码块"); // 初始化阶段执行
x = 20;
}
}
触发初始化的时机:
new创建对象- 访问类的静态字段(final常量除外)
- 调用类的静态方法
- 反射调用
- 子类初始化时,父类先初始化
// 不会触发初始化的情况:
// 1. 通过子类引用父类的静态字段
// 2. 定义数组:Init[] arr = new Init[10];
// 3. 访问final static常量(编译期已确定)
86.7 打破双亲委派模型
// 自定义类加载器
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 1. 读取.class文件的字节数组
byte[] bytes = loadClassData(name);
// 2. 调用defineClass生成Class对象
return defineClass(name, bytes, 0, bytes.length);
}
private byte[] loadClassData(String name) {
// 从文件、网络等地方读取字节码
// ...
}
}
// Tomcat打破双亲委派:每个Web应用有独立的类加载器
// 同一个类名,不同Web应用可以加载不同版本
第93节:Java内存模型
93.1 JMM(Java Memory Model)是什么?
JMM是Java对多线程内存访问的规范,解决多线程下的可见性、原子性、有序性问题。
JMM规定:
- 所有变量都存储在主内存(Main Memory)
- 每个线程有自己的工作内存(Working Memory)
- 线程只能操作工作内存中的变量副本
93.2 三大问题
主内存(共享)
↓↑读写
线程1工作内存 ←→ 线程2工作内存 ←→ 线程3工作内存
① 可见性问题
// 线程1修改了x,线程2看不到(因为在工作内存里)
int x = 0;
// 线程1:x = 10; (修改的是工作内存,主内存可能还没更新)
// 线程2:读x (读的是主内存,还是0!)
② 原子性问题
// i++ 不是原子操作!
int i = 0;
// 线程1和线程2同时执行i++,结果可能是1而不是2
// 因为i++包含:读取i → 加1 → 写回i(三步,可能被打断)
③ 有序性问题(指令重排序)
// 编译器/CPU可能重排指令以优化性能
int a = 1; // 可能被重排到b=2后面
int b = 2;
93.3 解决方案
// 解决可见性:volatile
volatile boolean running = true;
// 任何线程修改running,立即对其他线程可见
// 解决原子性:synchronized / Lock / AtomicInteger
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子操作
// 解决有序性:volatile / happens-before规则
volatile int flag = 0;
// volatile写之前的操作,在volatile读之后都可见
93.4 volatile详解
public class VolatileTest {
volatile int x = 0;
// volatile保证:
// 1. 可见性:修改立即同步到主内存
// 2. 禁止指令重排序
// 但不保证原子性!
public void increment() {
x++; // 仍然不是原子的!多线程下有并发问题
}
}
// volatile适用场景:
// 1. 状态标志位
volatile boolean shutdown = false;
// 2. 双重检查锁定(DCL单例)
private static volatile Singleton instance;
93.5 synchronized的内存语义
synchronized(obj) {
// 进入同步块:清空工作内存,从主内存重新读取
// 退出同步块:把工作内存的值刷新到主内存
// 因此synchronized同时解决可见性和原子性
}
第97节:内存屏障与JVM指令
97.1 内存屏障(Memory Barrier)
内存屏障是CPU/JVM插入的特殊指令,用于禁止特定类型的指令重排序。
// volatile的实现原理:内存屏障
volatile int x = 10;
// JVM在volatile写后插入StoreStore + StoreLoad屏障
// JVM在volatile读后插入LoadLoad + LoadStore屏障
// 效果:
// 写x之前的所有操作,对其他线程立即可见
// 读x之后的所有操作,都能看到x之前的所有修改
97.2 happens-before原则
// 如果操作A happens-before 操作B,
// 那么A的结果对B一定可见
// 8条happens-before规则:
1. 程序顺序规则:同一线程中,前面的操作happens-before后面的
2. 监视器锁规则:解锁 happens-before 加锁(同一个锁)
3. volatile变量规则:写 happens-before 读
4. 线程启动规则:Thread.start() happens-before 线程中的操作
5. 线程终止规则:线程操作 happens-before Thread.join()
6. 中断规则:interrupt() happens-before 被中断线程检测到中断
7. 终结器规则:构造函数 happens-before finalize()
8. 传递性:A→B,B→C,则A→C
97.3 字节码指令示例
public int add(int a, int b) {
return a + b;
}
// 对应的字节码:
// iload_1 ← 把参数a压入操作数栈
// iload_2 ← 把参数b压入操作数栈
// iadd ← 弹出栈顶两个值相加,结果入栈
// ireturn ← 弹出栈顶值作为返回值
// 常用字节码指令:
// 加载指令:iload, lload, fload, dload, aload
// 存储指令:istore, lstore, fstore, dstore, astore
// 常量入栈:iconst, bipush, sipush, ldc
// 算术运算:iadd, isub, imul, idiv
// 类型转换:i2l, i2f, i2d
// 对象操作:new, getfield, putfield, invokevirtual
// 控制转移:ifeq, ifne, if_icmpge, goto
// 方法调用:invokevirtual, invokespecial, invokestatic, invokeinterface
第102节:Java运行时数据区与常用指令
102.1 JVM运行时数据区全景图
JVM运行时数据区
├── 线程私有区(每个线程独立拥有)
│ ├── 程序计数器(PC Register)
│ ├── 虚拟机栈(VM Stack)
│ │ └── 栈帧(Frame)
│ │ ├── 局部变量表
│ │ ├── 操作数栈
│ │ ├── 动态链接
│ │ └── 返回地址
│ └── 本地方法栈(Native Method Stack)
│
└── 线程共享区(所有线程共享)
├── 堆(Heap)
│ ├── 年轻代(Young Generation)
│ │ ├── Eden区(80%)
│ │ ├── Survivor From(10%)
│ │ └── Survivor To(10%)
│ └── 老年代(Old Generation)
└── 方法区(Method Area / MetaSpace)
├── 类信息
├── 常量池
├── 静态变量
└── JIT编译后的代码
102.2 各区域详解
① 程序计数器(PC Register)
- 记录当前线程正在执行哪条字节码指令
- 线程切换后能恢复到正确位置
- 占用内存极小,不会发生OOM
② 虚拟机栈(VM Stack)
// 每调用一个方法,就压入一个栈帧
public void methodA() {
int x = 10; // x存在局部变量表
methodB(); // 新的栈帧压入
}
public void methodB() {
int y = 20; // y存在methodB栈帧的局部变量表
// methodB执行完,栈帧弹出,y自动消失
}
// StackOverflowError:递归太深,栈帧太多
// 可以通过-Xss增大栈大小
③ 堆(Heap)
// 所有new出来的对象都存在堆中
Object obj = new Object(); // obj引用在栈上,对象本身在堆上
// 堆是GC的主要区域
// OutOfMemoryError:堆满了,GC也回收不了
④ 方法区(Method Area)
- 存储类的元数据(类名、方法签名等)
- Java 8之前:永久代(PermGen)
- Java 8之后:元空间(MetaSpace),使用本地内存
// 加载10000个类,方法区就会存储这10000个类的元数据
// 方法区满了 → OutOfMemoryError: Metaspace
102.3 对象在内存中的布局
public class Person {
private int age; // 4字节
private String name; // 引用(4或8字节)
}
// 堆中的对象布局:
// ┌─────────────────┐
// │ 对象头 Mark Word │ 8字节(32位JVM)
// │ (hashCode/GC信息) │
// ├─────────────────┤
// │ 类型指针 │ 4字节(指向类元数据)
// ├─────────────────┤
// │ age (int) │ 4字节
// │ name (reference) │ 4字节
// ├─────────────────┤
// │ 对齐填充 │ 补齐到8的倍数
// └─────────────────┘
第110节:JVM调优必备理论-GC
110.1 什么是GC?
GC(Garbage Collection)= 自动回收不再使用的对象,释放内存。
Object obj = new Object();
obj = null; // 对象变成垃圾,等待GC回收
// JVM自动检测这个对象没有任何引用指向它 → 回收
110.2 如何判断对象是否可以回收?
方法一:引用计数法(有缺陷,JVM不用)
// 每个对象维护一个计数器
// 被引用+1,引用断开-1,减到0就回收
// ❌ 循环引用无法回收
Object a = new Object();
Object b = new Object();
a.ref = b;
b.ref = a;
// a和b互相引用,计数都不为0,永远无法回收
方法二:可达性分析(JVM使用的方式)⭐
GC Roots(根对象)
├── 虚拟机栈中的引用(方法参数、局部变量)
├── 静态变量引用的对象
├── 常量引用的对象
└── JNI本地引用
从GC Roots出发,能到达的对象都是"活着的"
到达不了的对象 = 垃圾,可以回收
public void test() {
Object a = new Object(); // a是GC Root(局部变量)
Object b = new Object(); // b是GC Root
a = null; // a指向的对象无法从GC Root到达 → 可回收
// b指向的对象仍然可达 → 不回收
}
110.3 GC的四种引用类型
// 1. 强引用(Strong Reference)
Object obj = new Object(); // 只要obj在,对象永远不会被回收
// 2. 软引用(Soft Reference)
SoftReference<Object> softRef = new SoftReference<>(new Object());
// 内存不足时才回收,适合做缓存
// 3. 弱引用(Weak Reference)
WeakReference<Object> weakRef = new WeakReference<>(new Object());
// 下次GC就回收,不管内存够不够
// ThreadLocal就是弱引用实现
// 4. 虚引用(Phantom Reference)
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
// 随时可能被回收,主要用于跟踪对象被回收的时机
110.4 分代收集理论
弱分代假说:大多数对象都是朝生夕灭的
→ 新创建的对象放在年轻代
强分代假说:熬过越多次GC的对象,越难消亡
→ 存活时间长的对象移入老年代
跨引用假说:跨代引用只占极少数
→ 老年代引用年轻代的情况很少(用记忆集解决)
110.5 年轻代GC(Minor GC)
对象创建流程:
1. 新对象 → 优先放入Eden区
2. Eden满了 → 触发Minor GC
3. 存活对象 → 移入Survivor From(年龄+1)
4. 年龄达到阈值(默认15) → 晋升到老年代
Eden:S0:S1 = 8:1:1(默认比例)
110.6 老年代GC(Full GC / Major GC)
触发Full GC的情况:
1. 老年代空间不足
2. 方法区空间不足
3. System.gc()(建议,不一定执行)
4. Minor GC后,晋升老年代的对象超过老年代剩余空间
5. CMS的concurrent mode failure
Full GC代价大:
- 整个堆和方法区都要扫描
- 应用完全暂停(Stop-The-World)
第114节:ps+po调优实战
114.1 垃圾收集器概述
┌─────────────────────────────────────────┐
│ 垃圾收集器全景 │
├───────────────┬─────────────────────────┤
│ 年轻代 │ 老年代 │
├───────────────┼─────────────────────────┤
│ Serial │ Serial Old │
│ ParNew │ Parallel Old │
│ Parallel Scav │ CMS │
│ G1(年轻+老年)│ G1(年轻+老年) │
│ ZGC │ ZGC │
└───────────────┴─────────────────────────┘
114.2 经典组合:ParNew + CMS
# ParNew(年轻代,并行) + CMS(老年代,并发)
# 这是最常用的服务端配置(JDK8时代)
-XX:+UseParNewGC # 使用ParNew年轻代收集器
-XX:+UseConcMarkSweepGC # 使用CMS老年代收集器
-XX:NewRatio=2 # 年轻代:老年代 = 1:2
-XX:SurvivorRatio=8 # Eden:S0:S1 = 8:1:1
114.3 Parallel Scavenge + Parallel Old
# 吞吐量优先的选择
# 适合后台计算任务,不关注响应时间
-XX:+UseParallelGC # 年轻代
-XX:+UseParallelOldGC # 老年代
-XX:MaxGCPauseMillis=100 # 目标最大暂停时间
-XX:GCTimeRatio=19 # 吞吐量目标:1/(1+19)=95%
114.4 调优参数实战
# 完整的生产JVM启动参数示例
java -Xms2g -Xmx2g \ # 堆大小(初始=最大,避免扩容开销)
-Xmn800m \ # 年轻代大小
-XX:MetaspaceSize=256m \ # 元空间初始大小
-XX:MaxMetaspaceSize=512m \
-XX:+UseG1GC \ # 使用G1垃圾收集器
-XX:MaxGCPauseMillis=200 \ # 目标最大GC停顿时间
-XX:+HeapDumpOnOutOfMemoryError \ # OOM时自动dump堆
-XX:HeapDumpPath=/logs/heapdump.hprof \
MyApp.jar
114.5 调优思路(ps = 性能调优)
1. 确定目标:
- 响应时间目标(GC停顿不超过X ms)
- 吞吐量目标(GC时间占比不超过Y%)
2. 监控现状:
- jstat -gcutil <pid> 1000 # 每秒打印GC统计
- jmap -histo <pid> # 查看对象直方图
- jstack <pid> # 查看线程堆栈
3. 分析问题:
- Minor GC太频繁 → 年轻代太小,或对象创建太快
- Full GC频繁 → 老年代太小,或有内存泄漏
- GC停顿太长 → 换用并发收集器(G1/ZGC)
4. 调整参数并验证
第119/124节:JVM实战调优
119.1 G1垃圾收集器 ⭐(主流选择)
G1(Garbage First)特点:
1. 把堆分成多个Region(默认2048个)
2. 每个Region可以是Eden、Survivor或Old
3. 优先回收"价值最大"的Region(垃圾最多的)
4. 支持可预测的停顿时间模型
# G1推荐配置
java -XX:+UseG1GC \
-Xms4g -Xmx4g \
-XX:MaxGCPauseMillis=200 \ # 停顿时间目标
-XX:G1HeapRegionSize=4m \ # Region大小(1-32MB,2的幂次)
-XX:InitiatingHeapOccupancyPercent=45 \ # 触发并发标记的堆占用率
-XX:G1NewSizePercent=20 \ # 年轻代最小占比
-XX:G1MaxNewSizePercent=40 # 年轻代最大占比
MyApp.jar
119.2 G1的GC过程
Young GC(年轻代回收):
1. 扫描所有Eden和Survivor Region
2. 复制存活对象到新的Region
3. 清空旧Region
4. 更新引用
Mixed GC(混合回收,G1独有):
1. 回收所有年轻代Region
2. 同时回收部分老年代Region(选择垃圾最多的)
3. 通过多次Mixed GC逐步清理老年代
Full GC(最后手段):
- 当Mixed GC来不及清理时触发
- 整个堆单线程扫描(很慢!)
- 目标:通过调优避免Full GC
119.3 常见调优场景
场景1:Minor GC太频繁
# 现象:每秒都在Minor GC
# 原因:年轻代太小,或对象创建太快
# 解决:增大年轻代
-XX:G1NewSizePercent=30 # 提高年轻代占比
场景2:Full GC频繁
# 现象:每隔几分钟就Full GC
# 原因:老年代空间不足,有内存泄漏
# 排查:
jmap -histo:live <pid> # 查看哪些对象占内存最多
jmap -dump:live,format=b,file=heap.hprof <pid> # dump堆
# 用MAT/Eclipse Memory Analyzer分析dump文件
场景3:GC停顿时间过长
# 现象:接口响应偶尔超过1秒
# 原因:GC停顿导致
# 解决:
-XX:MaxGCPauseMillis=100 # 降低停顿目标
# 或换用ZGC(JDK11+)
-XX:+UseZGC # 停顿<10ms!
119.4 内存泄漏排查
// 常见内存泄漏场景:
// 1. 静态集合持有大量对象
public class Cache {
static Map<String, Object> map = new HashMap<>(); // 永不释放!
}
// 2. 未关闭的资源
InputStream in = new FileInputStream("file.txt");
// 忘记close(),in一直被引用
// 3. ThreadLocal未清理
ThreadLocal<User> tl = new ThreadLocal<>();
tl.set(new User());
// 线程池中,线程不销毁,User对象永远不会被回收
// 解决:用完后 tl.remove();
// 4. 内部类持有外部类引用
public class Outer {
class Inner {
// 隐式持有Outer.this引用
// 如果Inner被长期持有,Outer也无法回收
}
}
第128节:垃圾回收算法串讲
128.1 四大垃圾回收算法
① 标记-清除(Mark-Sweep)
② 复制算法(Copying)
③ 标记-整理(Mark-Compact)
④ 分代收集(Generational)
128.2 标记-清除(Mark-Sweep)
第一步:从GC Roots出发,标记所有存活的对象
第二步:清除所有未被标记的对象
优点:不需要移动对象,速度快
缺点:产生内存碎片(不连续的空闲空间)
标记前:[A][B][ ][C][ ][D][ ]
清除后:[A][ ][ ][C][ ][ ][ ]
↑内存碎片,无法分配大对象
128.3 复制算法(Copying)
把堆分成两块,只用其中一块
GC时,把存活对象复制到另一块,清空原来的块
优点:不产生碎片,分配效率高(指针碰撞)
缺点:可用空间减半,复制成本大
From区:[A][B][C][ ][ ] To区:[ ][ ][ ][ ][ ]
GC后:
From区:[ ][ ][ ][ ][ ] To区:[A][B][C][ ][ ]
交换From和To
适用:年轻代(对象朝生夕死,存活率低,复制成本小)
128.4 标记-整理(Mark-Compact)
第一步:标记存活对象
第二步:把存活对象向一端移动(整理)
第三步:清除边界外的所有空间
优点:不产生碎片,空间利用率高
缺点:移动对象需要更新引用,速度慢
标记后:[A✓][ ][B✓][ ][C✓][ ]
整理后:[A][B][C][ ][ ][ ]
↑连续空间,无碎片
适用:老年代(对象存活率高,不适合复制算法)
128.5 分代收集(Generational Collection)
现代JVM都采用分代收集,结合以上算法:
年轻代:复制算法(Minor GC)
- 对象存活率低(大多数对象朝生夕死)
- 复制成本小(只需复制少量存活对象)
老年代:标记-清除 或 标记-整理(Full GC)
- 对象存活率高
- 不适合复制算法(复制成本太大)
128.6 各收集器使用的算法
| 收集器 | 区域 | 算法 |
|---|---|---|
| Serial | 年轻代 | 复制 |
| Serial Old | 老年代 | 标记-整理 |
| ParNew | 年轻代 | 复制(并行) |
| Parallel Scavenge | 年轻代 | 复制(并行) |
| Parallel Old | 老年代 | 标记-整理(并行) |
| CMS | 老年代 | 标记-清除(并发) |
| G1 | 整体 | 分Region,复制+整理 |
| ZGC | 整体 | 着色指针+读屏障 |
第132节:JVM常见参数总结
132.1 堆内存参数
-Xms2g # 初始堆大小(建议与-Xmx相同,避免扩容开销)
-Xmx2g # 最大堆大小
-Xmn800m # 年轻代大小(G1不需要设置,自动调整)
-XX:NewRatio=2 # 老年代:年轻代 = 2:1
-XX:SurvivorRatio=8 # Eden:S0:S1 = 8:1:1
132.2 方法区参数
# JDK8之前(永久代)
-XX:PermSize=128m
-XX:MaxPermSize=256m
# JDK8+(元空间)
-XX:MetaspaceSize=256m # 初始大小
-XX:MaxMetaspaceSize=512m # 最大大小(不设则无上限)
132.3 GC相关参数
# 选择收集器
-XX:+UseSerialGC # Serial + Serial Old
-XX:+UseParallelGC # Parallel Scavenge
-XX:+UseParallelOldGC # Parallel Old
-XX:+UseConcMarkSweepGC # CMS
-XX:+UseParNewGC # ParNew
-XX:+UseG1GC # G1(推荐JDK8+)
-XX:+UseZGC # ZGC(JDK11+,超低延迟)
# G1参数
-XX:MaxGCPauseMillis=200 # 目标停顿时间
-XX:G1HeapRegionSize=4m # Region大小
-XX:InitiatingHeapOccupancyPercent=45 # 并发标记触发阈值
# CMS参数
-XX:CMSInitiatingOccupancyFraction=75 # CMS触发阈值
-XX:+UseCMSInitiatingOccupancyOnly # 只在达到阈值时触发
-XX:+CMSParallelRemarkEnabled # 并行重标记
132.4 调试与监控参数
# GC日志(必须开!)
-XX:+PrintGCDetails # 打印GC详情
-XX:+PrintGCDateStamps # 打印GC时间戳
-Xloggc:/logs/gc.log # GC日志输出到文件
# JDK9+
-Xlog:gc*:file=/logs/gc.log
# OOM时自动dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/logs/heapdump.hprof
# 栈大小
-Xss256k # 每个线程的栈大小
# 其他
-XX:+PrintFlagsFinal # 查看所有JVM参数最终值
-XX:+PrintCommandLineFlags # 打印命令行参数
132.5 常用JVM工具命令
# jps - 查看Java进程
jps -l
# jstat - 统计GC信息
jstat -gcutil <pid> 1000 # 每秒打印一次
jstat -gc <pid> 1000 # 更详细的GC统计
# jmap - 内存映射
jmap -heap <pid> # 查看堆配置
jmap -histo <pid> # 对象直方图(按数量排序)
jmap -histo:live <pid> # 只看存活对象
jmap -dump:format=b,file=heap.hprof <pid> # dump堆
# jstack - 线程堆栈
jstack <pid> # 查看所有线程状态
jstack <pid> | grep BLOCKED # 查找阻塞线程
# jinfo - JVM配置信息
jinfo -flags <pid> # 查看JVM参数
jinfo -sysprops <pid> # 查看系统属性
# jcmd(JDK7+,推荐替代jmap/jstack)
jcmd <pid> GC.heap_dump heap.hprof
jcmd <pid> Thread.print
jcmd <pid> VM.flags
132.6 调优参数速查表
| 场景 | 推荐参数 |
|---|---|
| Web应用(低延迟) | -XX:+UseG1GC -XX:MaxGCPauseMillis=200 |
| 批处理(高吞吐) | -XX:+UseParallelGC -XX:MaxGCPauseMillis=1000 |
| 微服务(超低延迟) | -XX:+UseZGC(JDK11+) |
| 内存受限(<100MB) | -XX:+UseSerialGC |
| 排查OOM | -XX:+HeapDumpOnOutOfMemoryError |
| 生产环境标配 | -Xlog:gc*:file=gc.log -XX:+HeapDumpOnOutOfMemoryError |
附:设计模式六大原则速记
| 原则 | 英文 | 核心思想 |
|---|---|---|
| 单一职责 | SRP | 一个类只做一件事 |
| 开闭原则 | OCP | 对扩展开放,对修改关闭 |
| 里氏替换 | LSP | 子类能完全替代父类 |
| 接口隔离 | ISP | 接口要小而精 |
| 依赖倒置 | DIP | 依赖抽象,不依赖具体 |
| 迪米特法则 | LoD | 最少知识原则 |
附:JVM调优思维导图
JVM调优
├── 1. 确定目标
│ ├── 延迟目标(GC停顿 < X ms)
│ └── 吞吐目标(GC占比 < Y%)
├── 2. 选择收集器
│ ├── JDK8:G1(首选)或 CMS+ParNew
│ ├── JDK11+:ZGC(超低延迟)
│ └── JDK17+:ZGC 或 Shenandoah
├── 3. 设置堆大小
│ ├── -Xms = -Xmx(避免扩容)
│ └── 根据业务内存使用量 × 1.5~2
├── 4. 开启GC日志
│ └── 必须有,否则无法分析
├── 5. 压测验证
│ └── 观察GC频率、停顿时间、吞吐量
└── 6. 问题排查
├── Minor GC频繁 → 增大年轻代/减少对象创建
├── Full GC频繁 → 排查内存泄漏/增大老年代
└── 停顿过长 → 换收集器/降低停顿目标
📚 本文档覆盖马士兵设计模式全套课程(Thinking in OO + 工厂 + Strategy + Iterator + 动态代理 + 责任链 + 追美眉系列)及JVM完整课程(Class文件 + 类加载 + 内存模型 + 运行时数据区 + GC算法 + 调优实战)
建议结合视频学习,先看文档理解概念,再看视频看老师现场编码演示

浙公网安备 33010602011771号