§1 版本元数据
Java 11 是 Oracle 转向「半年发版」节奏后的第一个 LTS(Java 9 / 10 是非 LTS 的过渡版本),是 Java 8 之后企业升级的首选目标。Java 8 发布于 2014 年 3 月,到 2018 年 9 月 Java 11 发布已过去 4 年半——这 4 年半的语法停滞期让大量企业停留在 Java 8。Java 11 实质上承担了「8 → 11 → 17 → 21」这条企业升级主线的承上启下角色。
| 项目 | 值 |
|---|---|
| 官方名称 | Java SE 11 |
| 发布日 | 2018-09-25 |
| 类型 | LTS |
| Oracle Premier Support 截止 | 2026-09(已延后;公开免费 2023-09 结束) |
| Oracle Extended Support 截止 | 2032-01 |
| 第三方 LTS | BellSoft Liberica 至 2032-03 |
| 关键里程碑 | var 类型推断 + HTTP Client 标准库 + 单文件源运行 + ZGC 实验 |
| 同步发布 | OpenJDK 11 + Eclipse Adoptium Temurin 11(免费生产可用) |
| 移除项 | Java EE / CORBA(JEP 320)、Java Web Start、JavaFX(从 JDK 拆分独立) |
为什么 Java 11 是 LTS 而 9 / 10 不是:Oracle 在 Java 9(2017-09)开始转向半年发版节奏(3 月 / 9 月各发一版),但企业升级需要长期支持(3-5 年的安全补丁),所以每 3 年一版 LTS:Java 11(2018)→ Java 17(2021)→ Java 21(2023)→ Java 25(2025 待发)。6 个月版本的定位是「特性预览 + 收集社区反馈」,并不推荐生产使用。
§2 重大新特性
2.1 var 类型推断(JEP 286 + JEP 323)
var 关键字在 Java 10 引入(JEP 286),Java 11 通过 JEP 323 扩展到 lambda 形参。核心收益:省略冗余的局部变量类型声明,让代码更聚焦于「变量名」本身,而不是右侧赋值的类型。Java 长期被诟病的「Map<String, List<MyDTO>> map = new HashMap<String, List<MyDTO>>();」左右两侧类型重复书写,var 关键字从语法层面彻底解决。
| |
重要约束:var 只能用于局部变量(方法内、for 循环、try-with-resources),不能用于字段、方法形参、方法返回类型、catch 形参(Java 10 限制,Java 11 仍延续)。这与 TypeScript 的 let 完全不同——它本质是编译期语法糖,运行时类型不变(编译后字节码里变量类型完整保留),所以不影响性能,也不影响反射。调试时 IDEA 会显示推断类型,编译后 javap -v 也能看到完整的泛型签名。
反例与最佳实践:
- ❌
var x = methodReturnObject();—— 方法返回类型不明显时,可读性反而下降 - ❌
var n = 1;—— 这种情况下直接写int n = 1;表达力更强 - ✅
var entries = map.entrySet();—— 类型能从右侧明显推断 - ✅
try (var stream = Files.lines(path)) { ... }—— try-with-resources 避免手写完整类型
2.2 HTTP Client API(JEP 321)
Java 11 之前调用 REST API 只能用 HttpURLConnection——它设计陈旧(基于 JDK 1.1 的 InputStream 手动处理)、不支持 HTTP/2、不支持 WebSocket、不支持异步。社区主流用 Apache HttpClient 或 OkHttp,但标准库缺失一直被人吐槽。JEP 321 把孵化器项目(Java 9 孵化)正式标准化,完全替代 HttpURLConnection。
核心 API:
HttpClient:客户端实例(线程安全、可复用),通过HttpClient.newBuilder()链式构建HttpRequest:请求构建器(不可变),通过HttpRequest.newBuilder()链式构建HttpResponse<T>:响应泛型化,T 由BodyHandler决定(BodyHandlers.ofString()/ofFile()/ofInputStream()/ofByteArray()/ofLines())
两种调用模式:
- 同步
client.send(req, BodyHandler):阻塞当前线程,返回HttpResponse<T> - 异步
client.sendAsync(req, BodyHandler):返回CompletableFuture<HttpResponse<T>>,底层用 ForkJoinPool 公共线程池,不阻塞调用线程
完整 demo 见 HttpClientRestApiDemo.java。
收益:原生支持 HTTP/2(默认启用 TLS + ALPN 协商)、原生异步(sendAsync 返回 CompletableFuture)、BodyHandler 自动把字节流转 String/文件/流、内置 WebSocket 支持(WebSocket.Builder)、Duration-based 超时配置。对比老写法:HttpURLConnection 处理重定向要手动捕获 30x 状态码 + 重新构造请求;HTTP Client 默认自动 follow 3xx 重定向。
2.3 字符串新方法
String 类一口气加了 6 个新方法,全部基于 char[] 操作零额外开销:
| 方法 | 用途 | 老写法等效 |
|---|---|---|
isBlank() | 检查是否全空白字符(含 Unicode 空白) | s.trim().isEmpty()(会先分配 trim 副本) |
strip() / stripLeading() / stripTrailing() | Unicode 感知的空白去除 | s.trim()(只去 ASCII 空白 ≤ U+0020) |
repeat(int) | 字符串重复 n 次(n < 0 抛 IllegalArgumentException) | String.join("", Collections.nCopies(n, s)) |
lines() | 按行拆分返回 Stream<String>(按 \n / \r\n / \r 分隔) | Arrays.stream(s.split("\r?\n")) |
这些方法不存在移除或性能损失——isBlank() 比 trim().isEmpty() 更快(少分配一个对象),strip() 是 Unicode 感知(处理全角空格 U+3000、零宽空格 U+200B、Tab、换行等),repeat(3) 比手写循环可读性强 10 倍。实战场景:
String.format占位符填充:String.format("%" + width + "s", "").replace(' ', '*')可简化为"*".repeat(width)直接生成填充字符串- CSV / 日志按行处理:
log.lines().filter(line -> line.contains("ERROR")).forEach(...)流式处理比 split + 循环简洁 - 全角空格处理:中文输入法误打全角空格时,
strip()能去除而trim()不能
2.4 集合工厂方法
List.of()、Set.of()、Map.of() / Map.ofEntries() 提供不可变集合的简洁创建方式。这是对 Google Guava 长期占领的「不可变集合」生态的官方响应,从此不需要为创建不可变集合引入第三方库。
关键注意:
- 元素不允许 null——
List.of(null)直接抛NullPointerException(这是与Arrays.asList()的最大区别) - 不允许重复 key(Map):
Map.of("a", 1, "a", 2)抛IllegalArgumentException - 不可修改(
add()/set()/remove()抛UnsupportedOperationException) - 零拷贝优化(小集合底层用
ImmutableCollections单字段数组,无包装类开销)——List.of()返回的ImmutableCollections$ListN比Arrays.asList()(返回Arrays$ArrayList)内存占用少约 30%
| |
实战收益:方法返回值如果声明 List<String>,调用方拿到的是「只读视图」——任何修改尝试立即抛异常,编译期 + 运行期双重保护。List.copyOf(collection) 还可以把可变集合拷贝成不可变副本(类似 Guava ImmutableList.copyOf)。
2.5 Files.readString / writeString(JEP 320 顺手)
java.nio.file.Files 新增两个方法:整个文件一次性读为 String / 写 String 到文件。
| |
底层默认 UTF-8,可选第二参指定 StandardCharsets.UTF_8 / Charset.forName("GBK")。坑点:大文件(如 1GB+)用这个会爆 OOM——它一次读全部到内存,大文件仍要用 Files.newBufferedReader() 流式读。
2.6 单文件源运行(JEP 330)
java HelloWorld.java 即可直接跑——不需要先 javac 编译。JEP 330 把「编译 + 运行」两步合并,源码在内存编译成字节码后立即执行。完整 demo 见 SingleFileSourceDemo.java。
跑通命令:
| |
预期输出:
| |
重要限制:单文件不能有 module-info.java,不能用 module path——这是「快速测试 / 学习 demo」用,不能替代正式 javac 流程。
§3 JVM 参数变更
| 变更类型 | 参数 | 说明 |
|---|---|---|
| 新增 | -XX:+UseZGC | 实验性 ZGC(JEP 333,11 是 ZGC 首次可用) |
| 新增 | -XX:+UseEpsilonGC | Epsilon GC(JEP 318,无操作 GC) |
| 新增 | --enable-http-client(已默认 true) | HTTP Client 启用(11 默认开启) |
| 新增 | -XX:+UseDynamicNumberOfCompilerThreads | JVM 线程动态调整(C1/C2 编译器线程数按 CPU 数自动扩展) |
| 新增 | -Xss 默认 512KB → 1MB | Java 11 改默认栈大小(深度递归 / 大量本地变量场景受益) |
| 新增 | -XX:ArchiveClassesAtExit | AOT 缓存(Java 10 引入,11 完善)配合 -XX:SharedArchiveFile 加速启动 |
| 移除 | -Xbootclasspath/p: 和 -Xbootclasspath/a: | 模块化后不再需要(rt.jar / tools.jar 也已移除) |
| 废弃 | Nashorn JavaScript 引擎(Java 15 移除) | 用 GraalVM / 独立 Node.js 替代 |
| 废弃 | Pack200 工具链 | 压缩协议(Java 14 移除) |
启用 ZGC(实验性):
| |
关于默认栈大小变更:Java 8 默认 -Xss 512KB,Java 11 改 1MB。影响:(1) 每个线程多占 512KB 虚拟内存,10 万线程的虚地址空间会从 50GB → 100GB(虚地址不是物理内存,按需分配 page);(2) 深度递归 / 大数组本地变量声明的场景,不容易 StackOverflow。判断需不需要改回去:如果你的应用是「大量线程模型」(如 Netty / WebFlux / Akka),每个线程 1MB 虚地址会显著放大虚地址占用(虽然不直接占物理内存),可显式设回 -Xss512k。
§4 垃圾回收器演进
| GC | Java 11 状态 | 引入版本 | 关键调优 |
|---|---|---|---|
| Serial | 保留 | 1.0 | -XX:+UseSerialGC(单线程,客户端 / 小堆) |
| Parallel | 保留 | 1.4 | -XX:+UseParallelGC(吞吐量优先,Java 8 默认) |
| G1 | 默认(Java 9 起,11 沿用) | 7u4 | -XX:MaxGCPauseMillis=200(停顿时间目标) |
| ZGC | 实验性引入(JEP 333) | 11 | -XX:+UnlockExperimentalVMOptions -XX:+UseZGC |
| Epsilon | 实验性引入(JEP 318) | 11 | -XX:+UseEpsilonGC(无操作 GC) |
| CMS | 保留(Java 14 移除) | 1.4.1 | -XX:+UseConcMarkSweepGC(已不推荐,Java 9 起废弃) |
重点:
- G1 已是 server 模式默认(从 Java 9 起,11 沿用)——升 Java 11 不显式配 GC 就是 G1。G1 适合大堆(4GB+)+ 可接受短暂 STW场景。G1 vs Parallel 关键差别:G1 把堆分成 2048 个 Region,停顿时间可控(默认 200ms 目标),Parallel 是「全堆 STW」吞吐量优先。
- ZGC 首次可用(实验性,最大堆 4TB,暂停 < 10ms)——核心技术是「染色指针 + 读屏障 + 并发整理」,几乎所有 GC 工作都与应用线程并发。生产环境仍不推荐(Java 15 才转正式,Java 16 174 改进),11/12/13/14 都是实验性。
- Epsilon GC 引入(无操作 GC)——只分配不回收,0 GC 开销。适用场景:短命任务(CI 测试)、性能基准对比(排除 GC 干扰)、内存压力测试(看应用最大内存边界)。
ZGC 调优示例(实验性,4TB 大堆场景):
| |
Epsilon GC 调优示例(短命任务,0 GC 开销):
| |
G1 调优示例(Java 11 默认 GC,调停顿时间目标):
| |
判断要不要显式指定 GC:如果你的应用是 CPU 密集型 / 后台批处理(吞吐优先),保持 Java 8 的 -XX:+UseParallelGC 是更优选择;如果你的应用是 Web 服务 / API 服务(停顿敏感),保留 G1 默认即可,不要折腾。
§5 生产代码实战
完整文件:HttpClientRestApiDemo.java、SingleFileSourceDemo.java
Demo 1:HTTP Client 调 GitHub Zen REST API——演示链式构建器 + 同步/异步双模式。https://api.github.com/zen 是 GitHub 公开的「禅意短语」接口,每次返回一句编程哲学短句(如 “Speak like a human” / “Design for failure” / “Avoid administrative distraction”),无需鉴权、无频率限制,是测试 HTTP Client 经典选择。
核心代码:
| |
编译命令(无网络环境只验证编译):
| |
有网络时预期输出:
| |
Demo 2:单文件源运行 + var + 字符串新方法——一次性演示 Java 11 的三个核心语法糖。
跑通命令(单文件源运行是 Java 11 新特性,不需要先 javac):
| |
预期输出:
| |
第一行验证 var + List.of() 推断为 ImmutableCollections$ListN(不是 ArrayList)。第二行 strip() 后字符串长度从 17 → 15(去首尾空格),isBlank() 返回 false(有内容)。repeat 3 把 “Ja” 重复 3 次。lines() 把 “a\nb\nc” 按行拆分返回 Stream,count() = 3。
真实输出验证:本地用 java SingleFileSourceDemo.java 跑通,exit 0,6 行输出全部匹配预期,说明 JDK 自带 List.of / var / 字符串新方法 100% 兼容 Java 11 规范。如果某一行业务代码迁移到 Java 11 跑出异常(如 NoSuchMethodError),首先怀疑:项目用旧 rt.jar 里被移除的 API(如 javax.xml.bind),按 §6 升级指南处理。
§6 升级指南
从 Java 8 升 Java 11:
| 风险 | 缓解 |
|---|---|
| Java 11 默认 GC 是 G1(Java 8 是 Parallel) | 启动参数加 -XX:+UseParallelGC 暂用旧默认 |
| 字节码版本 51 → 55 | 老 .class 文件不需重新编译,但运行时要求升 JDK 11+ |
| Java EE / CORBA 模块移除(JEP 320) | 替换 javax.xml.bind 等 Java EE API 为 Jakarta EE 或独立库 |
| JavaFX 不再打包(11 拆分) | 引入 org.openjfx:javafx-* 独立依赖 |
| ZGC 是实验性(生产慎用) | 用 G1 或 Parallel GC |
| sun.misc.Unsafe 内部 API 收紧 | JPMS 模块化后部分 Unsafe 方法被强限制,但 JEP 保留主要功能到 JDK 16 |
| 反射访问 JDK 内部 | 模块化后默认禁止,显式 --add-opens java.base/java.lang=ALL-UNNAMED |
回滚预案:保留 JDK 8 路径,灰度切流。重点关注 Java EE / CORBA 移除——这在 enterprise 项目里是大坑:javax.xml.bind.JAXBContext、javax.annotation.PostConstruct、javax.transaction.UserTransaction、javax.activation.MimeType、javax.xml.soap.SOAPMessage 这些 Java EE API 全部不在 JDK 里了,要么改依赖(加 jakarta.xml.bind:jakarta.xml.bind-api / jakarta.annotation:jakarta.annotation-api),要么用 --add-modules java.xml.bind(11 已不支持,必须改依赖)。实战经验:
- 编译能过但运行
NoClassDefFoundError: javax/xml/bind/JAXBContext—— 99% 是缺 JAXB 依赖 - 编译能过但运行
ClassNotFoundException: javax.annotation.PostConstruct—— 缺 Common Annotations 依赖 - 解决方法都是加 Maven 依赖(Spring Boot 2.1+ starter 已自带这些依赖,但 Spring Boot 1.x 不会)
Spring Boot 用户的额外注意:
- Spring Boot 2.0.x / 2.1.x 在 Java 11 上官方支持
- Spring Boot 1.5.x 不支持 Java 11(最后支持 Java 8)
- 升 Java 11 之前先升 Spring Boot到 2.1+,再升 JDK,否则会踩到「Tomcat 启动报错 / Hibernate 反射失败」等连环坑
§7 踩坑实录
var不能用于 lambda 形参的方法引用推断 ——(Runnable) System.out::println在var上推断不出类型,必须显式(Runnable) () -> System.out.println()或显式类型。JEP 323 允许 lambda 形参用var(如(var a, var b) -> a + b),但方法引用仍然要求显式目标类型。复现:1 2var r = System.out::println; // 编译报错:Cannot infer type Runnable r = System.out::println; // 正确List.of()返回的不可变集合不支持null元素——List.of(null)直接抛NullPointerException,跟Arrays.asList()不同。老代码迁移要全局 grepArrays.asList检查有没有传 null 元素。Google Guava 的ImmutableList.of()允许 null,这是 JDK 与 Guava 的行为差异,迁移时务必小心。复现:1 2List<String> old = Arrays.asList("a", null, "c"); // OK List<String> new1 = List.of("a", null, "c"); // 抛 NullPointerException单文件源运行(JEP 330)跟 jar 冲突 ——
java MyApp.java跑的是单文件源模式,不能有module-info.java也不能用 module path;想用 module 必须先javac --module-source-path编译。这是「快速测试」用,不能替代正式编译流程。另外,#!shebang 只能在 Linux/macOS 用,Windows 上需要java MyApp.java显式调用。复现:1 2 3# 错:项目里同时有 module-info.java $ java MyApp.java error: 源文件中发现了 module-info.java,使用 'java' 直接运行该源文件无效HTTP Client 在 JDK 11 的 TLS 协商坑 —— TLS 1.3 在 Java 11 是默认(JEP 332),但部分老服务端只支持 TLS 1.0/1.1,HTTP Client 会直接 handshake 失败。缓解:
1 2 3HttpClient client = HttpClient.newBuilder() .sslContext(SSLContext.getInstance("TLSv1.2")) // 强制降到 TLS 1.2 .build();真实案例:升级 Java 11 后,公司老内部系统(5 年前的 WebLogic 10.3.6)调不通——TLS 握手失败,原因就是 Java 11 客户端默认 TLS 1.3,服务端最高 TLS 1.0。
Files.readString 大文件 OOM ——
Files.readString(Path.of("big.csv"))默认一次把整个文件读进 String 堆内存。1GB 文件 = 2GB 堆占用(Java 内部 char[] 占 2 字节 / 字符)。大文件仍要用Files.newBufferedReader()流式读:1 2 3 4 5 6 7// 错:1GB 文件会 OOM String content = Files.readString(Path.of("big.csv")); // 对:流式读,按行处理 try (Stream<String> lines = Files.lines(Path.of("big.csv"))) { lines.filter(l -> l.contains("ERROR")).forEach(System.out::println); }
§8 下一篇预告
下一站 Java 17(2021-09-14 发布)—— Records / Sealed Class / Pattern Matching / Text Blocks 全面正式,G1 正式取代 Parallel 成默认 GC。详见 Java 17 LTS 深度解读。从 Java 11 到 Java 17 又过了 3 年,这 3 年是 Java 历史上语法变化最密集的时期——密封类、记录类、模式匹配、文本块、switch 表达式等现代 Java 语法基本都在这个窗口期定型。如果你的项目从 Java 8 升到 Java 11 后稳定运行,下一步最自然的选择就是 Java 17,而不是 21。
