设计模式——原型:拷贝的四条路
原型模式只解决一件事:让一个对象复制出第二个实例。JDK 从 1.0 起就提供了 Object.clone(),它的签名到今天没动过。它的默认语义只有逐字段赋值:字段里的数组、集合、嵌套对象在副本里仍是原来那几个。本文用一个含嵌套数组与集合的对象图,把四条拷贝路各跑 100 万个,量耗时与分配字节,浅拷贝还是深拷贝由运行时的布尔结果给出。 一、原型换到的东西new 的成本有时花在构造器之前:参数要从十个数据源拼出来,或者对象的形状取决于启动阶段读到的配置。原型模式的前提是手上已经有一个配好的对象,接下来只剩复制。 JDK 自己的四个样本JDK 里几处需要「拿到一个和现有对象一样的新对象」的地方,做法各不相同: 位置 复制动作 嵌套引用 Object#clone VM 内建,逐字段赋值 原样带走 ArrayList#clone super.clone() 后 Arrays.copyOf(elementData, size) 原样带走 Arrays.copyOf 基本类型走 arraycopy,引用类型逐元素赋值 原样带走 HashMap 拷贝构造 逐条 ...
设计模式——抽象工厂:产品族的一致性
抽象工厂常被描述成「创建一族对象」的接口,可是创建不值钱:new、Supplier、Map 查表、switch 都能做。值钱的那部分在编译期:一个族少实现一个产品,javac 直接拒绝编译;族与产品的对应关系写进接口,不用靠人记。本文用 Button/Checkbox 两套主题、三种写法,量 100 万次创建的耗时与类加载数量,并贴出 javac 与运行期的输出。 一、产品族的一致性是谁的责任两套主题,两个产品。每个主题要同时提供 Button 和 Checkbox,同一个界面里的两个控件必须来自同一套主题。 classDiagram direction TB class GuiFactory { <<interface>> +button() Button +checkbox() Checkbox } class MacFactory class WinFactory class Button { <<int...
设计模式——工厂方法:创建动作的成本
工厂方法把「造哪个对象」从调用点挪进一个函数。调用点少写一个类名,多走一次转发。这次转发值多少钱,取决于 JIT 能不能看穿它:内联成功就归零,调用点变多态就留下几纳秒。量级差出在另外两处:反射入口的参数装箱、第一次创建时组装访问器的开销。本文用 100 万次创建、两个 LTS(21.0.8 与 25),把「多一跳」拆成可测量的几项。 一、抽象掉一次创建,到底抽象掉了什么把构造调用换成工厂方法的代码长这样: 12345// 直接创建:类型写在调用点,编译期就定了Order order = new Order(userId, cart);// 工厂方法:类型挪进实现里,调用点只知道接口Order order = orderFactory.create(userId, cart); 第一行是一次 invokespecial:JVM 解析常量池的时候就知道要分配哪个类、调用哪个构造函数,内联器和逃逸分析都能直接往里看。第二行是一次 invokeinterface:调用点只知道 OrderFactory 这个接口,具体分配哪个类取决于运行时拿到的实现对象。 工厂方法换到的是「造哪个...
Java——JVM 内存与 GC 定位实战
上一篇《一次锁竞争的完整定位》查的是 CPU 和锁,这篇查内存。同一个思路:先写一个能复现症状的程序,再用 JVM 自带工具一层层把证据收齐。顺序是从粗到细:GC 日志看趋势,jcmd 和 JFR 看运行时,堆 dump 看末端对象。全文用 JDK 25 跑,每个数字都对应下面贴出来的原始输出。最后收两个真会把人带偏的场景:堆几乎是空的时候 OOM,以及没人碰老年代却一直 Full GC。 实验环境与口径机器和 JDK: 12345678910$ sysctl -n machdep.cpu.brand_string hw.ncpu hw.memsize kern.osversionApple M1 Pro101717986918425G83$ java -versionjava version "25" 2025-09-16 LTSJava(TM) SE Runtime Environment (build 25+37-LTS-3491)Java HotSpot(TM) 64-Bit Server VM (build 25+37-LTS-3491, mix...
自建 DNS 实测:DoH、DoT 与 UDP 的延迟对比
前一篇讲 sing-box 的 DNS 配置怎么写才对,这一篇回答「到底配哪个上游、用哪种协议」。本文所有数字来自本机真实运行:Apple M1 Pro / macOS,Python 3.14.7 手写 DNS 报文,2026-09-15 17:01 至 17:23 之间完成。脚本放在 /tmp/dnsbench/,关键片段贴在文中。DNS 延迟高度依赖你到上游 anycast 节点的物理路径,本文的数字只在这个网络环境下成立,最后有一节专门说明不能外推到哪。 1. 测量环境延迟测试里,环境就是结论的一半。先把会影响数字的东西列清楚。 机器与负载 项 值 机型 Apple M1 Pro,10 核 arm64 系统 Darwin 25.6.0(macOS 15) Python 3.14.7(Homebrew) OpenSSL 3.6.4 sing-box 1.14.1(go1.26.8,官方 darwin-arm64 发行版) 主矩阵跑在 17:01:13 到 17:04:29,开始时 load average 5.60 / ...
sing-box 分流与 DNS 配置实践
《Ubuntu 24.04 配置 Sing-box 代理服务器完整指南》解决的是「流量能出去」,这篇解决它之后的三个问题:规则按什么顺序生效、域名与 IP 规则分别在什么时机才能拿到匹配依据、rule_set 和 DNS 在 1.14 该怎么写。分流最难的地方在于:规则写错了 sing-box 不会报错,只是安静地走错出口。每一节都给出结论、本机跑出来的日志和一个能自动断言的验证方法。文中所有输出都来自 sing-box 1.14.1(macOS arm64)实跑,贴出的完整配置都过了 sing-box check,只写了 rules、dns 之类的片段也都在拼回完整配置后逐个校验过。 0. 实验环境分流要回答的是「某个请求最终由哪个出站接管」,所以先把出口做成可辨识的:直连出口指向一个本机 HTTP 服务,它会带回 X-Exit: direct;代理出口指向一个本机 SOCKS5 服务,它接受任何 CONNECT 并回 X-Exit: proxy。请求统一用 curl -x http://127.0.0.1:28802 发出,看响应头就知道落在哪个出口。DNS 用两台假服务...
Java——模式匹配演进史:从 JEP 406 到 JEP 441
Java 的模式匹配是十几轮 JEP 叠加出来的:instanceof 模式三轮(JDK 14 预览、15 再预览、16 定稿),switch 上的模式匹配五轮(JDK 17~20 四次预览、21 定稿),记录模式三轮(19、20 预览、21 定稿),未命名变量两轮(21 预览、22 定稿),而原始类型模式到 JDK 27 已经是第五次预览,仍然没有定稿。这篇文章按 JEP 的顺序回答另一类问题:每一轮究竟改了什么、为什么不得不改,以及那些只在预览期存在过的语法去了哪里。文中所有编译器输出都来自本机实测(JDK 17.0.5 / JDK 21.0.8 / JDK 25+37-LTS,Apple M1 Pro,macOS);凡是标注「实测」的结论,都能在下面找到对应的原始输出块。JEP 的编号、版本、状态与引用的动机,全部核对自 openjdk.org 的 JEP 页面正文。 先看一条时间线把后面要展开的东西压成一张表,方便随时对照: JEP JDK 状态 关键变化 305 14 预览 instanceof 模式首次预览 375 15 第二次...
Java——JDK 21 到 25 迁移清单
JDK 21 和 JDK 25 是两个相邻的 LTS,中间隔着 22、23、24 三个非 LTS 版本。多数 LTS 到 LTS 的升级都是”安静”的:你的代码大概率原样能跑。但 21 到 25 这两年是”预览转正 + 移除清理”集中发生的窗口,有几类坑是编译器和 JVM 不会替你兜底的:预览 API 改了形状、遗留线程 API 被删、sun.misc.Unsafe 开始打印警告、Security Manager 彻底关死、虚拟线程在 synchronized 里的行为被重写。这篇文章是一份可执行的迁移清单:每条结论都在本机的 JDK 21(21.0.8)和 JDK 25(25+37-LTS)上分别编译或运行过,原始输出贴在对应的 ```txt 块里。凡是没在本机验证过的,都单独标注”未实测”。 实验环境与取证方式本机是 Apple M1 Pro(10 核 arm64,macOS),装了 JDK 21.0.8 和 JDK 25 两个版本,路径如下: 123456789$ /Library/Java/JavaVirtualMachines/jdk-21....
Java——一次锁竞争的完整定位
一个接口的 p50 从 23ms 变成 1.5 秒、QPS 从 2756 掉到 41,而机器几乎不忙。这篇记录把这个现象定位到锁竞争的全过程。顺序是:先造一个「把 20ms 下游调用写进 synchronized 块」的服务并把症状跑出来,再用 jcmd <pid> Thread.print 找到 63 条 BLOCKED 线程,再用 JFR 量出「8 秒里累计等了 429.5 秒」,最后改代码复测到 5 毫秒。中间还记了两个容易被误判的场景:CPU 打满时锁等待会被一起放大、p95 高但请求全堆在线程池队列里——这两种情况下改锁是白改。文里的每个数字都是本机(Apple M1 Pro / JDK 25)跑出来的,原始输出贴在对应位置;跑不出来的地方会直说。 实验环境与口径机器与 JDK12345678910$ java -versionjava version "25" 2025-09-16 LTSJava(TM) SE Runtime Environment (build 25+37-LTS-3491)Java HotSpot(T...
Java——虚拟线程迁移实测
把「每请求一线程」的服务从固定平台线程池换成虚拟线程,收益到底有多少,用嘴说不清楚,得量出来。这篇文章用 JDK 自带的 HttpServer 搭了一个阻塞式服务,用固定并发数的压测客户端在 50 / 200 / 1000 / 5000 四档并发下分别跑固定池与虚拟线程,配上 JFR 录制、载体线程调参、CPU 密集反例和池化虚拟线程的反面教材。文里的每个数字都是本机(Apple M1 Pro / JDK 25)跑出来的,原始输出都贴在对应的表格前后;跑不出来的地方会直说是推测或未验证。 实验环境与口径机器与 JDK123456789101112$ java -versionjava version "25" 2025-09-16 LTSJava(TM) SE Runtime Environment (build 25+37-LTS-3491)Java HotSpot(TM) 64-Bit Server VM (build 25+37-LTS-3491, mixed mode, sharing)$ sysctl -n...









