Java 27 已于 9 月 15 日正式 GA。作为 2026 年第二个功能版本,它包含 9 个 JEP,其中两项变化尤其值得平台团队关注:JEP 523 将 G1 统一为所有环境下的默认垃圾回收器,TLS 1.3 则开始引入后量子加密能力。
这两个变化都发生在基础设施层。业务代码可能一行不改,但应用的停顿特征、CPU 消耗、TLS 握手以及跨系统兼容性仍可能发生变化。因此,升级前不能只确认“能否启动”,还要重新验证运行时基线。
G1 默认值统一,解决的是环境漂移
过去,同一份 JAR 在不同机器、容器镜像或硬件条件下,可能因为 JVM 对默认垃圾回收器的选择不同而表现出不同的延迟和吞吐特征。Java 27 通过 JEP 523 将 G1 设为所有环境的默认 GC,减少了这种由隐式默认值造成的差异。
这项调整的直接收益不是“所有应用突然更快”,而是行为更容易预测:
- 开发、测试和生产环境更容易使用同一种默认 GC;
- 容器迁移或节点规格变化时,不容易意外切换回收器;
- 没有显式配置 GC 的服务,也能获得一致的运行时起点;
- 性能问题排查时,团队少了一个隐含变量。
但默认值仍然不等于最优值。大堆、极低延迟服务、批处理任务和短生命周期命令行程序的目标不同。已经显式使用其他回收器的应用,不应仅因为默认值变化就删除现有参数。
先确认 JVM 实际选择了什么
升级后的第一步,可以直接查看启动日志,而不是根据机器类型猜测:
export JAVA_HOME=/opt/jdk-27
export PATH="$JAVA_HOME/bin:$PATH"
java -version
java -Xlog:gc=info -version
在 Java 27 的默认配置下,GC 日志应显示 JVM 正在使用 G1。生产环境建议保留显式参数,使部署意图不会随着基础镜像或 JDK 版本变化:
java \
-XX:+UseG1GC \
-Xms1g \
-Xmx1g \
-Xlog:gc*,safepoint:file=logs/gc.log:time,uptime,level,tags \
-jar app.jar
显式写出 -XX:+UseG1GC 并非技术上的必需条件,但对需要可复现配置的生产系统很有价值。
用一个小程序建立 Java 27 的 GC 基线
下面的示例会持续制造短期对象,并保留少量数据,以便观察 G1 的回收活动。它只是验证日志和运行时行为的探针,不是正式基准测试。
cat > MemoryProbe.java <<'EOF'
import java.util.ArrayList;
import java.util.List;
public class MemoryProbe {
public static void main(String[] args) throws Exception {
List<byte[]> retained = new ArrayList<>();
for (int i = 0; i < 2_000; i++) {
byte[] block = new byte[512 * 1024];
block[0] = 1;
if (i % 40 == 0) {
retained.add(block);
}
if (i % 200 == 0) {
System.out.printf("iteration=%d, retained=%d MB%n",
i, retained.size() / 2);
Thread.sleep(20);
}
}
System.out.println("done, retained blocks=" + retained.size());
}
}
EOF
javac MemoryProbe.java
java \
-Xms256m \
-Xmx256m \
-XX:+UseG1GC \
-Xlog:gc*,safepoint:file=gc.log:time,uptime,level,tags \
MemoryProbe
grep -E "Using|Pause|Concurrent" gc.log | head -n 30
在真实服务中,建议对升级前后的相同流量窗口比较以下指标:
- GC 暂停的 p50、p95 和 p99;
- 请求延迟的 p95 和 p99;
- GC 前后的堆占用;
- CPU 使用率和分配速率;
- Full GC 次数;
- 容器是否接近内存限制,或出现 OOM Kill。
不要只比较平均暂停时间。一次罕见但很长的暂停,往往比平均值更能解释生产环境的超时。
后量子能力进入 TLS 1.3,不代表连接自动完成升级
Java 27 将后量子加密能力带入 TLS 1.3,是面向未来网络安全的重要变化。不过,从运维角度看,真正需要验证的是协商结果和互操作性。
一次 TLS 连接是否实际采用相应能力,还取决于对端服务器、JDK 安全提供者、配置策略和中间网络设备。仅升级客户端 JDK,并不能证明每条 TLS 1.3 连接都已经获得后量子保护。具体算法名称、启用方式和限制,应以 Java 27 的安全提供者文档及部署环境策略为准。
下面可以用一个最小 Java 客户端确认 TLS 1.3 连接,并打印协商出的协议与密码套件。把示例地址替换成自己的测试端点:
cat > TlsProbe.java <<'EOF'
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import javax.net.ssl.SSLContext;
public class TlsProbe {
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: java TlsProbe https://host/path");
System.exit(2);
}
SSLContext tls13 = SSLContext.getInstance("TLSv1.3");
tls13.init(null, null, null);
HttpClient client = HttpClient.newBuilder()
.sslContext(tls13)
.build();
HttpRequest request = HttpRequest.newBuilder(URI.create(args[0]))
.GET()
.build();
HttpResponse<Void> response = client.send(
request, HttpResponse.BodyHandlers.discarding());
System.out.println("HTTP status: " + response.statusCode());
response.sslSession().ifPresent(session -> {
System.out.println("Protocol: " + session.getProtocol());
System.out.println("Cipher suite: " + session.getCipherSuite());
});
}
}
EOF
javac TlsProbe.java
java TlsProbe https://example.com/
如果需要检查更完整的握手过程,可在隔离的测试环境中启用 JSSE 调试日志:
java \
-Djavax.net.debug=ssl,handshake \
TlsProbe https://your-test-endpoint.example/
调试输出可能包含证书、主机名和连接细节,不宜在生产环境长期打开,也不要未经处理就上传到公共工单或日志平台。
TLS 升级还应覆盖这些场景:
- Java 客户端连接旧版网关或负载均衡器;
- Java 服务端面对不同 JDK、浏览器和移动端客户端;
- TLS 终止位于反向代理,而不是 Java 进程内部;
- 企业代理、安全设备或服务网格会检查并重新建立 TLS;
- 内部服务使用私有 CA 或定制安全提供者。
升级时把默认值变成显式决策
Java 27 的变化降低了默认环境之间的差异,但上线仍应采用分阶段策略:
- 固定 JDK 发行版、构建号和容器镜像摘要,避免测试与生产使用不同构建。
- 记录当前 GC、堆参数、TLS 参数和安全提供者,建立升级前基线。
- 在预生产环境回放代表性流量,同时采集 GC、延迟、CPU 和内存数据。
- 对关键上下游执行 TLS 1.3 互操作测试,不要只测试一个公开网站。
- 先灰度无状态实例,再扩大范围,并准备可快速回退的旧镜像。
- 保留显式 GC 参数和必要的 TLS 策略,避免再次依赖隐含默认值。
G1 成为统一默认值,让 Java 27 的运行时行为更一致;后量子能力进入 TLS 1.3,则让平台开始面对下一阶段的密码迁移。前者需要性能测量,后者需要端到端协商验证。两者都不能只靠版本号完成验收。