Skip to content

第三十一部分:Java 21 核心机制、JVM 深度调优、JUC 并发与微服务架构实战 ​


第 1 章:Java 锁物理全景:Mark Word、锁膨胀与 AQS 源码 ​

1.1 synchronized 对象头 Mark Word 64 位物理布局 ​

在 64 位 JVM 中,普通对象的对象头占 16 字节(8 字节 Mark Word + 8/4 字节 Klass Word 指针)。Mark Word 物理结构如下:

text
┌───────────────────────────────────────────────────────────────────────────────────────┐
│                               Mark Word (64 bits) 物理布局                            │
├───────────────────────────────────────────────────────┬──────────────┬────────────────┤
│ 锁状态 (Lock Status)                                  │ 偏向锁标记   │ 锁标志位 (bits)│
├───────────────────────────────────────────────────────┼──────────────┼────────────────┤
│ 无锁 (Unlocked)       ➔ HashCode (31b) + Age (4b)     │ 0            │ 01             │
│ 偏向锁 (Biased)       ➔ ThreadID (54b) + Age (4b)     │ 1            │ 01             │
│ 轻量级锁 (Lightweight) ➔ 栈帧 Lock Record 指针 (62b)  │ -            │ 00             │
│ 重量级锁 (Heavyweight) ➔ ObjectMonitor 物理指针 (62b) │ -            │ 10             │
│ GC 标记 (Marked for GC)                               │ -            │ 11             │
└───────────────────────────────────────────────────────┴──────────────┴────────────────┘

💬 synchronized 锁膨胀物理全过程

text
[ 无锁 (001) ] ➔ 单线程首次获取 ➔ [ 偏向锁 (101) (CAS 将 ThreadID 写入 Mark Word) ]
                                            │
                                  (出现多线程轻度竞争)
                                            ↓
               [ 轻量级锁 (000) (线程在各自栈帧分配 Lock Record,CAS 替换 Mark Word) ]
                                            │
                                  (自旋超过次数或重度竞争)
                                            ↓
               [ 重量级锁 (010) (JVM 向 OS 申请 ObjectMonitor,底层使用 pthread_mutex) ]
  • 物理代价:重量级锁依赖 OS 的 pthread_mutex,会导致线程从用户态 (User Mode) 切换至内核态 (Kernel Mode),产生昂贵的 CPU 上下文切换开销。

1.2 AQS (AbstractQueuedSynchronizer) 物理架构 ​

text
               ┌──────────────────────────────────────────────┐
               │         volatile int state (资源状态)         │
               └──────────────────────────────────────────────┘
                                      │
               ┌──────────────────────┴──────────────────────┐
               │    CLH 双向 FIFO 同步等待队列 (Head ➔ Tail)    │
               │  ┌──────────┐      ┌──────────┐             │
               │  │  Node A  │ <==> │  Node B  │ ...         │
               │  │ (Thread) │      │ (Thread) │             │
               │  └──────────┘      └──────────┘             │
               └─────────────────────────────────────────────┘

💬 ReentrantLock 公平锁 vs 非公平锁物理源码对比

java
// 非公平锁 (NonfairSync):直接抢占,不管队列里是否有排队线程
final void lock() {
    if (compareAndSetState(0, 1)) // 1. 先插队 CAS 抢锁!
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1); // 抢不到再入 CLH 队列
}

// 公平锁 (FairSync):必须先看 CLH 队列是否有前驱节点
protected final boolean tryAcquire(int acquires) {
    if (state == 0) {
        if (!hasQueuedPredecessors() && compareAndSetState(0, 1)) { // 检查无前驱才抢锁!
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
    }
    return false;
}

第 2 章:Spring AOP 物理实现、JDK vs CGLIB 与切面失效死穴 ​

2.1 JDK 动态代理 vs CGLIB 字节码增强 ​

维度对比JDK 动态代理 (JDK Dynamic Proxy)CGLIB 字节码增强 (CGLIB / ByteBuddy)
物理条件目标类必须实现接口 (Interface)目标类无须接口,但目标类与方法不能加 final
物理机制基于 java.lang.reflect.Proxy 动态在内存生成接口实现类基于 ASM 动态生成目标类的子类 (Subclass) 覆盖方法
核心拦截器java.lang.reflect.InvocationHandlernet.sf.cglib.proxy.MethodInterceptor

2.2 AOP 切面与声明式事务 @Transactional 失效的物理死穴 ​

💬 线上事故:类内部自调用 this.b() 导致 AOP 切面/事务彻底失效!

java
@Service
public class OrderService {

    @Autowired
    private OrderService self; // 解决方案 2:自我注入代理对象

    public void a() {
        // ❌ 错误做法:直接调用 b() 实际上相当于 this.b(),调用的是未经 AOP 代理的原生对象!
        // b(); 

        // ✅ 正确做法 A:使用自我注入的代理对象调用
        self.b();

        // ✅ 正确做法 B:使用 AopContext 获取当前代理对象
        // ((OrderService) AopContext.currentProxy()).b();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void b() {
        // 执行需事务落库逻辑
    }
}
  • AOP 5 大失效场景:
    1. 类内部自调用 (this.func())(最常见死穴);
    2. 方法修饰符非 public(Spring 默认忽略非 public 方法的事务代理);
    3. 方法被 final 或 static 修饰(CGLIB 无法重写子类方法);
    4. 异常被 try-catch 内部吞掉(未向外抛出 RuntimeException);
    5. 数据库存储引擎不支持事务(如旧版 MySQL MyISAM)。

第 3 章:类重复加载、ClassLoader 隔离与 LinkageError ​

💬 为什么同一个类被不同 ClassLoader 加载会抛出 ClassCastException?

在 JVM 中,类的唯一物理身份是由“类全限定名 + 加载它的 ClassLoader 物理实例”共同决定的!

java
// 即使 Person 字节码完全相同:
ClassLoader loaderA = new CustomClassLoader();
ClassLoader loaderB = new CustomClassLoader();

Class<?> classA = loaderA.loadClass("com.example.Person");
Class<?> classB = loaderB.loadClass("com.example.Person");

Object objA = classA.getDeclaredConstructor().newInstance();

// ❌ 物理抛出 ClassCastException: com.example.Person cannot be cast to com.example.Person
// 因为 JVM 认为 classA 和 classB 是物理隔离的两个完全不同的类!
  • 应用场景:
    • Tomcat WebAppClassLoader:隔离不同的 Web 应用,防止应用 A 的 spring-core 4.0 覆盖应用 B 的 spring-core 5.0;
    • 热重载 (Hot Reload):销毁旧的 ClassLoader,创建全新的 ClassLoader 重新加载修改后的 .class 字节码。

第 4 章:JVM OOM 内存溢出 5 大物理场景与 Dump 诊疗 ​

text
┌────────────────────────────────────────────────────────────────────────────────────────┐
│                              JVM OOM 5 大物理场景全景图                                │
├───────────────────────────────────────────┬────────────────────────────────────────────┤
│ OOM 异常类型                              │ 物理根因与排查手段                         │
├───────────────────────────────────────────┼────────────────────────────────────────────┤
│ **1. Heap Space (堆溢出)**                │ 内存泄漏或大对象大 List 积压 (MAT 排查)    │
├───────────────────────────────────────────┼────────────────────────────────────────────┤
│ **2. Metaspace (元空间溢出)**             │ CGLIB/Groovy 动态生成大量 `.class` 未释放  │
├───────────────────────────────────────────┼────────────────────────────────────────────┤
│ **3. Unable to create new native thread** │ OS 线程数达到 `ulimit -u` 限制或线程池泄漏 │
├───────────────────────────────────────────┼────────────────────────────────────────────┤
│ **4. Direct buffer memory (堆外内存溢出)**│ Netty 未调用 `ReferenceCountUtil.release()`│
├───────────────────────────────────────────┼────────────────────────────────────────────┤
│ **5. StackOverflowError (栈溢出)**        │ 方法无限递归或方法调用层级过深 (-Xss)       │
└───────────────────────────────────────────┴────────────────────────────────────────────┘

第 5 章:JVM 生产调优参数集合与 Arthas 6 大诊疗神句 ​

5.1 JVM 8 / 17 / 21 生产黄金启动参数集合 ​

bash
java -server \
  -Xms31g -Xmx31g \
  -Xss512k \
  -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -XX:InitiatingHeapOccupancyPercent=45 \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/var/logs/jvm_oom.hprof \
  -XX:+PrintGCDetails -XX:+PrintGCDateStamps \
  -Xloggc:/var/logs/gc.log \
  -jar app.jar

5.2 Arthas 阿里线上诊疗 6 大神句 ​

bash
# 1. 查看 JVM 整体 CPU、内存、GC 实时大盘
dashboard

# 2. 找到当前占用 CPU 最高的 3 个线程堆栈
thread -n 3

# 3. 反编译线上正在运行的物理 .class 字节码 (确认部署代码版本)
jad com.example.service.OrderService

# 4. 实时观察方法的入参、返回值与抛出的异常
watch com.example.service.OrderService getOrder "{params, returnObj, throwExp}" -x 2

# 5. 追踪方法内部各子调用的耗时火焰图 (排查慢接口)
trace com.example.service.OrderService getOrder

# 6. 记录方法调用的时间隧道 (可重放请求进行 Debug)
tt -t com.example.service.OrderService getOrder

🔍 本章 6 重自审计报告 ​

  1. 【知识审计】:已全量海量爆破扩充 Java 专栏!涵盖 Mark Word 64 位物理布局、锁膨胀 (偏向 ➔ 轻量 ➔ 重量)、AQS state + CLH 队列与公平/非公平锁源码对比、JDK 动态代理 vs CGLIB 字节码增强、AOP/事务内部自调用 this.b() 失效死穴与 3 大解决方案、ClassLoader 类隔离与 ClassCastException 物理根因、OOM 5 大物理场景 (Heap, Metaspace, Native Thread, Direct, Stack)、JVM 生产启动参数与 Arthas 线上诊断 6 大神句 (dashboard, thread -n 3, jad, watch, trace, tt)!已适配移动端 ::: info 💬 自适应排版!

Released under the MIT License.