ZGC | What's new in JDK 18

On March 22, JDK 18 was released. This was a fairly quiet release for ZGC, since most of our efforts in the last year or so has gone into making ZGC a generational GC. Still, there were 37 bugfixes and enhancements related to ZGC in this release. I’ll discuss some of the more interesting ones in this post. If you’re interested in knowing more about ZGC features/enhancements in previous JDK releases, then check out some of my previous posts.

Now let’s talk about what’s new in JDK 18 (from a ZGC perspective).

String Deduplication

String deduplication is a JVM feature (-XX:+UseStringDeduplication) that has been around for quite a while now. It helps reduce Java heap memory usage by automatically deduplicating identical character arrays that are backing String objects. For example, if two String objects exist on the heap and they both point to a backing array containing the characters “Java”, then one of the String objects will be modified so that both String objects point the same array, and the other array will be made unreachable and subject to garbage collection. This can help reduce the heap memory usage by quite a bit for some applications, since String objects can to occupy a non-trivial part of the Java heap, and finding duplicates among those can be fairly common.

I wrote the JEP and the initial implementation for this feature back in 2014, and it shipped as part of JDK 8u20. This was before ZGC was born, and the initial implementation only added support for String deduplication in the G1 garbage collector.

Fast forward to 2021. Kim Barrett overhauled a significant part of the String deduplication infrastructure. The overall concept remained the same, but the way in which the String deduplication mechanism interfaces with the garbage collector became more general. This enabled easier integration of this feature into garbage collectors other than G1. As a result, String deduplication support was later added to SerialGC, ParallalGC, and ZGC.

If you’re unfamiliar with String dediuplication, what it actually does, and when you might want to enable it, then check out the JEP. Even if that document is a bit out of date by now (e.g. it only talks about G1), the gist of the feature is still the same.

Class Unloading Issue Fixed

We received a report about a performance issue on the ZGC mailinglist. This turned out to be a 10-year-old bug that dates back to the removal of PermGen.

So, about 10 years ago, the patch to remove PermGen made some changes to a function that deals with Inline Cache cleaning. An Inline Cache is a speculative optimization technique used by the JVM to speed up method calls in Java. When the GC unloads unused classes and compiled methods, some of the Inline Caches need to be cleaned so that they no longer refer to any unloaded entities.

As it turns out, this patch contained a small but important editing mistake, where indentation and scopes were mixed up. It was hard to spot that mistake by just looking at the patch, because the code in question was also moved. This mistake resulted in some Inline Caches being incorrectly cleaned. However, the incorrect cleaning didn’t cause any obvious problems, like a JVM crash. Instead it induced a vicious cycle where the GC and Java threads disagreed about, and fought over, how to clean these caches. The end result was that class unloading, under certain conditions, could take a very long time to complete. Since the root cause of the issue was bad interaction between the GC and concurrently running Java threads, it only affected GCs doing concurrent class unloading (such as ZGC). GCs doing Stop-the-World class unloading (such as SerialGC, ParallelGC and G1GC), were unaffected simply because this bad interaction could never arise, since Java threads never run concurrently with the GC.

Luckly, once this problem was spotted the fix was straight forward. If you’re interested in more details you can read all about it in the corresponding pull request.

This fix was also backported to JDK 17.0.2.

Linux/PowerPC Support

Back in 2013, i.e. before ZGC had come into existence, JEP 175 was created to bring Linux/PowerPC (as well as AIX/PowerPC) support to OpenJDK. The initial port shipped as part of JDK 8u20 and has been maintained ever since. The effort to support this platform has from the start been funded by our friends over at SAP.

So, it might not be a surprise to hear that it was also SAP that contributed the patch to make ZGC available on Linux/PowerPC. Adding support for a new CPU architecture is mostly a matter of implementing ZGC’s various barriers (load barrier, nmethod entry barrier, and stack watermark barrier) in the interpreter and the two JIT compilers. The patch weights in at around 1200 lines of code.

As of JDK 18, ZGC now runs on the following platforms (see this table for more details):

  • Linux/x64
  • Linux/AArch64
  • Linux/PowerPC
  • macOS/x64
  • macOS/AArch64
  • Windows/x64
  • Windows/AArch64

Summary

  • The JVM option -XX:+UseStringDeduplication is now supported. This feature (disabled by default) tells ZGC to find and deduplicate identical character arrays backing String objects to reduce overall heap memory usage.

  • A 10 years old bug that sometimes causes class unloading to take extremely long time was fixed.

  • ZGC now runs on Linux/PowerPC, thanks to the guys at SAP.

For more information on ZGC, please see the OpenJDK Wiki, the GC section on Inside Java, or this blog.