Exception Handling

异常捕捉的确是对性能有影响的,那是因为一旦异常被抛出,函数也就跟着 return 了。而程序在执行时需要处理 函数栈 的上下文,这会导致性能变得很慢,尤其是线程栈比较深的时候。但从另一方面来说,异常的抛出基本上表明程序的错误。程序在绝大多数情况下,应该是在没有异常的情况下运行的, 所以,有异常的情况应该是少数的情况,不会影响正常处理的性能问题。 对于我们并不期望会发生的事,我们可以使用异常捕捉;对于我们觉得可能会发生的事,使用返回码。 https://time.geekbang.org/column/article/675 你觉得自己是一个Java专家吗?是否肯定自己 已经全面掌握了Java的异常处理机制?在下面这段代码中,你能够迅速找出异常处理的六个问题吗? 1 OutputStreamWriter out = ... 2 java.sql.Connection conn = ... 3 try { // ⑸ 4 Statement stat = conn.createStatement(); 5 ResultSet rs = stat.executeQuery( 6 "select uid, name from user"); 7 while (rs.next()) 8 { 9 out.println("ID: " + rs.getString("uid") // ⑹ 10 ",姓名: " + rs.getString("name")); 11 } 12 conn.close(); // ⑶ 13 out.close(); 14 } 15 catch(Exception ex) // ⑵ 16 { 17 ex.printStackTrace(); //⑴,⑷ 18 } 作为一个Java程序员,你至少应该能够找出两个问题。但是,如果你不能找出全部六个问题,请继续阅读本文。 ...

Created 23236-03-23 · Updated 9096-10-09 · 6 min · 1166 words · -

相互转换逗号分隔的字符串和 List

“相互转换逗号分隔的字符串和List” https://blog.csdn.net/yywusuoweile/article/details/50315377 将逗号分隔的字符串转换为List 方法 1: 利用JDK的Arrays类 String str = "a,b,c"; List<String> result = Arrays.asList(str.split(",")); 方法 2: 利用Guava的Splitter String str = "a, b, c"; List<String> result = Splitter.on(",").trimResults().splitToList(str); 方法 3: 利用Apache Commons的StringUtils (只是用了split) String str = "a,b,c"; List<String> result = Arrays.asList(StringUtils.split(str,",")); 方法 4: 利用Spring Framework的StringUtils String str = "a,b,c"; List<String> str = Arrays.asList(StringUtils.commaDelimitedListToStringArray(str)); 将List转换为逗号分隔符 方法 1: 利用JDK (好像没有很好的方法,需要一步一步实现) NA 方法 2: 利用Guava的Joiner List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); String str = Joiner.on(",").join(list); 方法 3: 利用Apache Commons的StringUtils ...

Created 9096-10-09 · Updated 9096-10-09 · 1 min · 130 words · -

Red-Black Tree, 红黑树, R-B Tree

二叉搜索树 红黑树本质上就是一棵二叉搜索树(Binary Search Tree, BST)。BST 的定义、性质与退化成链表后性能变差的问题,见 二叉树与二叉搜索树 §二叉搜索树 Binary Search Tree。 红黑树的性质 红黑树在二叉搜索树的基础上增加了着色和相关的性质,使得树相对平衡,从而保证查找、插入、删除的时间复杂度最坏为 O(log n)。要保证一棵有 n 个结点的红黑树的高度始终保持在 O(log n),需要满足以下 5 条性质: 每个结点要么是红色,要么是黑色。 根结点是黑色。 每个叶结点(即树尾端的 NIL 指针或 NULL 结点)是黑色。 如果一个结点是红色,那么它的两个子结点都是黑色。 对于任一结点,其到叶结点的每一条路径都包含相同数目的黑色结点。 正是这 5 条性质,使得一棵有 n 个结点的红黑树始终保持 O(log n) 的高度,从而解释了上面"查找、插入、删除的时间复杂度最坏为 O(log n)“这一结论的原因。 示例:一棵合法的红黑树 下面几张图都用同一套配色:红色底色代表红色节点,深色底色代表黑色节点(NIL 叶子也按黑色处理);后面删除修复的例子里还会多一种"双重黑"的颜色,专门标出来。 graph TD classDef red fill:#d64545,stroke:#333,color:#fff; classDef black fill:#2c2c2c,stroke:#333,color:#fff; r((10)):::black a((5)):::red b((3)):::black c((7)):::black d((15)):::black e[NIL]:::black f((20)):::red r --> a r --> d a --> b a --> c d --> e d --> f 对照上面的 5 条性质检查这棵树:根 10 是黑色;红色节点 5、20 的子节点都是黑色(5 的子节点 3、7 是黑色,20 的两个子节点是图中未画出的 NIL,同样按黑色计);从根到任意一条路径上的黑色节点数都是 3(例如 10 → 5 → 3 → NIL 与 10 → 15 → 20 → NIL 都是 3 个黑色节点),满足黑高一致。 ...

Created 28286-06-28 · Updated 15156-09-15 · 4 min · 647 words · -

Spring IoC,依赖注入

IoC(Inversion of Control,控制反转)是 Spring 的核心机制。它把"创建和管理对象"的控制权从应用代码交给 Spring 容器,应用代码只需声明依赖,容器负责提供。Spring Framework 整体定位见 Spring。 IoC 的具体实现方式叫 DI(Dependency Injection,依赖注入),容器通过构造器、字段或 Setter 方法将依赖对象注入进来。 IoC 解决了什么问题 传统写法中,对象自己负责创建依赖: // 传统写法:自己创建依赖,紧耦合 public class OrderService { private UserDao userDao = new UserDaoImpl(); // 自己 new,换实现要改代码 private EmailService email = new EmailService("smtp.example.com", 465); } 这带来三个问题: 紧耦合:OrderService 直接依赖具体实现类,换实现必须修改源码 难以测试:无法替换成 Mock 对象,单元测试困难 对象生命周期散乱:每次 new 都产生新对象,无法复用单例 IoC 写法: // IoC 写法:声明需要什么,容器负责提供(推荐构造器注入) @Service public class OrderService { private final UserDao userDao; private final EmailService emailService; public OrderService(UserDao userDao, EmailService emailService) { this.userDao = userDao; this.emailService = emailService; } } 切换实现时只需修改容器配置(或替换实现类上的 @Primary / @Qualifier),OrderService 代码本身不用动。 ...

Created 12126-05-12 · Updated 11116-08-11 · 9 min · 1753 words · -

Java Knowledge Map

背景 Java 相关文章分散在多篇文章中。本文用 关系图 + 索引表 串起 JVM 运行时、类加载、调试、字节码织入、生产诊断等概念,便于从一点跳到专题文。 与 BTrace 对话中沉淀的 attach / agent / ASM 链路,是下图右侧「生产诊断」分支的核心。 总览关系图 flowchart TB subgraph runtime [JVM 运行时] HS[HotSpot 简介] JVM[jvm 通用概念] CL[classloader] GC[java-gc 等] end subgraph native [Native 与通道] JVMTI[JVMTI] ATT[Attach API] JCMD[jcmd] end subgraph debug [调试与开发热更] JPDA[JPDA 远程调试] HOT[开发期热替换 DCEVM] SBDT[Spring Boot DevTools] end subgraph instrument [字节码与 Agent] INST[java.lang.instrument] ASM[java-asm] end subgraph prod [生产诊断] BTRACE[BTrace] TOOL[诊断工具选型] JFR[JFR] end HS --> CL HS --> GC HS --> ATT HS --> JVMTI JPDA --> JVMTI ATT --> INST INST --> ASM ASM --> BTRACE ATT --> BTRACE JCMD --> ATT HOT --> JPDA HOT --> ASM BTRACE --> TOOL JFR --> TOOL SBDT -.->|非 JVM HotSwap| CL 按主题索引 JVM 与 HotSpot 概念 文章 JVM 生态、实现选型 Java 虚拟机生态与选型 HotSpot 架构与站内索引 HotSpot 简介 HotSpot 启动参数 hotspot-options JIT / C1·C2 / 分层编译 hotspot-jit GC、分代(多篇) java-gc 等 Safepoint safepoint 类加载 概念 文章 双亲委托、命名空间、Agent 与业务 ClassLoader java classloader java.lang.Class(类型元信息、获取方式) lang-class Class.forName class-forname 调试体系(JPDA) 概念 文章 JPDA 三层 JDI / JDWP / JVMTI、远程调试 JAVA 调试与 JPDA JVMTI native 接口 JVMTI Attach 与内置诊断 概念 文章 Attach API、loadAgent、与 jcmd 区别 Java Attach API jcmd、Diagnostic Command jcmd 字节码、Agent、织入 概念 文章 ASM、Instrumentation、retransform、INVOKESTATIC 示意 Java ASM 与运行时字节码织入 BTrace 安装、attach、脚本、慢调用 BTrace 生产诊断工具选型(BTrace / Arthas / JFR) 生产环境诊断工具选型 JFR Java Flight Recorder 开发期热替换(非 BTrace) 概念 文章 HotSwap、DCEVM、HotSwapAgent 开发期热替换 Spring Framework 总览与演变 Spring Spring Boot DevTools Restart Spring Boot DevTools Spring Boot 总览 Spring Boot Spring IoC / 依赖注入 Spring IoC Spring Bean 生命周期 Spring Bean Lifecycle Spring WebFlux Spring WebFlux Java Virtual Threads Java Virtual Threads Spring Boot 3.x 迁移 Spring Boot 3.x Migration IDE / DCEVM 热更 vs attach 探针 见 java-asm、BTrace AOP / 其它织入 概念 文章 AspectJ 编译时织入 AspectJ CTW CGLIB、AOP cglib、aop BTrace 相关概念链(精读路径) 若从 BTrace 出发,建议按因果顺序阅读: ...

Created 31316-05-31 · Updated 11116-08-11 · 3 min · 470 words · -

Spring Boot Startup Callbacks: 应用启动后执行逻辑

若要在 Spring 应用上下文已就绪 之后执行一段初始化代码,常见做法如下。多个 Bean 可同时实现同一类接口,使用 @Order 或 Ordered 控制顺序。框架总览见 Spring Boot。 ApplicationRunner 与 CommandLineRunner 二者都会在 SpringApplication 启动流程末尾被调用。需要结构化解析命令行参数(如 key=value 形式)时,优先使用 ApplicationRunner(入参为 ApplicationArguments);只关心原始字符串数组时用 CommandLineRunner 即可。 @Component @Order(1) public class SeedDataRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) { // post-startup logic } } @Component @Order(2) public class BannerRunner implements CommandLineRunner { @Override public void run(String... args) { // post-startup logic } } @PostConstruct 在某个 Spring Bean 完成依赖注入之后、对外使用前执行,适合该 Bean 自身的轻量初始化。不要在 static 方法上使用 @PostConstruct(规范要求实例方法)。 ...

Created 26266-06-26 · Updated 11116-08-11 · 1 min · 135 words · wiloon

Spring Bean Lifecycle: 从实例化到销毁

Spring 容器管理的不只是"创建对象",还包括从实例化到销毁的完整过程。容器概览见 Spring IoC;本文聚焦单个 singleton Bean 从 new 到被销毁经历的各个阶段。 整体流程 graph TD A[实例化调用构造器] --> B[属性填充依赖注入] B --> C[Aware 接口回调] C --> D[BeanPostProcessor前置处理] D --> E["初始化回调PostConstruct → afterPropertiesSet → init-method"] E --> F[BeanPostProcessor后置处理AOP 代理在此生成] F --> G[就绪可用] G --> H[容器关闭] H --> I["销毁回调PreDestroy → destroy → destroy-method"] singleton Bean 只走一次这套完整流程;prototype Bean 只做到"就绪可用",容器交出对象后就不再管理,销毁阶段(H、I)不会发生——细节见 Spring 中 Bean 的作用域。 实例化与属性填充 容器先按 BeanDefinition 里记录的构造器(或工厂方法)创建原始对象,再做属性填充(即依赖注入,见 Spring IoC §依赖注入的三种方式)。这两步是循环依赖唯一可能出现的阶段——对象已存在但还没准备好被别人安全引用。 三级缓存与循环依赖 以 A 依赖 B、B 又依赖 A(字段注入或 Setter 注入场景)为例: sequenceDiagram participant C as Container participant A as Bean A participant B as Bean B C->>A: new A()(原始对象,属性未填) C->>C: A 的 ObjectFactory 放入三级缓存 C->>A: 注入属性,发现需要 B C->>B: new B()(原始对象) C->>C: B 的 ObjectFactory 放入三级缓存 C->>B: 注入属性,发现需要 A C->>C: 查 A:一级 miss,二级 miss,三级 hit C->>C: 调用 ObjectFactory 得到 A 的早期引用 C->>C: 早期引用移入二级缓存,移除三级缓存 C->>B: 注入 A 的早期引用,B 创建完成 C->>C: B 放入一级缓存 C->>A: 用完成态的 B 完成属性注入,A 创建完成 C->>C: A 放入一级缓存,清理二三级缓存里的临时数据 容器创建 A:new A() 已经创建出 A 的真实原始对象(构造器已执行完毕,只是属性还没注入)。Spring 把「如何暴露这个已存在的原始对象」包装成一个 ObjectFactory(本质是个 () -> getEarlyBeanReference(...) 的 lambda,调用它才会决定返回原始对象还是 AOP 代理),再把这个工厂本身放进三级缓存 singletonFactories,并把 A 标记为「正在创建中」。也就是说,三级缓存里存的是「怎么拿到早期引用」的工厂,不是对象本身;对象在 new 这一步就已经真实存在了。 给 A 注入属性时发现需要 B,转去创建 B,同理:new B() 创建出真实的原始对象,再把它的 ObjectFactory 放进三级缓存。 给 B 注入属性时发现需要 A:依次查一级(没有,A 还没做完)、二级(没有)、三级缓存——命中 A 的 ObjectFactory,调用它拿到 A 的早期引用,挪进二级缓存并从三级缓存移除。 B 用这个早期引用完成注入、创建完成,放入一级缓存。 流程回到 A:用刚创建好的 B 完成属性注入,A 也创建完成,放入一级缓存。 三级缓存各自存的到底是什么(一级、二级缓存的底层结构见 Spring IoC §单例缓存怎么存): ...

Created 11116-08-11 · Updated 11116-08-11 · 3 min · 597 words · -

Compiled vs Interpreted: 编译型、解释型与脚本语言

背景 教材里常把编程语言分成 编译型(compiled) 和 解释型(interpreted)。二者是一组相对概念,但现代语言大多落在光谱中间,不宜一刀切。 脚本语言(scripting language) 在日常说法里,往往就是解释型一侧的别名,或更窄一点:强调「缩短 edit-compile-link-run、以源码直接跑、做自动化 / 嵌入」的那一类解释型语言。下文先讲编译 / 解释,再单独说脚本。 两种执行路径 类型 典型流程 运行时依赖 编译型 源码 → 编译器 → 机器码 / 本地二进制 → 直接由 OS 加载执行 一般不需要语言自带的解释器;可内嵌 runtime(如 GC) 解释型 源码(或中间表示)由 解释器 边读边执行 必须安装对应解释器 / 运行时 对照: 编译型(AOT): 源码 ──compile──► 本地二进制 ──► 运行 解释型: 源码 ──► 解释器逐句执行 字节码 + VM: 源码 ──compile──► 字节码 ──► 解释 / JIT ──► 机器码 「预编译 / ahead-of-time(AOT)」说的是:运行前就编成可执行形态;日常口语里常与「编译型」混用。注意 C 里的「预编译」有时指 #include / #define 的预处理,那是编译流水线的一步,不是语言分类。 ...

Created 1016-08-01 · Updated 1016-08-01 · 2 min · 395 words · -

2026 W29

从 HashMap 关联到的 上周说面试复盘整理了一部分,这周继续。blog 里 Java 相关的文档以前记得有些零散,重新整理一下。 HashMap 关联的东西其实挺多的… 先是解决哈希冲突主要的两条路:开链法和开放地址法。链表长到一定程度会转成红黑树,于是又去看了红黑树。哈希值怎么来的又牵出随机数——xorshift、LCG。再往下是 CPU 层面的事:加法/乘法/除法消耗的时钟周期不一样,缓存的空间局部性会直接影响这类数据结构的实际性能。横向看,Python 的 dict、Go 的 map 用的 Swiss Table,思路各不相同。 Swiss Table 这条又牵出 SIMD,还挺有意思的。1997 年的 Pentium MMX 就带了 MMX 指令集,不过只能做整数;AMD 1998 年 K6-2 上的 3DNow! 把单精度浮点加了进来;到 1999 年 Pentium III 的 SSE,才有了独立的 128 位寄存器。早年这套指令主要是拿来做多媒体和图形的。 而 Swiss Table 用 SIMD 一次比对一组控制字节来定位槽位,Go 的 map 后来换成了这个结构——中间隔了二十多年。一个 90 年代末为了跑多媒体加进来的特性,现在被用来加速哈希表的查找。 JVM 这边也一并整理了:HotSpot 的 GC 和 JIT。中间还跑偏去看了下 Linux 的进程调度。 starlabrys:把公司的壳先搭起来 和朋友聊天,有人找到了工作但不太理想,有人还在面试。我之前注册了营业执照,就在想我们是不是可以一起做点什么。 有人建议建一个 GitHub organization,这样想贡献代码的人可以加进来。公司暂时没业务,但有件事是现在就能做的——把官网建起来。 于是注册了域名 starlabrys.com,建了 GitHub org,把仓库和权限用 OpenTofu 管起来,后来又把这部分 IaC 从我自己的配置仓库拆出去,单独放进了 starlabrys-infra。 ...

Created 19196-07-19 · Updated 19196-07-19 · 1 min · 126 words · -

JVM HTTP 代理配置

概述 Java 程序访问外部 HTTP(S) 资源时,如果所在网络需要经过代理才能出网,可以通过系统属性告诉 JVM 走哪个代理,无需修改业务代码。本文整理常见的配置方式,并说明这些方式在现代 JDK(含 JDK 26)里是否依然有效。 经典方式:http.proxyHost / http.proxyPort java.net.URLConnection 以及后来的 java.net.http.HttpClient(JDK 11+)在没有显式指定代理时,都会使用 ProxySelector.getDefault(),而默认实现会读取以下系统属性: http.proxyHost / http.proxyPort —— HTTP 请求走的代理 https.proxyHost / https.proxyPort —— HTTPS 请求走的代理 http.nonProxyHosts —— 不走代理的主机列表,多个主机用 | 分隔 这几个属性在 JDK 26 上依然是标准、有效的配置方式。 命令行参数 java -Dhttp.proxyHost=proxy.example.com \ -Dhttp.proxyPort=8080 \ -Dhttps.proxyHost=proxy.example.com \ -Dhttps.proxyPort=8080 \ -jar app.jar 代码里设置系统属性 // Must be set before the first network call is made System.setProperty("http.proxyHost", "proxy.example.com"); System.setProperty("http.proxyPort", "8080"); 不要把代理地址硬编码在源码里,建议从配置文件读取。 Tomcat:catalina.properties 追加到 ${CATALINA_HOME}/conf/catalina.properties: ...

Created 5056-07-05 · Updated 16166-07-16 · 1 min · 205 words · -

JDK Tools: 命令行工具现状与过时工具

JDK 自带的命令行工具随着版本演进变化很大:CORBA、JAX-WS/JAXB、Java Web Start 等整套技术栈在 JDK 11 前后被移除,一批老工具也随之消失;同时 jshell、jlink、jpackage、jhsdb 等是后来才加入的。本文按「现在还在用」「名存实亡」「已从 JDK 移除」三类整理,方便判断一个工具在 JDK 26 上还值不值得用。 仍在用的核心工具 分类 工具 用途 编译与运行 java 运行 class/jar,也可直接运行单文件源码 编译与运行 javac 编译 Java 源码 编译与运行 jshell REPL,交互式执行 Java 代码(JDK 9 引入) 打包与模块 jar 打包/解包 jar 文件 打包与模块 jlink 生成裁剪后的自定义运行时镜像(JDK 9 引入) 打包与模块 jmod 创建和查看 jmod 模块文件(JDK 9 引入) 打包与模块 jpackage 生成平台原生安装包(JDK 14 引入,见下文 javapackager 的替代) 签名与安全 jarsigner jar 签名与验证 签名与安全 keytool 密钥、证书管理 文档与静态分析 javadoc 生成 API 文档 文档与静态分析 javap 反汇编 class 文件 文档与静态分析 jdeps 分析类/模块依赖关系(JDK 8 引入) 文档与静态分析 jdeprscan 扫描代码中用到的已废弃 API(JDK 9 引入) 文档与静态分析 jnativescan 扫描 FFM API(java.lang.foreign)受限方法调用(JDK 22 引入) 诊断与监控 jcmd 向运行中的 JVM 发送诊断命令,官方现在推荐的统一入口,详见 jcmd 诊断与监控 jconsole 图形化 JMX 监控 诊断与监控 jfr Flight Recorder 命令行工具,详见 Java Flight Recorder 诊断与监控 jhsdb HotSpot Serviceability Agent 统一入口,整合了旧版 jsadebugd/SA 调试功能(JDK 9 引入) 诊断与监控 jps 列出本机 JVM 进程 诊断与监控 jstat / jstatd JVM 统计信息采集 / 远程统计信息服务 诊断与监控 jwebserver 内置的简单静态文件 HTTP 服务器(JDK 18 引入) 调试 jdb 命令行调试器 RMI rmiregistry RMI 远程对象注册表 脚本 jrunscript 命令行脚本外壳工具,见下文「名存实亡」 其它签名工具 serialver 生成 serialVersionUID Windows 专属 javaw 不弹控制台窗口运行 Java 程序 Windows 专属 jabswitch / jaccessinspector / jaccesswalker Java Access Bridge 开关与辅助功能检查工具 Kerberos kinit / klist / ktab Kerberos 票据与密钥表管理 名存实亡:还在但不建议依赖 这几个工具没有被移除,仍出现在最新 JDK 的工具列表里,但官方已经不建议把它们当作首选: ...

Created 6066-02-06 · Updated 16166-07-16 · 3 min · 475 words · -

VisualVM: 线程与堆内存监控分析工具

VisualVM 是什么 VisualVM 是一个图形化的 JVM 监控与分析工具,可以查看应用程序的 CPU、内存占用,监控线程状态,生成线程转储(Thread Dump)或堆转储(Heap Dump),跟踪内存泄漏,监控垃圾回收,并支持简单的 CPU/内存 profiling。它也能浏览和操作 MBean,配合 JMX 做远程监控。 VisualVM 曾在 JDK 6 Update 7 之后随 Sun/Oracle JDK 一起分发;从 JDK 9 起被移出 JDK 发行包,独立成开源项目 visualvm.github.io,需要单独下载安装,本身要求 JDK 8 及以上运行,但可以监控 JDK 1.4 以上版本的目标进程。 安装与启动 从 visualvm.github.io 下载对应平台的安装包,解压后运行 visualvm(Windows 下是 visualvm.exe)。 如果 VisualVM 找不到本机 JDK,用 -jdkhome 显式指定: visualvm -jdkhome /path/to/jdk 插件默认安装在用户目录下,例如 Windows: %APPDATA%\VisualVM\<version> 线程监控与 Thread Dump 启动 VisualVM 后,“应用程序 > 本地” 节点会列出本机正在运行的 Java 进程。双击目标进程,切换到「线程」标签页,可以看到各线程的实时状态跟踪。 点击「线程 Dump」按钮会生成当前时刻的线程转储文件,里面包含每个线程的调用栈、锁等待对象等信息,用来定位线程阻塞、死锁的位置;关于线程转储的字段含义与死锁排查方法,参考 jstack。 VisualVM 线程状态 状态 说明 Running 正在运行 / 可运行(runnable) Sleeping 线程调用了 Thread.sleep() Wait 线程处于 wait 或 timed_waiting Park 线程调用了 LockSupport.park() Monitor 线程被 monitor 锁阻塞(blocked) 远程监控 VisualVM 也可以通过 JMX 或 jstatd 监控远程 JVM 进程,思路和本地类似,多了一步网络连接配置。 ...

Created 4046-11-04 · Updated 16166-07-16 · 2 min · 245 words · -

Java Thread.join() 原理与用法

join() 是做什么用的 join() 定义在 Thread 类里,作用是让当前线程等待另一个线程结束。谁调用了 t.join(),谁就会阻塞,直到线程 t 执行完毕才继续往下走。 容易搞混的一点是"谁等谁":t.join() 阻塞的不是 t 自己,而是执行这行代码所在的那个线程。 为什么需要 join() 典型场景:主线程启动一个子线程去做耗时运算,主线程处理完自己手头的事情后,需要用到子线程算出来的结果——这时就要用 join(),等子线程真正跑完再继续,否则可能拿到还没算完的结果。 用例子理解 写三个类来演示:CustomThread1 循环打印 5 次、每次间隔 1 秒;CustomThread 在 run() 里调用了 t1.join(),要等 t1 跑完才继续;JoinTestDemo 是入口。 class CustomThread1 extends Thread { public CustomThread1() { super("[CustomThread1] Thread"); } @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " start."); try { for (int i = 0; i < 5; i++) { System.out.println(threadName + " loop at " + i); Thread.sleep(1000); } System.out.println(threadName + " end."); } catch (InterruptedException e) { System.out.println("Exception from " + threadName + ".run"); } } } class CustomThread extends Thread { private final CustomThread1 t1; public CustomThread(CustomThread1 t1) { super("[CustomThread] Thread"); this.t1 = t1; } @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " start."); try { t1.join(); System.out.println(threadName + " end."); } catch (InterruptedException e) { System.out.println("Exception from " + threadName + ".run"); } } } public class JoinTestDemo { public static void main(String[] args) throws InterruptedException { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " start."); CustomThread1 t1 = new CustomThread1(); CustomThread t = new CustomThread(t1); t1.start(); Thread.sleep(2000); t.start(); t.join(); // main thread waits for t to finish System.out.println(threadName + " end!"); } } main 线程先启动 t1,睡 2 秒后再启动 t,最后调用 t.join() 等 t 结束。执行顺序大致是: ...

Created 13136-01-13 · Updated 16166-07-16 · 2 min · 401 words · -

HotSpot

背景 OpenJDK 发行版里默认的 Java 虚拟机实现是 HotSpot(C++)。你安装的 java 命令、容器里的 JDK 镜像,除非明确换用 OpenJ9 或 GraalVM Native Image,否则跑的就是 HotSpot。 Java 领域还有哪些 JVM,见 Java 虚拟机。 HotSpot 是什么 HotSpot 得名于 热点探测(hot spot detection):大部分时间只执行少量代码,JIT 优先编译这些热点方法。内置 解释器、C1 与 C2 两个 JIT 编译器,默认通过 分层编译 在启动速度与峰值性能之间折中。 对比 HotSpot Eclipse OpenJ9 GraalVM Native Image 典型形态 OpenJDK 默认 java IBM Semeru native-image 产物 JIT C1 + C2 自有 JIT 构建期 AOT,无运行时 JIT 常用诊断 jcmd、JFR、Attach 自有工具 与传统 JVM 排障不同 历史 HotSpot 的技术 lineage 来自 Self / Strongtalk 一脉的动态编译与热点探测。Longview Technologies(对外也称 Animorphic)1994 年成立,1997 年 2 月被 Sun 收购;收购后 Sun 在其 VM 技术上为 Java 编写更强的 JIT(C2 等),因运行时持续探测并优化热点代码而得名 HotSpot。 ...

Created 31316-05-31 · Updated 16166-07-16 · 3 min · 507 words · -

HotSpot JIT 编译器(C1/C2)

背景 OpenJDK 默认的 HotSpot 虚拟机通过 解释器 + 即时编译(JIT) 执行字节码:冷代码先解释运行,热点方法再编译为本地机器码。本文归纳 C1/C2 分工、分层编译与热点探测;JDK 21 之后的 AOT 预热见 JDK 25 变更汇总。 C1 与 C2 HotSpot 内置两个 JIT 编译器,历史上也称 Client 编译器 与 Server 编译器,对应 C1 与 C2: 编译器 特点 C1 编译快,做简单、可靠的优化 C2 编译慢,做更重的优化,会依据运行时 profile 做激进优化 二者都只编译 热点代码(多次调用的方法,或多次执行的循环体),编译前须先做热点探测。 Client/Server 两套 VM 的划分在 64 位 JDK 上已基本消失:现代 HotSpot 统一走 Server VM,并通过 分层编译 把 C1 的快速启动与 C2 的峰值性能结合起来(见下一节)。 分层编译(Tiered Compilation) 自 Java 7 起,Server VM 默认启用 分层编译(-XX:+TieredCompilation,一般无需手动开)。HotSpot 用 5 个执行级别 在解释器与两个编译器之间过渡: ...

Created 30306-03-30 · Updated 16166-07-16 · 2 min · 354 words · -

Java 虚拟机

背景 日常说的「JVM」常混用两层含义: JVM 规范(JLS/JVMS):字节码、class 文件格式、类加载语义、JNI、JPDA 等,与具体实现无关。 JVM 实现:真正把字节码跑起来的运行时,如 HotSpot、OpenJ9。 安装 java 命令得到的是 JDK 发行版(见 OpenJDK JDK 发行版;安装命令见 openjdk),其中默认捆绑的虚拟机实现几乎都是 HotSpot。本文介绍 JVM 实现(HotSpot、OpenJ9、GraalVM Native 等)的差异与选型;HotSpot 的参数与工具见 hotspot。 JDK 发行版 vs JVM 实现 Temurin、Corretto、Dragonwell 等是不同厂商打包的 OpenJDK 发行版,默认都跑 HotSpot,不是三种并列的 JVM。换 Temurin 换 Corretto,通常只是换构建源与支持方;要换虚拟机实现才看 OpenJ9(Semeru)或 GraalVM Native Image。发行版对照见 openjdk-distributions.md。 规范层:各实现共同遵守什么 无论用哪种 JVM,下列概念在规范层是共通的: 概念 说明 字节码 javac 产出 .class,由虚拟机解释或 JIT 编译后执行 类加载 加载、链接、初始化;双亲委托等模型见 classloader JNI Java 与本地代码互操作规范,见 java-jni JPDA 调试体系(JDI / JDWP / JVMTI),见 java-debug-JPDA 规范 不 规定用哪种垃圾收集器、有哪些 -XX 开关、是否提供 JFR——这些是实现细节。 主流 JVM 实现对照 实现 典型发行版 特点 适用场景 HotSpot Oracle JDK、Temurin、Corretto、Dragonwell 等 生态最全;C1/C2 JIT、G1/ZGC/Shenandoah、JFR、jcmd、Attach 默认可用 默认首选;绝大多数服务端与桌面应用 Eclipse OpenJ9 IBM Semeru 常更低内存占用、较快启动;工具链与 HotSpot 不同 容器密度高、内存敏感;需验证 agent/诊断工具兼容性 GraalVM GraalVM CE/EE 可嵌 HotSpot 作 JIT 运行时,或通过 Native Image AOT 为本地可执行文件 极致冷启动(serverless、CLI);见 GraalVM 简介、Native Image JRockit 已停止独立发展 曾用于金融低延迟;特性已并入 HotSpot 仅作历史了解,见 jrockit ┌─────────────────────────────────┐ │ Java 语言 + JVM 规范 │ └─────────────────────────────────┘ │ ┌───────────────────────────┼───────────────────────────┐ ▼ ▼ ▼ OpenJDK HotSpot Eclipse OpenJ9 GraalVM (默认 java 命令) (Semeru 等) ┌──────┴──────┐ │ │ ▼ ▼ │ │ HotSpot 运行时 Native Image ▼ ▼ (JIT) (AOT 本地二进制) jcmd / JFR / Attach 自有诊断工具 选型建议 没有明确理由时,用 HotSpot(即常见 OpenJDK 发行版)。文档、agent、APM、排障经验都围绕它积累最多。 内存或启动是瓶颈,且已验证依赖(Attach、部分 agent、GC 日志格式)在目标环境可用,可评估 OpenJ9。 冷启动极敏感(函数计算、短生命周期进程),评估 GraalVM Native Image(见 GraalVM 简介、Native Image);需接受构建期分析、反射配置、动态类加载受限等成本。 不要混用概念:GraalVM 不等于「又一个 HotSpot」;Native Image 路径下没有传统 JIT 预热,也没有完整的 -XX 调优空间。 HotSpot 执行路径(概念) HotSpot 的典型路径(细节见专文): ...

Created 19196-05-19 · Updated 15156-07-15 · 2 min · 308 words · -

HotSpot JVM 启动参数

背景 本文汇总 OpenJDK HotSpot 常用启动参数(-、-X、-XX)。这些开关 不是 JVM 规范的一部分,其他实现(OpenJ9 等)名称与语义可能不同。 JVM 生态选型见 jvm;HotSpot 架构索引见 hotspot。GC 算法细节见 java-gc;JIT 与分层编译见 hotspot-jit。 参数分类 HotSpot 命令行参数分三类: 类型 前缀 说明 标准参数 - 规范要求所有实现支持,如 -version 非标准参数 -X 实现相关,如 -Xms、-Xmx;不保证跨版本兼容 不稳定参数 -XX HotSpot 专有调优项,可能无预警变更或移除 查看当前生效的 HotSpot 参数: java -XX:+PrintCommandLineFlags -version java -XX:+PrintFlagsFinal -version | grep -i gc 堆与元空间 堆大小 参数 别名 说明 -Xms<size> -XX:InitialHeapSize 初始堆大小;常与 -Xmx 设相同,避免运行期扩堆 -Xmx<size> -XX:MaxHeapSize 最大堆大小 -Xmn<size> -XX:NewSize 等 年轻代大小(eden + 2×survivor);显式设置会干扰 G1 等区域自适应,现代应用更常用比例或交给 G1 自动调整 <size> 可用 k/m/g(如 -Xmx2g)。 ...

Created 19196-06-19 · Updated 15156-07-15 · 3 min · 442 words · -

Spring AOT

是什么 Spring AOT(Ahead-Of-Time)是 Spring Framework 6 / Spring Boot 3 引入的构建期处理机制:在打包或 Native 编译之前,对 Spring ApplicationContext 做静态分析,生成预先计算好的 bean 定义、反射/资源 hint,以及(可选)面向 GraalVM Native Image 的入口代码。 默认 Spring Boot 应用在运行时做 classpath 扫描、@Configuration 解析、条件装配(@Conditional / @ConditionalOn*)和大量反射;Spring AOT 把这些工作尽量前移到构建期,使: 在 HotSpot JVM 上跑的处理后 JAR,启动更快(仍用 java -jar) 在 GraalVM Native Image 路径上,构建器能拿到闭世界分析所需的元数据 不要和 JDK AOT 混淆 博客与 JDK 发行说明里还有另一类「AOT」,含义不同: 名称 层级 做什么 Spring AOT Spring 框架 / Boot 插件 构建期处理 IoC 容器,生成 bean 与 Native hint GraalVM Native Image AOT GraalVM 工具链 字节码 → 本地机器码 + Substrate VM JDK AOT 缓存(JEP 483 等) HotSpot JVM 训练运行后缓存类加载/链接/剖析,缩短下次 JVM 冷启动 Spring AOT 常与 GraalVM Native Image 配合使用,但不是同一 thing;JDK 24+ 的 AOT 缓存与 Spring AOT 可以并存,也互不替代。JDK 侧见 JDK 25 变更汇总、JVM 编译器。 ...

Created 21216-06-21 · Updated 15156-07-15 · 3 min · 596 words · -

JDK 1.4

JDK 1.4(J2SE 1.4)于 2002 年 2 月发布。对外称 Java 2 Platform, Standard Edition 1.4;命名体系与后续演变见 Java 版本历史。 1.4 GA 后,下一代 Tiger(JDK 5) 的语言特性即在 JCP 并行规划——含 JSR 175 注解(2002 年 3 月立项),并非等 1.4 维护多年后才起步。详见 JDK 5 §发布历程。 注意:jdk14.md 记录的是 JDK 14(2020),与本文的 JDK 1.4 不是同一版本。 版本号说明 维度 JDK 1.4 平台品牌 J2SE 1.4 java.version 1.4.2_19 等 class major version 48 下一版本 JDK 5 完整命名演变见 Java 版本历史。 概览 语言 assert 断言(-source 1.4 编译,运行时 -ea 开启) 类库 java.util.regex 正则表达式(详见 分组与捕获) NIO(java.nio):Buffer、Channel、Selector、内存映射文件 java.util.logging(JUL,Java 原生日志,见 commons-logging 与 log4j、jcl-over-slf4j) java.util.prefs Preferences API(用户/系统配置持久化) URI 与 URL 区分(java.net.URI) 链式异常(Throwable.initCause()) Image I/O、Java Web Start、Sound API 等 JVM 与性能 HotSpot 成为唯一 VM(1.4.0 起移除 Classic VM;HotSpot 自 JDK 1.3 起已是默认实现,见 HotSpot) Parallel Scavenge 新生代收集器(1.4.0);1.4.1 起 GC 调优选项增强(见 Java GC) 1.4.2 引入自旋锁(-XX:+UseSpinning,见 Java Lock) 工具与诊断 JPDA(Java Platform Debugger Architecture)架构完善 assert 断言 JDK 1.4 在语言层新增 assert 关键字,用于开发/测试阶段的条件检查;默认关闭,生产环境通常不启用。语法、-ea / -da 运行参数及设计取舍详见 Java assertion。 ...

Created 20206-06-20 · Updated 15156-07-15 · 3 min · 461 words · -

JDK 1.0

JDK 1.0 于 1995 年 5 月 23 日由 Sun Microsystems 正式发布,是 Java 语言与平台的第一个对外产品版本。命名与后续演变见 Java 版本历史。 1.0 之前还有别的「版本」吗? 没有更早的对外编号版本。 在 1.0 之前只有 Sun 内部的研发代号与预览构建,不算正式产品线: 阶段 时间(约) 说明 Green 项目 1991 起 Sun 内部面向消费电子设备的研究项目 Oak 1991~1995 语言与运行时的内部代号(James Gosling 等) 更名 Java 1995 初 因商标冲突放弃 Oak,改用 Java Alpha / Beta 1995 上半年 面向开发者的预览版,供 Applet 等场景试用 JDK 1.0 1995-05 首个公开发布、可商用的 JDK 因此讨论「Java 最早版本」时,1.0 就是起点;Oak、Alpha、Beta 是开发史里的名称,不是 0.x 或更早的正式版本号。下一版为 JDK 1.1(1997 年 2 月)。 版本号说明 维度 JDK 1.0 对外名称 Java 1.0 / JDK 1.0 java.version 1.0.x class major version 45(1.0.2 起常见为 45.3) 下一版本 JDK 1.1 概览 1.0 已经具备今天仍熟悉的核心语言形态:类与接口、继承、包、final / static / synchronized、异常(含 checked exception)、数组、基本类型与 String。标准库规模很小——大约 212 个类、8 个包,许多今天常用的 API 尚未出现。 ...

Created 20206-06-20 · Updated 15156-07-15 · 2 min · 324 words · -