先说结论:Java 比 C 仅慢 17%,比 Python 快 13 倍,比 C++ 还要快一点 。被骂"慢"骂了 20 年,Java 表示这个锅不背——要背也得 Spring 来背。
GitHub 上有个项目叫 speed-comparison,对 62 门编程语言的运行速度做了基准测试。方式很简单:用莱布尼茨公式算 π,跑 10 亿次迭代,然后掐表。结果公开透明,谁都能复现。
"Java 太慢了"——这话我听了十年。但说这话的人,通常有三个共同特点:拿不出基准数据,分不清语言和框架,用着 JDK 8 评价 JDK 25。
Python 一行代码搞定的事,Java 确实还在定义接口和实现类。但写起来慢和跑起来慢,是两个世界的事 。今天就把"Java 慢"这个持续了二十年的技术谣言,用数据和原理彻底拆掉。
废话少说,上硬菜。
speed-comparison 项目的测试方法很直白:莱布尼茨公式算 π,10 亿次迭代,取中位运行时间。没有框架、没有 IO、没有数据库——纯粹比拼语言本身的计算性能 。
成绩单:
| | |
|---|
| | |
| | |
| Java (GraalVM) | 7.33s | 1.04x |
| | |
| Java (OpenJDK) | 8.26s | 1.17x |
| | |
| | |
| | |
三个数字需要刻到脑子里:
第一,Java(GraalVM)比 C++ 快了 10%。 注意看清楚——不是"差不多",是 Java 赢了。一门跑在虚拟机上的语言,跑分比手写 C++ 还快。
第二,即使是普通的 OpenJDK,也只比 C 慢 17%。 17% 是什么概念?一个请求 C 处理 100ms,Java 处理 117ms。你的用户感知不到任何差异,但你的招聘成本能降一半。
第三,Java 比 Python 快了将近 14 倍。 不是 14%——是 14 倍。Python 跑两分钟的活,Java 不到十秒。但诡异的是,业界对 Python 的性能槽点远没有对 Java 这么猛。大概因为 Python 程序员早就认命了,而 Java 程序员还在被冤枉。
附赠一个暴击:Java 比 Go 快 26% 。Go 天天被吹"高性能并发语言",结果在纯计算场景被 Java 按着打。所以 Go 的优势是什么?冷启动快,二进制小。但论运行时性能——Java 说了算。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
- 视频教程:https://doc.iocoder.cn/video/
数据已经翻了牌,Java 的计算性能碾压绝大多数主流语言。那问题来了:为什么所有人都觉得它慢?
这不是一个技术问题,这是一个"认知破案"问题。嫌疑人有三个,真凶只有一个。
多数人对 Java 性能的认知,形成于 2005-2012 年。那会儿的 Java 什么样?
- JDK 1.4/1.5 ,JIT 编译器还在青春期,优化能力有限
- Serial GC / Parallel GC 是主流,Full GC 动辄停顿 3-5 秒
- Applet 在浏览器里卡成翻页 PPT,成了全网嘲笑的素材
- Swing 桌面程序 的UI响应速度,能让人怀疑人生
这些印象就像胎记一样,烙在了一代程序员的脑子里。
但 Java 这二十年干了什么?JIT 编译器从 C1 进化到了 C2 再到 Graal;GC 从 Serial 迭代到 G1、ZGC、Shenandoah;启动模式从纯解释执行变成了 AOT + CDS + AppCDS + Leyden。JDK 的版本号已经从 1.4 走到了 25,每半年更新一版。 你的认知呢?还停在哪个版本?
用 2005 年的体验评价 2025 年的 JVM,和用翻盖手机的体验评价智能手机,没有任何区别。
很多"Java 好慢"的结论,出自一段随手写的 for 循环。但 JVM 的性能特征和 C/Python 完全不一样——它是一台会学习的机器 ,你测它的方式如果不对,得到的数据就是错的。
陷阱一:你只测了最弱形态。
JVM 执行代码分三档:
第 1-1000 次 → 纯解释执行,逐条翻译字节码(龟速)
第 1K-10K 次 → C1 编译,基础优化(正常速度)
第 10K+ 次 → C2 编译,激进内联 + 分支预测 + 向量化(全力冲刺)
大量的"性能对比"测试,跑了几百次循环就收工了。恭喜,你精准地测到了 Java 最慢的解释执行阶段 ,然后拿它和 C 的编译产物比。这就好比让刘翔从蹲着起跑,然后在他还没站直的时候就吹了终场哨。
陷阱二:JVM 可能把你的测试代码直接删了。
long start = System.nanoTime();
for (int i = 0; i 1_000_000; i++) {
new Object(); // 你以为在测对象创建开销
}
long elapsed = System.nanoTime() - start;
// 实际:JVM 发现这个 Object 没人用,整个循环被优化成了 nop
// 你测到的是"什么都没做"的耗时
JVM 的 C2 编译器会做死代码消除 ——你创建的对象没被引用,循环体被直接当废代码跳过了。你拿着一个 0.01ms 的数字吹"Java 对象创建飞快",其实什么都没测到。
只有
JMH (Java Microbenchmark Harness)才能绕过这些陷阱——它会用 Blackhole 吞掉返回值、强制 fork JVM 进程、控制预热轮数。所以行业铁律是:没跑 JMH 的 Java 性能数据,一律不可信。
终于到真凶了。
我写了一段斐波那契递归,分别用纯 Java 和 Spring Bean 跑:
// ① 纯 Java 直接调用
public long fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
// 耗时:0.5ms
// ② Spring 托管版本
@Service
public class FibService {
@Cacheable("fib")
public long fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
}
// 耗时:15ms
同一段算法逻辑,Spring 版慢了 30 倍。 代码相同、输入相同、硬件相同,唯一的区别是 Spring 在中间插了一堆东西。
这 30 倍性能损耗从哪来的?拆开看:
- CGLIB 动态代理 :Spring 给你的 Bean 套了一层代理类,每次方法调用先走代理再到真实对象
- AOP 拦截链 :
@Cacheable 触发缓存拦截器,走一遍 key 生成 → 缓存查找 → 命中判断的流程 - 反射调用 :代理内部最终通过
Method.invoke() 调用你的方法。反射比直接调用慢 50-100 倍 ,而且 JIT 基本无法优化反射路径
问题还不止于此。Spring 体系下的反射调用无处不在 :
| |
|---|
|
Bean 实例化、属性注入、@Autowired 解析 |
| |
| |
| |
| @RequestParam |
每一个 HTTP 请求进来,从 Controller 到 Service 到 DAO 再到 JSON 响应,中间经历的反射调用可能有几十次到上百次 。这些调用叠加起来,才是你"感觉 Java 慢"的罪魁祸首。
Java 语言无罪。它只是因为生态里最流行的框架太重,被连坐了二十年。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/yudao-cloud
- 视频教程:https://doc.iocoder.cn/video/
既然已经给 Java 正了名,再进一步——Java 不仅不慢,JVM 还藏着几手多数语言做不到的绝活。
暗牌一:JIT 的开卷考试。
C++ 编译器在编译时做优化,属于"闭卷"——它只能根据代码的静态结构猜测哪条分支更热。JVM 的 JIT 是"开卷"——它先让代码跑一阵子,收集真实的运行 profiling 数据,然后针对实际执行路径做定制化编译 。
举个具体的例子:一个 interface Animal 有 Dog、Cat、Fish 三个实现类,但运行数据显示 99% 的调用都是 Dog。C2 编译器会直接把 Dog.eat() 的代码内联到调用点 ,连虚方法查表都省了。如果后来突然来了一只 Cat,JVM 会撤销优化然后重新编译 (deoptimization)。
这种"先观察再重写"的策略,让 JIT 在长时间运行的服务端程序中具备超越静态编译的优化潜力 。这就是前面跑分 Java 干翻 C++ 的底层原因。
暗牌二:逃逸分析 + 标量替换。
public double calculateDistance(double x1,
double y1, double x2, double y2) {
Point p1 = new Point(x1, y1); // 看起来创建了两个对象
Point p2 = new Point(x2, y2);
return p1.distanceTo(p2);
}
JVM 发现 p1 和 p2 不会逃逸出这个方法后,会做两件事:
- 栈上分配 :不在堆上 new 对象,直接用栈上的局部变量
- 标量替换 :把
Point 对象拆散成 x 和 y 两个 double 值
最终这段代码被优化成没有任何对象分配 的纯数值计算。GC 完全不需要介入,性能和手写 C 几乎一样。
暗牌三:ZGC 的亚毫秒停顿。
| | | |
|---|
| 数百毫秒到数秒 | | |
| 10-200ms | | |
| ZGC | < 1ms | 16TB | |
ZGC 用着色指针 给每个对象引用附加了颜色标记位,用读屏障 在访问对象时自动处理并发移动。整个标记-整理过程几乎完全并发,STW 阶段只做极少量的根集扫描。
1ms 的停顿意味着什么?你的 P99 延迟里,GC 贡献的部分可以忽略不计。高频交易系统(LMAX Disruptor)、广告竞价引擎这些对延迟极度敏感的场景,ZGC 已经是生产级方案。
但我在实际项目中见过的大多数团队,GC 配置还是 -XX:+UseParallelGC。然后在监控面板上看到 300ms 的 GC 停顿,得出结论:"Java GC 真是垃圾。"——兄弟,垃圾的是你的 JVM 参数。
吹完了优点,也得说实话。Java 确实有几个地方不如人,不认也不行:
冷启动确实输。 传统 JVM 启动需要加载类、验证字节码、JIT 预热,从进程拉起到提供服务通常需要几秒到几十秒。在 Serverless / Function-as-a-Service 场景下,Go 的 50ms 启动和 Python 的即开即用,确实是碾压级的优势。不过 GraalVM Native Image 已经把启动时间压到了 50ms 以内 ,Quarkus 也在生产环境大规模验证过了。这个短板正在被补上。
内存底噪偏高。 JVM 自身要吃 100-200MB 内存,堆再怎么压也有下限。边缘设备、嵌入式、512MB 内存的容器——这些场景 Go 和 Rust 的确更合适。Java 的设计哲学是"用内存换开发效率和运行时优化空间",这是取舍,不是缺陷。
写起来确实啰嗦。 一个简单的数据类,Python 一行搞定,Go 一个 struct 搞定,Java 要写 getter/setter/equals/hashCode/toString。虽然 Lombok 和 Record(JDK 16+)大幅改善了这个问题,但"Java 啰嗦"的刻板印象仍然深入人心。不过——啰嗦 ≠ 慢 ,这是语法表达力和运行时性能两回事。
下次再有人跟你说"Java 慢",把这篇文章甩给他,然后问三个问题:
如果他答不上来,说明他的观点不是来源于数据——是来源于十年前的段子 。