16、性能优化:守护进程配置、并行构建、构建缓存、配置缓存、增量编译

说实话,Gradle 刚出来那会儿,很多人吐槽它慢。我自己也经历过——一个中型项目,每次构建都要等两分钟,喝口水回来还没跑完。后来我深入研究才发现,不是 Gradle 慢,而是我们没把它调教好。

这一章,我就把压箱底的性能优化技巧掏出来。五个核心方向:守护进程、并行构建、构建缓存、配置缓存、增量编译。每一个我都踩过坑,也找到了最佳实践。

Gradle 性能优化五大支柱 性能 优化 守护进程 常驻 JVM 进程 并行构建 多任务并发 构建缓存 复用输出产物 配置缓存 跳过配置阶段 增量编译 减少启动开销 利用多核CPU 避免重复编译 加速配置阶段 只编译变更文件

16.1 守护进程:别让 JVM 反复重启

Gradle 守护进程,说白了就是一个常驻后台的 JVM 进程。你第一次构建时启动它,后续构建直接复用。我见过太多团队,每次构建都重新加载整个 Gradle 运行时,那感觉就像每次开车都重新打火一样——费油又费时。

核心配置(gradle.properties):

# 开启守护进程(默认就是 true)
org.gradle.daemon=true

# 设置守护进程的堆内存,我习惯给 2GB
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m

# 守护进程空闲超时(毫秒),默认 10800000(3小时)
org.gradle.daemon.idletimeout=3600000

我的经验:守护进程的堆内存别设太大,也别太小。2GB 对于大多数中型项目足够了。设太大反而会跟其他应用抢内存,导致系统卡顿。

守护进程的生命周期管理:

  • --daemon:显式启用守护进程
  • --no-daemon:临时禁用(调试时有用)
  • --stop:停止所有守护进程
  • --status:查看守护进程状态

我曾经踩过的坑:有一次项目构建突然变慢,排查了半天才发现是守护进程的内存被耗尽了。原来有个同事在 CI 脚本里忘了设置 --no-daemon,导致多个构建任务共享同一个守护进程,互相干扰。后来我强制在 CI 里用 --no-daemon,本地开发才用守护进程,问题就解决了。

16.2 并行构建:让多核 CPU 忙起来

现在的笔记本至少 4 核 8 线程,但默认情况下 Gradle 是串行构建的。你想想看,这不是浪费资源吗?

并行构建的核心思想是:把没有依赖关系的任务放到不同线程里同时跑。比如编译 module A 和 module B,如果它们互不依赖,完全可以并行。

开启并行构建:

# gradle.properties
org.gradle.parallel=true

# 控制并行线程数(默认是 CPU 核心数)
org.gradle.workers.max=4

什么时候该用并行?

  • 多模块项目(模块间依赖少)——效果最明显
  • 单模块但任务多(比如同时跑 lint、test、compile)
  • CI 服务器上——充分利用服务器资源

什么时候不该用?

  • 单模块小项目——并行开销可能大于收益
  • 模块间依赖链特别长——并行度有限
  • 内存紧张——并行会消耗更多内存

我个人习惯:先在本地用 --parallel 跑一次,对比一下时间。如果提速超过 20%,就永久开启。如果只快了 5%,那就算了,不值得多占内存。

16.3 构建缓存:别重复造轮子

构建缓存,说白了就是把编译结果存起来。下次如果输入没变,直接拿缓存结果,跳过编译。这跟浏览器缓存是一个道理。

Gradle 有两种缓存:

  • 本地缓存:存在 ~/.gradle/caches/
  • 远程缓存:存在服务器上,团队共享

开启构建缓存:

# gradle.properties
org.gradle.caching=true

# 配置远程缓存(build.gradle)
buildCache {
    local {
        enabled = true
        directory = "${System.getProperty('user.home')}/.gradle/caches/build-cache-1"
    }
    remote(HttpBuildCache) {
        url = 'https://cache.example.com/cache/'
        push = true
        credentials {
            username = 'cache-user'
            password = 'cache-pass'
        }
    }
}

哪些任务可以被缓存?

  • compileJavacompileKotlin——编译任务
  • processResources——资源处理
  • jarassemble——打包任务
  • 自定义任务——只要声明了 @CacheableTask

我曾经遇到的一个坑:有次远程缓存一直不生效,排查了半天才发现是缓存 key 的生成规则有问题。Gradle 默认把任务的输入(源码、配置、环境变量等)作为 key 的一部分。如果某个输入没被正确声明,缓存就会失效。比如自定义任务里用了 System.getProperty() 读取参数,但没有用 @Input 注解声明,那缓存就永远命中不了。

16.4 配置缓存:跳过配置阶段

Gradle 的构建过程分三个阶段:初始化 → 配置 → 执行。配置阶段会解析所有 build.gradle 文件,创建 Task 对象图。对于大型项目,这个阶段可能就要花 10-20 秒。

配置缓存(Configuration Cache)就是把这个阶段的结果序列化存起来。下次构建时,如果构建脚本没变,直接加载缓存,跳过配置阶段。

开启配置缓存:

# gradle.properties
org.gradle.configuration-cache=true

# 如果遇到问题,可以先启用严格模式排查
org.gradle.configuration-cache.problems=warn

配置缓存的要求:

  • 构建脚本不能有副作用(比如写文件、网络请求)
  • 不能使用 System.getProperty() 等非序列化 API
  • Task 的输入输出必须明确声明

我的建议:配置缓存是 Gradle 7.0 以后才稳定的功能。如果你在用旧版本,先升级再说。另外,第一次开启时可能会报一堆警告,别慌,一个个修就行。我花了一个下午才把项目里的所有问题修完,但修完之后构建速度提升了 40%。

配置缓存 vs 构建缓存:

特性 配置缓存 构建缓存
缓存什么 Task 配置图 Task 执行结果
跳过什么 配置阶段 执行阶段
适用场景 构建脚本没变 源码没变
缓存失效 构建脚本变化 输入文件变化

16.5 增量编译:只改哪里编译哪里

增量编译,说白了就是只编译你修改过的文件。Java 编译器本身支持增量编译,但 Gradle 需要配合才能发挥效果。

Gradle 的增量编译机制:

  • 跟踪每个源文件的哈希值
  • 只重新编译哈希值变化的文件
  • 如果修改了 public API,会级联编译依赖它的文件

开启增量编译:

# gradle.properties
# Java 增量编译(默认就是 true)
org.gradle.java.compile.incremental=true

# Kotlin 增量编译
kotlin.incremental=true

# 启用编译避免(跳过不必要的注解处理)
org.gradle.annotation.processing.avoidance=true

增量编译的最佳实践:

  • 保持模块粒度适中——太大会导致级联编译范围大
  • 避免循环依赖——会破坏增量编译
  • 使用 --build-cache 配合增量编译——效果翻倍
  • 定期清理 build/ 目录——防止缓存膨胀

我曾经踩过的坑:有次增量编译突然失效了,每次改一个文件都要全量编译。查了半天,发现是某个第三方库的注解处理器没有声明 @SupportedOptions,导致 Gradle 无法判断它是否受增量编译影响。解决方案是升级那个库的版本,或者用 annotationProcessor 配置排除它。

16.6 综合优化策略

上面五个技巧单独用效果有限,组合起来才能发挥最大威力。我一般这样配置:

我的 gradle.properties 模板:

# 守护进程
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m

# 并行构建
org.gradle.parallel=true
org.gradle.workers.max=4

# 构建缓存
org.gradle.caching=true

# 配置缓存
org.gradle.configuration-cache=true

# 增量编译
org.gradle.java.compile.incremental=true
org.gradle.annotation.processing.avoidance=true

不同场景的优化重点:

场景 重点优化 预期提速
本地开发(频繁改代码) 增量编译 + 守护进程 50%-80%
CI/CD(全量构建) 构建缓存 + 并行构建 30%-60%
大型多模块项目 配置缓存 + 并行构建 40%-70%
首次构建(无缓存) 守护进程 + 并行构建 20%-40%

最后说一句:性能优化不是一劳永逸的事。项目在变,依赖在升级,Gradle 版本也在更新。我建议每季度做一次构建性能审计,用 --scan 生成构建报告,看看哪些环节还有优化空间。记住,优化的目标是让开发者少等几秒,而不是追求极致的数字。


公众号:蓝海资料掘金营,微信deep3321