Skip to content
第 239 / 250 章架构⏱ 12 分钟阅读

第 239 章:性能调优 - JVM

学习目标

  • JVM 内存模型
  • GC 调优策略
  • 线程与锁优化
  • 编写高性能代码

一、JVM 内存模型

1.1 堆分代

区域特点GC 算法
Eden新对象创建Minor GC
Survivor S0/S1短期存活Minor GC
Old长期存活对象Major GC / Full GC

1.2 内存配置

bash
# 启动参数
java -Xms4g -Xmx4g \
     -XX:MaxMetaspaceSize=512m \
     -XX:ReservedCodeCacheSize=256m \
     -jar myapp.jar
参数说明建议
-Xms初始堆= -Xmx
-Xmx最大堆机器内存 60%
-Xmn年轻代大小= 堆 1/3 ~ 1/4
-XX:MaxMetaspaceSizeMetaspace256M ~ 1G
-XX:ReservedCodeCacheSizeJIT 代码缓存256M

1.3 对象生命周期

二、垃圾回收器

2.1 回收器对比

回收器算法暂停适合
Serial串行单核
Parallel并行吞吐优先
CMS标记清除已废弃
G1区域分代可控默认推荐
ZGC染色指针< 1ms大堆低延迟
Shenandoah转发指针< 1ms大堆低延迟

2.2 G1 默认 / 通用

bash
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200      # 目标停顿
-XX:G1HeapRegionSize=16m     # 区域大小
-XX:+G1UseAdaptiveIHOP       # 自适应堆占用阈值

2.3 ZGC(超低延迟)

bash
-XX:+UseZGC
-XX:+UnlockExperimentalVMOptions
-XX:ZCollectionInterval=120

适合:

  • 大堆(8GB+)
  • 延迟敏感(微服务)
  • JDK 11+

2.4 选型决策

三、GC 调优

3.1 调优原则

  1. 优先级:内存足够 > GC 调优
  2. 不该:频繁 Full GC
  3. 关注:Young GC 频率 + Full GC 时长

3.2 目标

指标目标
Young GC几秒一次 ~ 几十秒一次
Young GC 耗时< 50 ms
Full GC0 次每小时 / 极少
Full GC 耗时< 500 ms
堆利用率< 75%

3.3 日志分析

bash
# 启动参数
-Xlog:gc*,gc+heap=trace,gc+age=trace,safepoint:file=gc.log:time,level,tags

# 使用工具
# 在线分析:GC Easy / GCEasy.io
# 本地工具: GCViewer, JClarity Censum

3.4 常见 GC 问题

Young GC 频繁

症状: Minor GC 每秒多次
原因: 短生命周期对象太多 / 年轻代太小
方案:
  - 调大 -Xmn
  - 复用对象避免高频创建
  - 减少大对象

Full GC 频繁

症状: 几秒一次 Full GC,停顿几百 ms
原因:
  - System.gc() 被调用
  - 大对象直接进 Old
  - 内存泄漏 / 长期引用未释放
  - Perm 区小(老版本)
方案:
  - 排查代码:不用 System.gc
  - 检查内存泄漏:jmap + MAT
  - 调大堆

GC 暂停过长

症状: 单次 GC > 1 秒
方案:
  - 换 ZGC 接近 0 暂停
  - 减少大对象分配
  - 调整 G1 region 大小

四、JVM 参数模板

4.1 通用 Java 应用

bash
java \
    -Xms4g -Xmx4g \
    -XX:MaxMetaspaceSize=512m \
    -XX:ReservedCodeCacheSize=256m \
    -Xss512k \
    -XX:+UseG1GC \
    -XX:MaxGCPauseMillis=200 \
    -XX:G1HeapRegionSize=16m \
    -XX:+ParallelRefProcEnabled \
    -XX:+UseStringDeduplication \
    -XX:+HeapDumpOnOutOfMemoryError \
    -XX:HeapDumpPath=/tmp/heap.hprof \
    -XX:+UnlockDiagnosticVMOptions \
    -XX:NativeMemoryTracking=summary \
    -Xlog:gc*,gc+heap=trace,safepoint:file=/tmp/gc-%t.log:time,level,tags:filecount=10,filesize=100m \
    -jar myapp.jar

4.2 JVM TLAB

yaml
TLAB: Thread Local Allocation Buffer
- 每个线程独占,分配对象无需同步
- 减少 Eden 竞争
- 默认开,可调大小

五、内存溢出定位

5.1 OOM 类型

5.2 排查步骤

bash
# 1. 看进程是否 OOM 退出
dmesg | grep -i "kill"
ls -lh /tmp/heap.hprof

# 2. 用 MAT 分析 heap 文件
# 下载 MAT 工具,启动,File -> Open Heap Dump

# 3. 在 MAT 中:
#    - Leak Suspects Report
#    - Dominator Tree
#    - 找 Retained Heap 大的对象

5.3 内存分析工具

工具用途
jmapdump 堆
jstack线程栈
jstatGC 监控
jcmd综合
async-profiler火焰图
Arthas在线诊断
VisualVM桌面工具

5.4 内存泄漏常见场景

java
// 1. 静态集合持有
private static Map<Long, Object> cache = new HashMap<>();
cache.put(id, obj);   // 永不释放

// 2. 长生命周期对象引用短生命周期
private static List<Object> list = new ArrayList<>();
public void add(Object o) { list.add(o); }

// 3. 未关闭资源
Connection conn = ds.getConnection();
// 忘记 close()

// 4. ThreadLocal 不清理
threadLocal.set(obj);
// 不 remove,线程复用时仍持有

5.5 ThreadLocal 内存泄漏

java
@Component
public class UserContext {

    private static final ThreadLocal<User> CURRENT = new ThreadLocal<>();

    public static void set(User user) {
        CURRENT.set(user);
    }

    public static User get() {
        return CURRENT.get();
    }

    // 在请求结束清理,避免线程复用泄漏
    public static void clear() {
        CURRENT.remove();   // 必须 remove!
    }
}

// Spring 拦截器
Interceptor:
  preHandle() -> UserContext.set(currentUser);
  afterCompletion() -> UserContext.clear();

六、线程与并发调优

6.1 线程池参数

yaml
默认 ThreadPoolExecutor:
  corePoolSize: cpu * 2
  maxPoolSize: cpu * 4
  queueCapacity: 1000

计算公式:
  - IO 密集型:cpu * 2 ~ 4 (大部分时间等 IO)
  - CPU 密集型:cpu + 1
  - 混合:压测定

6.2 优化 Tomcat

yaml
server:
  tomcat:
    threads:
      max: 400              # 最大工作线程
      min-spare: 50         # 最小空闲
    accept-count: 200       # 等待队列
    max-connections: 8192
    connection-timeout: 20s

6.3 优化连接池(HikariCP)

yaml
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      auto-commit: false     # 关闭自动提交

6.4 减少锁竞争

java
// ❌ 全局锁
private static final Object lock = new Object();
public synchronized void process() { ... }

// ✅ 分段锁(ConcurrentHashMap 思想)
private final Object[] locks = new Object[16];
public void process(long key) {
    synchronized (locks[(int)(key & 15)]) { ... }
}

// ✅ 读写锁
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void read() {
    rwLock.readLock().lock();
    try { ... } finally { rwLock.readLock().unlock(); }
}

// ✅ 乐观锁(CAS,无锁)
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();

// ✅ 不可变对象(无锁)
public final class ImmutableValue {
    private final int value;
}

七、内存可见性

7.1 JMM 内存模型

特性说明
可见性一线程修改,另一线程能看到
原子性一组操作不可分割
有序性重排序不影响单线程

7.2 volatile

java
private volatile boolean running = true;

void stop() { running = false; }

特性:

  • 可见性:写立即刷新到主内存
  • 禁止重排序
  • 不保证复合原子性

7.3 synchronized

java
private final Object lock = new Object();
private int count = 0;

public void increment() {
    synchronized (lock) {
        count++;
    }
}

JDK 1.6 后引入锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

八、JIT 编译器

8.1 C1 vs C2

yaml
C1 (Client):
  - 启动快
  - 优化少
  - 桌面应用

C2 (Server):
  - 启动慢
  - 激进优化(内联/逃逸分析)
  - **服务端默认**

TieredCompilation:
  - C1 渐进 → C2
  - JDK 7+ 默认

8.2 热点代码识别

bash
# 打印 JIT 编译日志
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintCompilation
-XX:+LogCompilation

8.3 方法内联

java
// 短方法被内联(JIT 自动)
public int add(int a, int b) { return a + b; }

8.4 逃逸分析

java
public String concat(List<String> list) {
    // JIT 优化:不开新对象,栈上分配
    StringBuilder sb = new StringBuilder();
    for (String s : list) sb.append(s);
    return sb.toString();
}

九、性能案例

9.1 案例 1:大对象频繁创建

java
// ❌ 大量临时数组
public byte[] process(byte[] input) {
    byte[] result = new byte[input.length * 2];
    // ...
    return result;
}

// ✅ 对象池或复用
private final ThreadLocal<byte[]> BUFFER = ThreadLocal.withInitial(() -> new byte[2048]);
public byte[] process(byte[] input) {
    byte[] buf = BUFFER.get();
    // ...
    return Arrays.copyOf(buf, length);
}

9.2 案例 2:对象字段顺序

java
// ❌ padding 浪费空间
class Data {
    boolean a;   // 1 byte
    long b;      // 8 byte (对齐)
    boolean c;   // 1 byte (对齐)
    long d;      // 8 byte (对齐)
}
// size: 32 bytes

// ✅ 字段排序紧凑
class Data {
    long b;      // 8 byte
    long d;
    boolean a;   // 1 byte
    boolean c;
}
// size: 24 bytes

9.3 案例 3:字符串拼接

java
// ❌ 多次拼接
String result = "";
for (String s : list) {
    result += s;       // 创建多个中间对象
}

// ✅ StringBuilder
StringBuilder sb = new StringBuilder();
for (String s : list) {
    sb.append(s);
}
String result = sb.toString();

十、性能监控指标

yaml
JVM 指标:
  - jvm_memory_used_bytes
  - jvm_memory_max_bytes
  - jvm_gc_pause_seconds_count
  - jvm_gc_pause_seconds_sum
  - jvm_threads_states_threads

应用指标:
  - JVM uptime
  - thread count
  - loaded classes count
  - heap size

导出方式:
  - /actuator/prometheus
  - JMX

工具:
  - JMX exporter
  - VisualVM
  - async-profiler

十一、本章小结

主题关键
内存合理分代,避免大对象
GCZGC/G1,关注暂停
线程池化、无锁、减少竞争
JIT短方法、逃逸分析
排查jmap + MAT

动手练习

  1. 用 jmap dump 一次堆,用 MAT 分析大对象
  2. 用 async-profiler 打火焰图,找 CPU 热点
  3. 写一段代码,测试线程池合理大小(压测)
  4. 配置 G1 启动参数,分析 GC 日志

下一章:第 240 章:性能调优 - 数据库

本站基于 VitePress 构建 · 由 Codebook 团队维护