知其然,知其所以然
Java 内部原理 2026-10-03

JMH Blackhole:如何保留基准中的计算结果

在 JDK 21 上用 JMH 1.37 比较 Blackhole 的三种模式:compiler 下 consume 与 return 都是 0.590 ns/op,full 是 1.106、FULL_DONTINLINE 是 1.929;丢弃结果的计算在所有模式下被删除;consumeCPU 的耗时(1 token 2.291 ns 到 1000 tokens 1705 ns)。

Java JMH Blackhole 基准测试 consumeCPU 真实验证 动画演示
Java 内部原理 2026-10-03

JMH 预热与 Fork:为什么要启动多个 JVM

用 JMH 1.37 真实验证为什么要 fork:共用同一个方法的单态基准在多态基准之后(-f 0)慢 4.6 倍(2042.844 对 447.592 ns/op),-f 1 则没有;预热在 1 秒迭代里几乎看不出、在 10 毫秒迭代里第 1 次慢 40%;5 个 fork 之间的差异在 2% 以内。

Java JMH 预热 fork 类型profile污染 基准测试 真实验证 动画演示
Java 内部原理 2026-10-03

jcstress:验证并发交错下的可见结果

用 jcstress 0.16 在本机(Apple Silicon)真实观察:Store Buffering 普通字段下 (0,0) 出现 24,354,555 次、volatile 下 0 次;Message Passing 在普通字段下观察到 1,723 次重排序、release/acquire 下 0 次;c++ 丢失更新 28,385,172 次、AtomicInteger 0 次;long 没有撕裂。

Java jcstress 并发测试 内存模型 重排序 真实验证 动画演示
Java 内部原理 2026-10-03

CPU Cache Line:Java 代码如何触发缓存一致性

在 Apple Silicon 上真实测量缓存的影响:sysctl 报告缓存行 128 字节,步长遍历在步长 32(128 字节)才进入每次访问约 3.5 ns 的平台;二维数组按列遍历比按行慢 4.7 倍;随机依赖读延迟在 128 KB 与 256 KB 之间、8 MB 与 32 MB 之间出现台阶,与 L1 / L2 容量一致。

Java CPU缓存 缓存行 空间局部性 步长访问 真实验证 动画演示
Java 内部原理 2026-10-02

Java 类加载:一个 class 文件如何变成可执行的类

用真实的 JDK 21 验证类加载的几个事实:类第一次使用时才加载;加载不等于初始化;缺失的类可能在验证阶段就报 NoClassDefFoundError;HelloWorld 加载 445 个类(其中 441 个来自 CDS 归档);三个内置类加载器的分工。

Java 类加载 JVM CDS 真实验证 动画演示
Java 内部原理 2026-10-02

synchronized:从对象锁到字节码监视器

javap 反汇编验证 synchronized 块是 monitorenter + 两次 monitorexit(正常与异常路径)、synchronized 方法只带 ACC_SYNCHRONIZED 标志;用 Unsafe 直接读对象头,在 JDK 17、21、27 上观察无锁、轻量级锁、重量级锁(wait 触发膨胀)的 mark word;块里抛异常时锁被释放。

Java synchronized 字节码 对象头 真实验证 动画演示
Java 内部原理 2026-10-02

AQS:ReentrantLock 是怎么排队和唤醒线程的

用反射直接读 AbstractQueuedSynchronizer 的 state 与等待队列(哨兵 → T1 → T2 → T3),等待线程 WAITING 并 park 在 AQS.acquire;排队线程先进先出(公平/非公平各 30/30);非公平 tryLock() 插队 31 次、公平 tryLock() 也能插队 7 次、tryLock(0, SECONDS) 只有 1 次。

Java AQS ReentrantLock 并发 真实验证 动画演示
Java 内部原理 2026-10-02

线程池:execute() 如何决定进队列还是创建线程

真实运行 ThreadPoolExecutor:execute 的决策顺序是核心线程 → 队列 → 非核心线程 → 拒绝;无界队列时 max 永远用不上;非核心线程空闲 keepAlive 后回收;execute 抛异常会换线程,submit 抛异常被存进 Future 而悄悄消失。

Java 线程池 ThreadPoolExecutor 并发 真实验证 动画演示
Java 内部原理 2026-10-02

垃圾回收:对象什么时候进入老年代

用 Serial GC 和 -Xlog:gc+age 真实观察对象何时进入老年代:保留对象在幸存区里 age 1→2→3,GC #4 晋升;幸存区装不下时有效阈值自动降为 1,对象提前晋升;PretenureSizeThreshold 让 2 MB 数组一出生就在老年代;幸存区太小时 3 MB 对象第一次 GC 就进了老年代。

Java 垃圾回收 分代 晋升 Serial GC 真实验证 动画演示
Java 内部原理 2026-10-02

G1 垃圾收集器:Region、记忆集和停顿目标

用 JDK 21 的 G1 日志实测:Region 大小随 -Xmx 变成 1M / 4M / 16M;MaxGCPauseMillis 从 50 ms 降到 5 ms,Eden 从 51.7 MB 缩到 22.2 MB、年轻代 GC 次数从 711 涨到 1692;一个完整并发标记周期的停顿序列;老年代大数组指向新对象时,年轻代停顿中位数从 0.65 ms 涨到 5.59 ms。

Java G1 垃圾回收 Region 记忆集 真实验证 动画演示
Java 内部原理 2026-10-02

JIT 编译:热点代码如何变成本地机器码

用 -XX:+PrintCompilation 与同一个 stdout 里的计时,验证一个方法先被解释执行(300 ns/次)、再被 C1 编译(约 57 ns/次)、最后被 C2 编译(8.7 ns/次);四种运行方式 -Xint / 只 C1 / 默认分层 / 只 C2 的 300 万次调用耗时分别是 554 / 31 / 23 / 24 ms。

Java JIT C1 C2 PrintCompilation 真实验证 动画演示
Java 内部原理 2026-10-02

Java NIO:Selector 如何同时管理成千上万个连接

真实运行:1 个线程的 Selector 服务端处理 3000 个保持打开的连接用 134 ms、Java 线程数 7、RSS 53 MB;每连接一线程的版本创建 3000 个线程、RSS 287 MB;select 水平触发下不读数据或常开 OP_WRITE 会让 selectNow 在 200 ms 内空转 60 多万次。

Java NIO Selector 网络 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号