Running JDK Mission Control on Apple M1
JDK Mission Control (JMC) is invaluable for analysing performance data recording using JDK Flight Recorder (JFR). The other day, I ran into a problem when trying to run JMC on my Mac Mini M1. Mostly for my own reference, here’s what I did to overcome it.
Upon launching a freshly downloaded JMC (I tried both the upstream build from OpenJDK and the one from the Eclipse Adoptium project), I’d get the following error message:
The JVM shared library "/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/bin/../lib/server/libjvm.dylib" does not contain the JNI_CreateJavaVM symbol.
"temurin-17.jdk" is my default JDK; it’s the Java 17 build provided by the Eclipse Temurin project for macOS/AArch64, i.e. the right one for the ARM chip of the M1 ("Apple silicon"). The error message isn’t overly helpful; after all, that referenced JDK works just fine for all my other applications. The problem though is that JMC itself currently only is shipped as an x64 application:
1 2 $ file "JDK Mission Control.app"/Contents/MacOS/jmc .../JDK Mission Control.app/Contents/MacOS/jmc: Mach-O 64-bit executable x86_64
So I decided to try with an x64 JDK build instead; thanks to Apple’s Rosetta project, x64 binaries can be executed on the M1 via a rather efficient emulation.
After downloading the macOS/x64 Temurin build,
it needs to be configured as the JDK to use for JMC.
For that, open the file JDK Mission Control.app/Contents/Info.plist in an editor and look for the
-vm parameter with the path to the x64 JDK to the key’s value.
Altogether, it should look like so:
1 2 3 4 5 6 7 8 ... <array> <string>-keyring</string> <string>~/.eclipse_keyring</string> <string>-vm</string> <string>/path/to/jdk-17.0.3+7-x86-64/Contents/Home/bin/java</string> </array> ...
Et voilà, JMC will now start just fine on the Apple M1. Note that in some cases I got an intermittent permission issue after editing the plist file. Resetting the permissions helped in that case:
1 $ sudo chmod -R 755 "JDK Mission Control.app"
With the x64 JDK around, it’s a good idea to make sure it’s only used for JMC, while sticking to the AArch64 build for all other usages for the sake of performance. Unfortunately, it’s not quite obvious to see flavour you are running, as the target architecture isn’t displayed in the output of java --version:
1 2 3 4 5 6 7 8 9 10 11 $ export JAVA_HOME=path/to/temurin-17.jdk/Contents/Home $ java --version openjdk 17.0.3 2022-04-19 OpenJDK Runtime Environment Temurin-17.0.3+7 (build 17.0.3+7) OpenJDK 64-Bit Server VM Temurin-17.0.3+7 (build 17.0.3+7, mixed mode) $ export JAVA_HOME=path/to/jdk-17.0.3+7-x86-64/Contents/Home $ jdks java --version openjdk 17.0.3 2022-04-19 OpenJDK Runtime Environment Temurin-17.0.3+7 (build 17.0.3+7) OpenJDK 64-Bit Server VM Temurin-17.0.3+7 (build 17.0.3+7, mixed mode, sharing)
Not sure what "sharing" exactly means in the x64 output, perhaps it’s a hint?
In any case, printing the contents of the
os.arch system property will tell the truth, e.g. in jshell:
1 2 3 4 5 6 7 $ export JAVA_HOME=/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home $ jdks jshell | Welcome to JShell -- Version 17.0.3 | For an introduction type: /help intro jshell> System.out.println(System.getProperty("os.arch")) aarch64
1 2 3 4 5 6 7 $ export JAVA_HOME=~/Applications/jdks/jdk-17.0.3+7-x86-64/Contents/Home $ jshell | Welcome to JShell -- Version 17.0.3 | For an introduction type: /help intro jshell> System.out.println(System.getProperty("os.arch")) x86_64
If you are aware of a quicker way for identifying the current JDK’s target platform, I’d love to learn about it in the comments below. Thanks!