Matt's Tidbits #84 - JVM Options, explained
Tags: mattstidbits, android, jvm
Published: 2020-11-05 22:29:41

[Last time I wrote about some strange unit test failures.](https://medium.com/@matthew.b.groves/matts-tidbits-83-puzzling-unit-test-failures-1a574af8a2fd) This week I’d like to explain some of the JVM options you might use.

While trying to figure out what was causing those unit tests to fail, I found a helpful way to see what your Android Studio memory settings are:
![](https://miro.medium.com/max/1076/1*EPwFJ8bpTUP7W8CHvev1vQ.png)

This opens a text editor where you can not only see, but also change any of these settings! The settings for my IDE are shown below:
```
-Xms512m
-Xmx4g
-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-Djdk.http.auth.tunneling.disabledSchemes=""
-Djna.nosys=true
-Djna.boot.library.path=

-da
-Xverify:none

-XX:ErrorFile=$USER_HOME/java_error_in_studio_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_studio.hprof
-XX:MaxPermSize=2g
```

You’ve probably seen some of these before, but what do they actually do? Let’s take a closer look.
* **-Xms**512m — the initial Java heap size — in this case, 512 MB — increasing this (within reason) means your program may perform better as it allocates more memory initially. The downside is that it will always use at least this much memory, so set it carefully.
* **Xmx**4g — the maximum Java heap size — I have mine set to 4 GB (Android Studio’s default is 1280 MB) — if you have RAM to spare, increasing this to at least 2 or 4 GB can greatly help improve performance — you will be less likely to encounter an `OutOfMemoryError`
*Note: I have seen some references to setting the `org.gradle.jvmargs` property in the `gradle.properties` file to be the amount defined by `Xmx` + 1024m.*
* **-XX:ReservedCodeCacheSize=**240m — the maximum amount of memory for Java JIT code compilation — increasing this (within reason) may help performance, particularly if your app is targeting Java 8.
* **-XX:**+**UseCompressedOops** — this special flag allows the JVM to compress “Ordinary Object Pointers” on 64-bit machines so they use less memory (since you’re probably not using all 18.5 Exabytes of possibly addressable RAM). This is enabled by default, and you probably shouldn’t change this unless you really know what you’re doing.
* **-Dfile.encoding=**UTF-8 — this sets the default encoding for reading/writing files.
* **-XX:**+**UseConcMarkSweepGC** — specifies that a “Concurrent Mark Sweep Garbace Collection” algorithm should be used. There are other options, and depending on your setup, a different one may have better performance. However, it is widely accepted that the newer(G1) algorithms are more efficient, as [ConcMarkSweepGC has been deprecated](http://openjdk.java.net/jeps/291). [This article](http://dhaval-shah.com/understanding-jvm-memory-management/) does a good job of explaining the differences.
* **-XX:SoftRefLRUPolicyMSPerMB=**50 — this controls how long (in milliseconds) soft-referenced values will stay alive after they are no longer referenced. [This article](https://blog.shiftleft.io/understanding-jvm-soft-references-for-great-good-and-building-a-cache-244a4f7bb85d) does a good job of explaining how this works. It’s all about balance, however — setting this as low as possible can degrade performance as frequently used objects might need to be recreated each time.
* **-Dsun.io.useCanonCaches=**false — this disables canonical file caches in the JVM. The canonical file cache dereferences symbolic links and improves file access time (and therefore JVM startup time), however, as with any cache, there have been [known issues](https://stackoverflow.com/questions/7479198/canonical-file-path-in-java-optimization-problem) with it becoming stale and returning incorrect information. I have seen in a number of places the recommendation to disable this by default.
* **-Djava.net.preferIPv4Stack=**true — this instructs the JVM to use IPv4 instead of IPv6. I found [articles](https://www.ibm.com/support/pages/ipv6-can-cause-poor-java-performance) stating that this prevents a performance issue that you might run into if your entire network is not IPv6 (which is still highly likely in 2020).
* **-Djdk.http.auth.tunneling.disabledSchemes=**”” — [starting in Java 8, web proxies that used basic authentication to tunnel over HTTPS will fail by default](https://stackoverflow.com/questions/41806422/java-web-start-unable-to-tunnel-through-proxy-since-java-8-update-111). You can override this change (and allow those requests to succeed) by setting this flag. I would leave this one alone unless you have a really good reason to change it — there’s no telling what might break (Android Studio, your app, etc.)
* **-Djna.nosys=**true — [Java Native Access is a library that allows Java programs to more easily use native libraries without the need for JNI or other native code](https://github.com/java-native-access/jna). When JNA loads, it attempts to load a platform-specific shared library, and it searches for it first in the paths specified by `jna.boot.library.path`, followed by system library paths. [Setting this flag to true](https://java-native-access.github.io/jna/4.2.1/com/sun/jna/Native.html) prevents JNA from searching for this shared library in the system library paths. I definitely would not change this unless you really know what you’re doing as it might cause your JVM applications to break if they unexpectedly use a different version of the JNA shared library. Take it from me — battling with shared library dependencies is not a fun time!
* **-Djna.boot.library.path=** — as mentioned in the previous item, this specifies the first place that JNA should look for its shared library. Given this configuration, I believe it will attempt to look for the JNA native shared library within the classpath of your application.
* **-da** — this disables Java assertions at runtime. Most commonly, you would likely use assertions in debug builds (or during tests), but would disable them during production and validate data in other ways. Assertions are disabled by default, but this flag provides extra security. I would not recommend changing this to `-ea` (to enable assertions) as it may cause unexpected issues at runtime and degrade application performance.
* **-Xverify**:none — [the JVM has security mechanisms in place to verify that bytecode is well-formed and loaded properly](https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html). This option disables that verification. My guess is that this is here to help improve Android Studio performance, but it is very dangerous to use this in a production app, to the point that beginning in Java 13 Oracle has deprecated this option.
* **-XX:ErrorFile=**$USER_HOME/java_error_in_studio_%p.log — this is where the JVM will store a log of any fatal errors that occur — the `%p` will be replaced by the process ID.
* **-XX:HeapDumpPath=**$USER_HOME/java_error_in_studio.hprof — this specifies where a Heap Dump will be written to if an `OutOfMemoryError` occurs and the `-XX:+HeapDumpOnOutOfMemoryError` flag is set.
* **-XX:MaxPermSize=**2g — this controls the maximum “permanent generation” space. This is the area of memory that is reserved by the JVM to store objects that will never be garbage-collected — such as the classes, string constants, etc. of your program. If you see a `java.lang.OutOfMemoryError: PermGen space` error, increasing this is the first thing to try. This is separate from the heap, which is where instances of objects are stored (dynamic memory).

Putting everything together, the following graphic shows the different types of memory used by the JVM (and how the various flags correspond to the different areas):
![](https://miro.medium.com/max/1400/1*IGdgunAfFTnIWUBl-M8vFA.gif)

I’d love to hear from you — what JVM configuration options do you use, and why? Drop me a note in the comments! And, please follow me on Medium if you’re interested in being notified of future tidbits.

*This tidbit was discovered on October 2, 2020.*