Discover how two peculiar annotations can drastically accelerate native calls and maximize your app’s performance.

In Android 8.0, Google added a new ART feature: faster native calls with the @FastNative and @CriticalNative annotations. According to Google, “these built-in ART runtime optimizations speed up Java Native Interface (JNI) transitions and replace the now deprecated !bang JNI notation.” Google provides a chart with example execution time differences between the different native invocation types, showing a ~4.6x improvement in execution time—but how accurate is it?

On a Google Pixel 2 on Android 11, the results from 10,000,000 innovations each of empty C functions are shown below. Note that the !bang JNI notation no longer shows any performance benefit, so it is excluded from testing on Android 8+. The source code for this example project that these results were generated from can be found here.

JNI invocation typeExecution time (in ns)
Regular JNI774
@FastNative278
@CriticalNative32

Here are also the results from running on an arm64 Android 13 emulator on my Apple MacBook Air (Late 2020):

JNI invocation typeExecution time (in ns)
Regular JNI100
@FastNative40
@CriticalNative23

Although these results are not too scientific (i.e., I did not use a proper benchmarking tool such as JMH), they show an impressive result: ~24x and ~4x improvement in execution time respectively! I did utilize a “warmup” run, and the results were consistent between runs.

So, how do I speed up my Android app?

Unfortunately, Google does not provide access to the @FastNative and @CriticalNative annotations in the Android 13 SDK and below; in the public Android SDK, these classes (in the dalvik.annotation.optimization package) are not included at all. Luckily, there is a very easy solution—simply adding these classes to your project is all that is needed to trick ART into optimizing your app’s native calls! The example project that I used to create the above-mentioned benchmark does exactly this.

However, there are several considerations to be made before using these annotations. While the @FastNative annotation does not require altering your existing JNI interface code, the @CriticalNative annotation does; using the @CriticalNative annotation requires you to remove the JNIEnv and jclass parameters in your native code, thus giving up access to Java object functionality. Additionally, Google’s documentation recommends no long-running or unbounded functions, as well as not acquiring locks that aren’t also released in that same function (excluding some logging and native allocation calls); these actions risk creating deadlocks or freezing the garbage collector for too long. Despite this, many typical C/C++ functions are eligible for these annotations.

Lastly, in the JavaDoc for the @CriticalNative annotation, Google warns that these annotations are not CTS-tested prior to Android 14, and that one should only use the RegisterNatives API to use these prior to Android 12 (like in my example project). Although lack of CTS-testing could prove to be a potentially fatal compatibility issue with functions making use of the @CriticalNative annotation, I question if there is any real-world precedent of a standard OEM modifying or removing support for these annotations in such a compatibility-breaking way.

Regarding Android 14, I just became aware that these annotations seem to be promoted to the public API, with the @SystemApi annotations and @hide JavaDoc annotations being removed! So, in cases where you have a critical production app and don’t want to risk running into compatibility issues, you can still take advantage of these two amazing annotations in the future.

If you take a look at the AOSP source, you may notice there are other annotations in the same package… those don’t look too interesting for typical usage, but I may explore them in a future blog.