Java性能优化基础篇
Java性能优化 · 基础篇
本篇是针对 Java 语言基础内容(常用类、容器、IO、序列化、网络编程)的调优总结,其中的很多内容在 通用系统性能优化 里也提到过,例如充分利用 cpu cache(arraylist、填充字段避免伪共享)、调整 tcp 连接相关参数等。
字符串
String 的实现演进
| 版本 | 底层结构 | 关键变更 |
|---|---|---|
| Java 6 及之前 | char[] + offset + count |
共享数组可导致 substring 内存泄漏 |
| Java 7 ~ 8 | char[] |
取消 offset/count,substring 不再共享数组 |
| Java 9+ | byte[] + coder |
节约内存:Latin-1 用 1 字节,UTF-16 用 2 字节 |
核心要点:String 的不可变性由 final class + private final char[]/byte[] 保证。
不可变性的四个作用
- 安全性:防止恶意篡改
- 哈希稳定:hash 不变,适合作为 HashMap/HashTable 的 key
- 字符串常量池:相同字面量可复用
- 线程安全:多线程下无需同步
优化策略
| 场景 | 推荐做法 | 原因 |
|---|---|---|
| 长字符串拼接 | StringBuilder |
避免循环内反复创建 String 对象 |
| 多线程拼接 | StringBuffer |
线程安全,但有锁开销,性能低于 StringBuilder |
| 大量重复字符串 | String.intern() |
把对象移到常量池复用,节省堆内存 |
| 字符串分割 | 优先 indexOf |
避免 split 内部正则回溯带来的 CPU 风险 |
关于 intern():常量池基于 HashTable 实现,数据量过大时遍历成本高,需结合场景使用。
正则表达式
引擎选型
| 引擎 | 时间复杂度 | 特性 | 代表 |
|---|---|---|---|
| DFA | O(n) | 功能弱、构建昂贵 | 较少使用 |
| NFA | O(ns) | 支持捕获组、环视、占有优先 | Java/JS/Python |
Java 正则基于 NFA,因此回溯是性能问题的根源。
匹配模式
| 模式 | 写法 | 行为 |
|---|---|---|
| 贪婪 | ab{1,3}c |
尽可能多匹配,失败时回溯 |
| 懒惰 | ab{1,3}?c |
尽可能少匹配 |
| 独占 | ab{1,3}+bc |
尽可能多匹配,失败不回溯,直接结束 |
核心结论:能用独占模式解决的问题,不要用贪婪模式;能用懒惰模式时优先懒惰。
性能优化四原则
- 减少贪婪:能用独占就不要用贪婪
- 减少分支:把常用项放前面;提取公共前缀;简单分支可用
indexOf替代(X|Y|Z) - 减少捕获嵌套:用
(?:X)替代(X),降低分组开销 - 避免回溯型灾难:警惕嵌套量词对相同字符重复匹配
集合与 Stream
ArrayList vs LinkedList 选型
| 维度 | ArrayList | LinkedList |
|---|---|---|
| 底层 | Object[] | 双向链表 |
| 随机访问 | O(1) | O(n) |
| 头部增删 | O(n) 数组复制 | O(1) |
| 中间增删 | O(n) 数组复制 | O(n) 查找 + O(1) 修改 |
| 尾部追加(无扩容) | O(1) 最快 | O(1) 但有 new 对象开销 |
| 内存占用 | 紧凑 | 每个元素多 2 个指针 |
反直觉的结论:
- “LinkedList 增删快” — 仅在头部成立;中间位置反而比 ArrayList 慢
- “ArrayList 遍历快” — for 循环最快;迭代器循环两者相近
- 遍历 LinkedList 严禁用 for 循环:每次循环都要遍历半个链表
ArrayList 三个易错点
- 初始化容量:预知大小时显式传入
new ArrayList<>(size),避免多次 1.5 倍扩容 - transient 不等于不可序列化:ArrayList 自定义 writeObject/readObject,只序列化有数据的部分
- 扩容公式:
newCapacity = oldCapacity + (oldCapacity >> 1),即 1.5 倍
Stream
| 操作分类 | 说明 |
|---|---|
| 中间操作 | 懒执行,只构建调用链 |
| 终结操作 | 真正执行计算 |
| 无状态 | 不受前置元素影响(filter/map) |
| 有状态 | 需要全部元素(sort/distinct) |
| 短路 | 满足条件即可终止(find/anyMatch) |
| 非短路 | 必须遍历全部 |
Stream 使用决策树:
1 | |
HashMap 设计与优化
数据结构演进
1 | |
关键参数
| 参数 | 默认值 | 作用 |
|---|---|---|
| initialCapacity | 16 | 哈希表初始大小,必须是 2 的幂 |
| loadFactor | 0.75 | 触发扩容的填充阈值 |
| threshold | 16 × 0.75 = 12 | 实际扩容阈值 |
| TREEIFY_THRESHOLD | 8 | 链表长度超过此值转为红黑树 |
| UNTREEIFY_THRESHOLD | 6 | 红黑树退化回链表的阈值 |
0.75 的来源:在时间和空间成本上的折中 — 过大查询慢,过小空间浪费。
哈希算法
1 | |
- 高 16 位异或低 16 位:打散哈希高位,降低冲突概率
(n - 1) & hash用位运算替代取模:n 为 2 的幂时,等价于 hash % n
优化建议
- 预知容量:
initialCapacity = 预期元素数 / loadFactor + 1,减少 resize - 容量必须是 2 的幂:位运算代替取模;扩容时只需看新增的最高位
- 重写 hashCode:分散性好的 hashCode 能显著降低冲突
- JDK 1.8 扩容优化:通过
(e.hash & oldCap) == 0判断新位置,无需重新计算所有 hash
I/O 模型
传统 I/O 两大瓶颈
- 多次内存复制:用户空间 ↔ 内核空间 ↔ 设备,至少两次拷贝
- 阻塞:read/write 是同步阻塞调用,线程数随连接数线性增长
五种 I/O 模型
| 模型 | 特点 | 适用 |
|---|---|---|
| 阻塞 I/O | 一连接一线程 | 连接少 |
| 非阻塞 I/O | 轮询检查,EAGAIN 错误 | 极少使用 |
| I/O 复用 | select/poll/epoll 单线程管理多连接 | 高并发主流方案 |
| 信号驱动 | 内核 SIGIO 通知 | UDP 场景 |
| 异步 I/O | 内核完成所有操作后通知 | Windows AIO |
select / poll / epoll 对比:
| 维度 | select | poll | epoll |
|---|---|---|---|
| 最大 fd 数 | 1024 | 无上限 | 无上限 |
| 数据结构 | 数组 fd_set | 动态数组 | 红黑树 + 就绪链表 |
| 扫描方式 | 线性扫描 | 线性扫描 | 事件回调 |
| 时间复杂度 | O(n) | O(n) | O(1) |
关于三种多路复用模式更详细的对比可以阅读 IO多路复用不同实现及对比。
NIO 三大组件
1 | |
零拷贝
零拷贝 的主要优势就是减少了数据的拷贝次数与操作系统状态的切换。
- mmap:用户空间与内核空间共享同一块物理内存,避免数据拷贝
- DirectBuffer:JVM 堆外内存,内核可直接访问
- Netty 的 FileRegion、transferTo 底层都基于 mmap/sendfile
Reactor 线程模型演进
1 | |
Tomcat 调优关键参数
| 参数 | 含义 | 默认 | 调优建议 |
|---|---|---|---|
| acceptorThreadCount | Acceptor 线程数 | 1 | 连接量大时调大 |
| maxThreads | Worker 线程数 | 200 | 按业务调整,不是越大越好 |
| acceptCount | accept 队列长度 | 100 | keep-alive 关闭时可调大 |
| maxConnections | 最大连接数 | 10000 | NIO 模式下应远大于 maxThreads |
RPC 通信
关于 RPC 的发展史和约定可以阅读 选择合适的RPC ,详细介绍了 RPC 的核心内容和 SOAP、Rest 发展历程
RMI 三大瓶颈
| 瓶颈 | 来源 |
|---|---|
| Java 默认序列化 | 性能差、不能跨语言 |
| TCP 短连接 | 高并发下连接创建/销毁开销大 |
| 阻塞式 I/O | 线程无法复用 |
高并发 RPC 优化七层路径
协议选型:TCP(可靠)/UDP(局域网高效)
连接模式:服务间使用单一长连接,避免短连接反复握手
通信框架:Netty 替代传统 Socket
报文格式:小报体、易解析
序列化:Protobuf/Hessian
| 框架 | 性能 | 体积 | 跨语言 | 易用性 | 典型场景 |
|——|——|——|——–|——–|———|
| Java Serialization | 差 | 大 | 否 | 高 | 已不推荐 |
| Hessian | 中 | 中 | 是 | 高 | Dubbo 默认 |
| JSON (FastJson/Jackson) | 中 | 较大 | 是 | 高 | 跨语言 API |
| Kryo | 高 | 小 | 否 | 中 | RPC、游戏 |
| Protobuf | 高 | 小 | 是 | 中 | 推荐首选 |
TCP 参数调优:见下表
Linux 内核参数:net.core.somaxconn、tcp_tw_reuse 等
Netty 是 Java 非常强大的网络编程框架,有很多高性能的设计,Netty 系列中对其的无锁串行化、核心架构都有介绍。
TCP 关键参数
| 参数 | 作用 | 调优建议 |
|---|---|---|
| TCP_NODELAY | 关闭 Nagle 算法 | 时延敏感场景关闭 |
| SO_RCVBUF / SO_SNDBUF | 收发缓冲区大小 | 根据包大小调整 |
| SO_BACKLOG | accept 队列长度 | 高并发调大 |
| SO_KEEPALIVE | 长连接保活 | 缩短探测间隔 |
关于 TCP 连接的参数还有很多,可以参考 系统性能优化之TCP篇 的内容~