几十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)

  1. 找名词 → 这些是候选类
  2. 找动词 → 这些是方法
  3. 找形容词 → 这些是属性

例题:设计一个"学生选课系统"

  • 名词:学生、课程、教师、成绩 → 候选类
  • 动词:选课、退课、打分、查询 → 方法
  • 形容词:学生姓名、课程学分 → 属性

设计阶段(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的三个特性

  1. 跨平台:字节码在任何平台的JVM上都能运行(Write Once, Run Anywhere)
  2. 自动内存管理:垃圾回收(GC)自动清理不再使用的对象
  3. 字节码指令集: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;
    }
}

触发初始化的时机:

  1. new 创建对象
  2. 访问类的静态字段(final常量除外)
  3. 调用类的静态方法
  4. 反射调用
  5. 子类初始化时,父类先初始化
// 不会触发初始化的情况:
// 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算法 + 调优实战)

建议结合视频学习,先看文档理解概念,再看视频看老师现场编码演示

posted @ 2026-09-23 13:57  始计天策  阅读(2)  评论(0)    收藏  举报