Switch to JDK 17 + Parallel GC and speed up your Android builds by 9-20%

Speed up Android build times with simple tweaks to the Java Development Kit and Gradle. After benchmarking build times for 3 projects with different JDK versions, JDK 17 is now also preinstalled on all build stacks to help you get the 9-20% speed improvements we did. Learn more and try them!

Speed up Android build times with simple tweaks to the Java Development Kit and Gradle. After benchmarking build times for 3 projects with different JDK versions, JDK 17 is now also preinstalled on all build stacks to help you get the 9-20% speed improvements we did. Learn more and try them!

Gradle and the Android tooling run on Java, and the minimum required JDK (Java Development Kit) version to run these tools is 11 at the moment, and this is the default on Bitrise stacks as well. JDK11 is 3 years old though, and the current LTS (Long Term Support) release is JDK17.

The inspiration for this experiment was an article about the performance improvement of recent JDK versions. We benchmarked the performance of JDK11 and JDK17 on Android builds with the default settings, then with an alternative garbage collector enabled.

Based on the results we decided to preinstall JDK 17 on the build stacks (both Linux and macOS), so you can benefit from the improvements (JDK 11 remains the default though).

The JDK version used for running Gradle does not affect the final application in any way. The Java runtime on an Android device is independent of the runtime used to build the app on a developer machine. An app built with Gradle running on JDK17 can still run on Android devices with earlier runtimes (because 'minSdkVersion' and 'compileSdkVersion' controls the app’s behavior)

Requirements

Changing the JDK version and the garbage collector are relatively quick tasks to improve build speeds compared to huge refactors or other long initiatives. There are a few requirements to keep in mind though:

JDK17 on Bitrise VMs

We decided to preinstall JDK 17 on the build stacks (both Linux and macOS), so you don’t have to install it manually and lose time waiting for the process. JDK 11 is still the one activated by default, but you can use our step to activate JDK 17.

Minimum Gradle version: 7.3

The first Gradle version that officially supports Java 17 is version 7.3. You might need to upgrade Gradle in your project in the 'gradle-wrapper.properties' file to start using JDK17.

Minimum Kotlin version: 1.5.30

Earlier Kotlin versions are incompatible with JDK 17 because of this bug.

Besides these changes, you might have other plugins and tools in the project that need an update for JDK 17.

Garbage collectors

The default garbage collector for both JDK11 and JDK17 is called G1. There are other garbage collectors that are part of JDK though, and these can be enabled with a command-line flag. The Parallel garbage collector is available for both JDK versions, and it seems to be ideal for the workload of a compiler (where throughput and memory footprint is more important than latency).

To change the garbage collector from the default G1 to Parallel, you need to add the '-XX:+UseParallelGC' flag to the 'gradle.properties' file:


org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 -XX:+UseParallelGC
Copy code

The benchmark

We tested the build time of open-source Android projects running on Bitrise with multiple JDK configs. Build time was measured with the gradle-profiler project on Linux EliteXL Bitrise build machines. See the end of this article for more details about the benchmark setup.

Open-source apps used for benchmarking:

Results

The baseline for the comparisons is running Gradle on JDK11 with its default garbage collector, G1. The relative improvements are compared to this config.

For the Tivi project, we measured a 16% improvement switching to the Parallel garbage collector while still running on JDK11. Switching to JDK17 and the Parallel GC resulted in a 20% speedup!

Building Firefox on JDK11 with Parallel GC was 5% faster and using Parallel GC on JDK17 is a 12% improvement compared to the baseline.

For the DuckDuckGo project, the percentages are a bit lower, but the pattern is the same as before. Switching to the Parallel GC on JDK11 makes builds 4% faster, switching to JDK17 with Parallel GC is a 9% improvement.

Summary

Across the three sample apps benchmarked, we see nice build speed improvements with relatively low effort changes:

  • Keep using JDK11, switch to Parallel GC: 4-16%
  • Upgrade to JDK17, no change to GC: 6-14%
  • Upgrade to JDK17, switch to Parallel GC: 9-20%

Benchmark details

For the benchmark we used a fork of every sample app where we made some changes:

  • Upgraded Gradle to 7.3
  • Upgraded Android Gradle Plugin as well if needed
  • Disabled both remote and local Gradle caching if it was enabled in the original project

Build times were measured with Gradle Profiler with the following config:

  • Warm-ups: 1
  • Iterations: 5
  • Restarting Gradle daemon between builds
  • Running the 'clean' task between builds

The warm-up task downloaded and cached dependencies to '~/.gradle', so the subsequent build measurements were not skewed by network requests.

Run your own benchmark on Bitrise

We encourage you to run the same benchmarks to see the exact numbers for your project. For reference, here is the full workflow used for the benchmarks:


steps:
  - git-clone@6: {}
  - script@1:
      title: Install Gradle Profiler
      inputs:
        - content: |-
            #!/usr/bin/env bash
            set -ex

            wget https://repo1.maven.org/maven2/org/gradle/profiler/gradle-profiler/0.17.0/gradle-profiler-0.17.0.zip
            unzip gradle-profiler-0.17.0.zip

            PATH=$PATH:$BITRISE_SOURCE_DIR/gradle-profiler-0.17.0/bin/
            envman add --key PATH --value $PATH
  - set-java-version@1:
      inputs:
      - set_java_version: 17
  - generate-text-file@0:
      title: Generate scenario file
      inputs:
        - file_name: benchmark.scenarios
        - file_content: |-
            default-scenarios = ["assemble"]

            assemble {
              title = "Assemble"
              tasks = ["assembleRelease"]
              cleanup-tasks = ["clean"]
              daemon = cold
              warm-ups = 1
              iterations = 5
            }
  - script@1:
      title: Run benchmark
      inputs:
        - content: |-
            #!/usr/bin/env bash
            set -ex

            gradle-profiler --benchmark --scenario-file $GENERATED_TEXT_FILE_PATH --project-dir .

            mv profile-out/benchmark.html $BITRISE_DEPLOY_DIR/benchmark.html
            mv profile-out/benchmark.csv $BITRISE_DEPLOY_DIR/benchmark.csv
  - deploy-to-bitrise-io@2: {}
Copy code

Benchmark results are deployed as build artifacts in CSV and HTML format, so you can process the raw data later or view the report instantly on Bitrise:

Get Started for free

Start building now, choose a plan later.

Sign Up

Get started for free

Start building now, choose a plan later.