Java 8 introduced String Deduplication that can be enabled by launching JVM with -XX:+UseStringDeduplication
option allowing to save some memory by referencing similar String
objects instead of keeping duplicates. Of course it's effectiveness varies from program to program depending on utilisation of Strings
but I think it is safe to say that in general it can be considered beneficial for most applications (if not all) making me wonder about few things:
Why is it not enabled by default? Is it because of costs associated with dedeuplication or simply because G1GC is still considered new?
Are there (or could there be) any edge cases where you would not want to use deduplication?
Explicitly Set the GC Algorithm For example on Java 8, the default GC is Parallel GC, while on Java 11 the default is G1 GC. This means that on upgrading your Java version, this will be changed on your behalf, for better or worse, unless you've explicitly set the GC.
String Deduplication allows multiple Strings to share the same underlying character array. You can activate it as follows. -XX:+UseG1GC -XX:+UseStringDeduplication. I'm using OpenJDK 13, as long as your using java version 9 or above you will be able to follow along and reproduce the results presented here.
The default garbage collector in Java 11 is the G1 garbage collector (G1GC). The aim of G1GC is to strike a balance between latency and throughput. The G1 garbage collector attempts to achieve high throughput by meeting pause time goals with high probability.
‘-XX:+UseStringDeduplication’ feature is supported only from Java 8 update 20. Thus, if you are running on any older versions of Java, you will not be able to use this feature. (7). -XX:+PrintStringDeduplicationStatistics
String Deduplication takes advantage of the fact that the char arrays are internal to strings and final, so the JVM can mess around with them. Whenever the garbage collector visits String objects it takes note of the char arrays. It takes their hash value and stores it alongside with a weak reference to the array.
During the garbage collection process, the JVM inspects all the objects in memory, thus as part of that process, it tries to identify duplicate strings among them and tries to eliminate it. Does that mean if you just pass ‘-XX:+UseStringDeduplication’ JVM argument will you be able to save 13.5% of memory immediately? Sounds pretty easy, right?
If your application is using G1 GC and running on a version above Java 8 update 20, you may consider enabling -XX:+UseStringDeduplication. You might get fruitful results especially if there are a lot of duplicate strings among long-lived objects.
Cases where String de-duplication could be harmful include:
There is a reasonable probability of duplicates, but most strings die in within a couple of GC cycles1 anyway. The de-duplication is less beneficial if the de-duped strings were going to be GC'ed soon anyway.
(This is not about strings that don't survive the first GC cycle. It would make no sense for the GC to even try to de-dup strings that it knows to be garbage.)
We can only speculate as to why the Java team didn't turn on de-duping by default, but they are in a much better position to make rational (i.e. evidence based) decisions on this that you and I. My understanding is that they have access to many large real-world applications for benchmarking / trying out the effects of optimizations. They may also have contacts in partner or customer organizations with similarly large code-bases and concerns about efficiency ... who they can ask for feedback on whether optimizations in an early access release work as expected.
1 - This depends on the value of the StringDeduplicationAgeThreshold
JVM setting. This defaults to 3 meaning that (roughly) a string has to survive 3 minor collections or a major collection to be considered for de-duping. But anyhow, if a string is de-duped and then found to be unreachable shortly afterwards, the de-duping overheads will not be repaid for that string.
If you are asking when you should consider enabling de-duping, my advice would be to try it and see if it helps on a per-application basis. But you need to do some application-level benchmarking (which takes effort!) to be sure that the de-duping is beneficial ...
A careful read of JEP 192 would also help you understand the issues, and make a judgment on how they might apply for your Java application.
I absolutely understand that this does not answer the question, just wanted to mention that jdk-9 introduces one more optimization that is on by default called :
-XX:+CompactStrings
where Latin1 characters occupy a single byte instead of two (via a char). Because of that change many internal methods of String have changed - they act the same to the user, but internally they are faster in a lot of cases.
Also in case of Strings for concatenating two Strings together via the plus sign the javac is going to generate different bytecode.
There is no bytecode instruction that concatenates two Strings together so the javac would generate a
StringBuilder#append
in the back-end. Until jdk-9.
Now the bytecode delegates to
StringConcatFactory#makeConcatWithConstants
or
StringConcatFactory#makeConcat
via the invokedynamic bytecode instruction:
aload_0 1: aload_2 2: aload_1 3: invokedynamic #8, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 8: areturn
How the two strings are concatenated is a Runtime decision now. it could be still a StringBuilder or it could be a concatenation of byte arrays, etc. All you know that this can change and you will get the fastest possible solution.
EDIT
I've just debugged and saw that there are quite a lot of strategies on how to append these Strings:
private enum Strategy { /** * Bytecode generator, calling into {@link java.lang.StringBuilder}. */ BC_SB, /** * Bytecode generator, calling into {@link java.lang.StringBuilder}; * but trying to estimate the required storage. */ BC_SB_SIZED, /** * Bytecode generator, calling into {@link java.lang.StringBuilder}; * but computing the required storage exactly. */ BC_SB_SIZED_EXACT, /** * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. * This strategy also tries to estimate the required storage. */ MH_SB_SIZED, /** * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. * This strategy also estimate the required storage exactly. */ MH_SB_SIZED_EXACT, /** * MethodHandle-based generator, that constructs its own byte[] array from * the arguments. It computes the required storage exactly. */ MH_INLINE_SIZED_EXACT }
The default being:
MH_INLINE_SIZED_EXACT
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With