运维问我们:Ubuntu 服务器上装的 JDK 要不要交钱
11 月底,运维在群里发了张截图,是某篇公众号文章的标题:"Oracle JDK 开始收费,你们公司准备好被起诉了吗"。他问我们的服务器要不要处理。
我把许可协议翻了一遍,发现事情没那么吓人,但确实得做个决定。这篇是整理出来的结论。
到底改了什么
2019 年 4 月 16 日,Oracle 更新了 JDK 8 的许可协议(OTN License Agreement)。在此之前,Oracle JDK 8 个人和企业都能免费用。更新之后:
- JDK 8u201 及以前的版本:老的 BCL 协议,可以免费用于生产环境。
- JDK 8u211 及以后的版本:新的 OTN 协议,用于商业用途需要购买 Oracle 的 Java SE 订阅。个人学习、开发、测试仍可免费。
- JDK 11 及以后:Oracle JDK 从一开始就是 OTN 协议,同样需要付费。
付费的标准(当时的公开报价):桌面端每人每月 2.5 美元,服务器端按处理器核心算,每核心每月 25 美元。我们线上 40 台 8 核机器,算下来一年要 9.6 万美元。这个数字一出来,结论就很明确了。
顺带一提,Oracle 的免费支持节奏也变了:
- JDK 8 的公开更新(免费版)到 2019 年 12 月结束,商业支持到 2020 年 12 月,之后延长支持到 2025 年(但要付费)
- JDK 11 是 LTS,2018 年 9 月发布
- 发布周期从"几年一个大版本"改成了每 6 个月一个版本,每 3 年一个 LTS
关键:收费的是 Oracle JDK,不是 Java
这是很多人(包括一开始的我)搞混的地方。
Java 语言规范和 JVM 规范是公开的,OpenJDK 是它的开源实现,采用 GPL v2 协议(带 Classpath Exception),永久免费,可以用于商业用途。Oracle 收费的是它自己打包发布、带商业支持的 Oracle JDK 这个发行版。
打个比方:Linux 内核是免费的,Red Hat Enterprise Linux 是收费的发行版。Java 也是这个结构——OpenJDK 是上游源码,各个厂商基于它构建自己的发行版。
Oracle 自己其实也参与 OpenJDK 开发,Oracle JDK 和 OpenJDK 的代码库差异非常小(Oracle 从 JDK 11 开始就在努力让两者趋同)。主要区别是:Oracle JDK 多了一些商业特性(比如 Java Flight Recorder 在新版本里已经开源了)、有官方支持和补丁服务。
几个可选的发行版
我把当时主流的几个都装了一遍,跑了我们的服务压测对比(4 核 8 G,跑同一个订单接口 5 分钟):
| 发行版 | 厂商 | 协议 | 免费商用 | 我们的压测 QPS | 备注 |
|---|---|---|---|---|---|
| Oracle JDK 8u212 | Oracle | OTN | 否 | 1,842 | 需订阅 |
| AdoptOpenJDK 8u212 | 社区(IBM/Red Hat 等支持) | GPL v2 | 是 | 1,836 | 社区维护,最"正统" |
| Azul Zulu 8u202 | Azul Systems | GPL v2 | 是 | 1,829 | 老牌,支持多种平台 |
| Amazon Corretto 8u222 | Amazon | GPL v2 | 是 | 1,841 | AWS 自家全量使用 |
| Alibaba Dragonwell 8 | 阿里巴巴 | GPL v2 | 是 | 1,918 | 带阿里内部优化 |
性能差异在 5% 以内,基本是噪音。重要的差别在维护承诺和额外特性。
AdoptOpenJDK
社区项目,2017 年成立,由 IBM、Red Hat、Azul 等公司支持。它的定位是"提供免费、开源、社区维护的 OpenJDK 二进制包"。提供 HotSpot 和 OpenJ9 两种虚拟机可选。
# macOS 用 brew 装很方便
brew cask install adoptopenjdk8
# 或者官网下载 tar.gz
wget https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/.../OpenJDK8U-jdk_x64_linux_hotspot_8u232b09.tar.gz
tar -zxvf OpenJDK8U-jdk_x64_linux_hotspot_8u232b09.tar.gz -C /usr/local/
它的优点是中立、透明,构建脚本全部开源。缺点是没有厂商兜底(虽然背后有大公司),出了问题要靠社区。
Azul Zulu
Azul Systems 做的,是最早的商业 OpenJDK 发行版之一(2008 年就有了)。免费版叫 Zulu,付费版叫 Zulu Enterprise(带支持和认证)。它支持的平台最多,包括一些冷门的操作系统和架构。
Amazon Corretto
Amazon 2019 年 GA 的。特点是 Amazon 自己内部全量使用(上万台服务器跑的就是它),所以更新和维护投入有保障。它承诺会持续提供免费的安全更新,还提供长期的季度更新。AWS 上有一键安装。
Alibaba Dragonwell
阿里 2019 年 3 月开源的,基于 OpenJDK 8,把阿里内部用了多年的优化补丁放了出来。压测里它 QPS 最高(1,918,比 Oracle JDK 高 4.1%),主要是因为里面有几个针对电商场景的优化,比如:JWarmup(预热,提前加载热点方法减少 JIT 编译的冷启动影响)、GCIH(GC Invisible Heap,把对象放到堆外管理)、协程 Wisp(这个我们没敢用)。
我们实际试过 JWarmup,效果挺明显:服务启动后前 5 分钟的 P99 从 1,840 毫秒降到 620 毫秒。原因是它记录了上次运行时 JIT 编译的方法列表,启动时直接加载编译结果,不用重新走解释执行再触发编译的慢路径。
我们最后的选择
结论:新项目用 AdoptOpenJDK 11,存量项目迁移到 AdoptOpenJDK 8。
理由:
- 存量项目我们跑的是 JDK 8 的代码(有大量
javax.xml.bind、sun.misc.Unsafe的依赖),直接升 11 改动太大,先平迁到 OpenJDK 8 解决合规问题。 - 新项目直接用 11,因为它是 LTS,能用上 HTTP Client(
java.net.http)、var局部变量类型推断(这个是 10 加的)、单文件源码直接运行这些新特性。而且 JDK 11 的 G1 比 8 里的版本成熟不少。 - 选 AdoptOpenJDK 而不是别家,是因为它中立、社区活跃、下载快(有国内镜像),而且我们不需要厂商的付费支持。
迁移过程
实际做完只花了两天,比预想的顺利。流程:
# 1. 下载解压
cd /usr/local
wget https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u232-b09/OpenJDK8U-jdk_x64_linux_hotspot_8u232b09.tar.gz
tar -zxvf OpenJDK8U-jdk_x64_linux_hotspot_8u232b09.tar.gz
ln -s jdk8u232-b09 java8
# 2. 改环境变量
export JAVA_HOME=/usr/local/java8
export PATH=$JAVA_HOME/bin:$PATH
# 3. 验证
$ java -version
openjdk version "1.8.0_232"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_232-b09)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.232-b09, mixed mode)
只遇到一个问题:字体渲染。我们有个生成验证码图片的功能,用的是 Font.createFont 加载自定义字体,迁移后报:
java.lang.NullPointerException
at sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1264)
原因是 OpenJDK 的精简版镜像默认没装字体库。装一下就好:
yum install -y fontconfig freetype
fc-cache --force
如果是 Docker 镜像,在 Dockerfile 里加:
FROM adoptopenjdk:8u232-b09-jdk-hotspot
RUN apt-get update && apt-get install -y fontconfig fonts-dejavu \
&& rm -rf /var/lib/apt/lists/*
另外提醒一句,迁移前后最好对比一下 JVM 的默认参数。不同发行版的 GC 默认值是一样的(都是 Parallel GC),但堆大小的自动计算逻辑可能有细微差别。我们用 -XX:+PrintFlagsFinal 导出了一份对比:
java -XX:+PrintFlagsFinal -version > flags.txt 2>&1
两个建议
一、别再挂着"Oracle JDK"的名字混用。有些同事本地开发装的是 Oracle JDK,服务器上是 OpenJDK,虽然代码跑起来一样,但排查问题时会平添困扰。我们统一成了 AdoptOpenJDK,并在 Dockerfile 里固定了版本 tag(不用 latest)。
二、盘点一下存量。运维用 Ansible 扫了所有机器:
ansible all -m shell -a "java -version 2>&1 | head -1" | grep -i oracle
扫出来 63 台还在用 Oracle JDK,其中 11 台跑的是 8u211 之后的版本,属于明确需要付费的范围。这 11 台优先处理。
留个问题
关于《Oracle JDK 收费之后,该选哪个 JDK 发行版》里这个坑,你当时是怎么处理的?欢迎在评论区聊聊你踩过的类似情况。