making JetBrains IDEs run faster…

synopsis

JetBrains’ IDEs suffer from awful interactive performance out of the box. This is unfortunate, and given the highly interactive nature of programming one would think it would be taken more seriously. This article gives instructions on how to change the “boot JDK” and compile the base classes of the JDK to binary form, so that they are not JIT-compiled every time one starts the IDE.

I suggest you try both operations for the maximum benefit. I am keen to hear about the results, so please tell me via firstname.lastname@gmail.com — check the domain name of this site for the values 🙂 In adjusting all of these settings, YMMV depending on your system. Good luck!

disclaimer / warning

This post contains advice / instructions on how to change your JetBrains installation. Following them may break your installation, and I do not take any responsibility for your actions in doing so 🙂

1 / stop using JDK 11-based JBR, start using a (vanilla) version 16 JDK instead

Install version 16 of the JDK such as Azul Zulu by following the instructions here. Other JVM implementations are available. I am neither paid by, nor associated with, Azul.

Change the boot JDK to the one you’ve just installed by following the instructions here.

All the EAP and latest release versions of JetBrains IDEs seem to work with JDK 16. In all cases, features using the JCEF [1] runtime library, which is bundled with the JBR [2], will not be available. If you run into problems, JDK 15 will work instead.

Change the “Custom VM options” to something like the below. I’m using these options on Debian/unstable on all JetBrains IDEs, with both JDK 15 and JDK 16.

  -XX:+IgnoreUnrecognizedVMOptions
  -XX:+UnlockExperimentalVMOptions
  -XX:+UseZGC
  -XX:-UseG1GC
  -XX:C1CompileThreshold=100
  -XX:C2CompileThreshold=5000
  -Xms4G
  -Xmx4G
  -XX:ReservedCodeCacheSize=512m
  -XX:SoftRefLRUPolicyMSPerMB=50
  -XX:CICompilerCount=2
  --illegal-access=permit

Adding -XX:ReservedCodeCacheSize= and -XX:SoftRefLRUPolicyMSPerMB= made all the difference in keeping heap usage down, although I have not yet experimented with different values for those parameters.

The option –illegal-access=permit is required on JDK 16, but this option was removed in JDK 17, and because of this (but probably not only this) I haven’t yet been able to make any JetBrains IDE work with JDK 17.

When you edit this file, make a note of the location via the “Copy path” feature in the IDE, so that if it breaks, you can find it and remove the mistake quickly. Also, you may wish to start the IDE in the console via the command line (rather than the Toolbox launcher) so that you can easily see the error.

[1] https://github.com/JetBrains/jcef
[2] which is currently based on JDK 11

2 / compile all the JDK classes ahead of time using the jaotc tool

Since day one, startup time has been a painful part of Java. Nowadays we are blessed with modules in the JDK and we can compile them ahead of time and load them into the JDK at runtime. Seems like it might a good idea for big applications, but actually the benefits are greater for smaller programs.

This script invokes the jaotc binary to create a single shared object library file from the modules in the JDK. Note that the ZGC garbage collector is not compatible with the use of jaotc.

The jaotc tool has been removed in version 17 and later of the JDK, but for now, we can make the most of it.

#! /bin/bash

set -o nounset


# java ------------------------------------------------------------------------

java_home=${JAVA_HOME:-/usr/lib/jvm/zulu16}
echo "java_home is [${java_home}]"

java_flags=( -XX:+UseG1GC -XX:-UseZGC )
java_name=$(basename ${java_home})
aot_so=${HOME}/lib/java/native/$(uname -m)-linux/aot.${java_name}.so
echo "output shared object file is [${aot_so}]"


# enumerate modules -----------------------------------------------------------

modules=()

jmod_files=( ${java_home}/jmods/*.jmod )
for _jmod in ${jmod_files[@]}; do
    module=$(basename ${_jmod/.jmod/})
    case ${module} in
        jdk.hotspot.agent) ;;
        jdk.incubator) ;;
        jdk.incubator.*) ;;
        jdk.internal.vm.compiler) ;;
        jdk.jcmd) ;;
        *) modules+=( ${module} ) ;;
    esac
done


# construct command line ------------------------------------------------------

argv=()
argv+=( ${java_home}/bin/jaotc )
for flag in ${java_flags[@]}; do
    argv+=( -J$flag )
done
argv+=( --compile-for-tiered )
argv+=( --output ${aot_so} )
argv+=( --module ${modules[@]} )


# execute --------------------------------------------------------------------

mkdir -p $(dirname ${aot_so})
${argv[@]}

This step above will take about ten minutes or so, depending on your system.

Add these lines to your IntelliJ custom VM options. The path matches the output path in the script above, and the “GC” options match the JVM options in the script as well. (As mentioned above, the ZGC garbage collector is not compatible with the use of jaotc.) Note that this is different from the previous section where ZGC was enabled.

-XX:+UnlockExperimentalVMOptions
-XX:AOTLibrary=/home/username/lib/java/native/x86_64-linux/aot.zulu16.so
-Xshare:on
-XX:+UseG1GC
-XX:-UseZGC
-XX:+PrintAOT

(Note the /home/username and change it to your home directory.) Restart the IDE — and you should see it start much faster. Remove or comment out -XX:+PrintAOT when you are satisfied it’s working as expected.

2 Comments

  1. The performance was good, but the font anti-aliasing and readability took a real hit, it was essentially unusable. Like going back to win95. Both the IDE fonts (menus etc) and the editor suffered. For me the legibility mattered more over the performance gain. How did you fare in that department?

    • Hello Abhiyan, I did have to manually reset the editor font to JetBrains Mono, and Noto Sans for everything else. Right now, I’m using PyCharm 2021.3.2 “(Professional Edition), Build #PY-213.6777.50, built on January 27, 2022” with Azul 16 JDK and it’s looking great on KDE Plasma on Linux. I have settled on this set of VM options at the moment (think I’ll make a new post or update this one to reflect this)

      -Dide.no.platform.update=true
      -Djdk.attach.allowAttachSelf=true
      -Djdk.http.auth.tunneling.disabledSchemes=""
      -Djdk.module.illegalAccess.silent=true
      -Dkotlinx.coroutines.debug=off
      -Dsun.io.useCanonCaches=false
      -Dsun.java2d.metal=true
      -Dsun.tools.attach.tmp.only=true

      -Xms4G
      -Xmx4G
      -XX:+IgnoreUnrecognizedVMOptions
      -XX:+UnlockExperimentalVMOptions
      -XX:+UseZGC
      -XX:-UseG1GC
      -XX:C1CompileThreshold=4
      -XX:C2CompileThreshold=32
      -XX:CICompilerCount=2
      -XX:ReservedCodeCacheSize=1G
      -XX:SoftRefLRUPolicyMSPerMB=50

      --illegal-access=warn

      --add-opens java.base/java.lang=ALL-UNNAMED
      --add-opens java.base/java.util=ALL-UNNAMED
      --add-opens java.desktop/java.awt.event=ALL-UNNAMED
      --add-opens java.desktop/java.awt.peer=ALL-UNNAMED
      --add-opens java.desktop/java.awt=ALL-UNNAMED
      --add-opens java.desktop/javax.swing.plaf.basic=ALL-UNNAMED
      --add-opens java.desktop/javax.swing.text.html=ALL-UNNAMED
      --add-opens java.desktop/javax.swing=ALL-UNNAMED

      Sorry it didn’t work out as well as it could. What platform are you on?

Leave a Reply

Your email address will not be published. Required fields are marked *