Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8350754

Deprecate UseCompressedClassPointers

XMLWordPrintable

    • behavioral
    • medium
    • See risk section in description. I consider the compatibility risk to be between low and medium, but I am a careful person so I chose medium, unless others think it can be lowered to low.
    • add/remove/modify command line option
    • JDK

      Summary

      The switch UseCompressedClassPointers is to be deprecated in preparation for the future removal of its negative mode (-UseCompressedClassPointers).

      Problem

      Since the advent of compact object headers, headers can take three possible shapes:

      • Containing raw 64-bit Klass pointers (-UseCompressedClassPointers -UseCompactObjectHeaders)

      • Containing "compressed" aka "narrow" 32-bit Klass pointers (+UseCompressedClassPointers -UseCompactObjectHeaders). This is the default mode.

      • Containing 22-bit narrow Klass pointers as part of the new compact object headers (+UseCompressedClassPointers +UseCompactObjectHeaders)

      The first variant, using 64-bit Klass pointers with -UseCompressedClassPointers, serves almost no practical purpose. It mainly causes memory bloat, since it wastes 4 bytes per object, which can mean a significant increase in heap space. Moreover, maintaining this mode costs resources and accrues technical debt. In addition to that, Github source code analyses show (e.g. [1]) that it is often misunderstood and misapplied ("uses less memory," which is somewhat ironic).

      Risk of deprecation

      There is a low but certainly nonzero risk involved with deprecating this mode.

      The number of loadable classes

      That number is determined by the size of the class space, the number of loaders, and the Klass alignment granularity. At the moment, the following rough limits exist:

      a) JVM called with -UseCompressedClassPointers: no limit

      With UseCompressedClassPointers, the limit is determined by the class space size and the number of class loaders. Klass structures are variable-sized but typically ~700 bytes, with larger outliers being rare. Class loaders increase this to 1KB, since each class loader needs a separate metaspace chunk, the smallest size of which is 1KB. However, applications with millions of class loaders are very rare. Therefore:

      b) JVM called with maxed out class space of 4GB: +UseCompressedClassPointers -UseCompactObjectHeaders -XX:CompressedClassSpaceSize=4G : about 4-6 million classes

      c) JVM called with default settings (1G default class space size and default settings +UseCompressedClassPointers -UseCompactObjectHeaders): about 1-1.5 million classes

      With Lilliput, we align Klass structures to 1KB boundaries (and use the alignment waste for non-Klass metadata); hence:

      d) JVM called with maxed out class space -XX:CompressedClassSpaceSize=4G and compact object headers + UseCompactObjectHeaders (which implies + UseCompressedClassPointers): about 4 million classes

      e) JVM called with default class space size of 1GB and compact object headers +UseCompactObjectHeaders: about 1 million classes

      If we deprecate -UseCompressedClassPointers (a), we lose the ability to load unlimited classes. The maximum loadable classes would drop to 5-6 million with an explicitly maxed-out class space (b).

      We consider that risk theoretical. We have never seen practical installations with these many classes, and if we disregard pathological leak scenarios, that would run into OOMEs eventually anyway. One interesting effect of loading this many classes would be that - since memory is needed from both class space and non-class metaspace in a ratio of 1:6..1:10 - maxing out 4GB of class space would use between 24..>40 GB of non-class metaspace in addition to the class space. This is a ridiculous number for class metadata alone.

      Moreover, one would hit other limits before that, e.g., we would run out of code cache when the compiler attempts to compile this many classes. So, the classes would mostly stay uncompiled, which is not a practical solution for production scenarios.

      We have a fallback plan if we run into problems with the number of classes limit. That plan (the Near/Far class idea, see mailthread [4]) has not yet been implemented but seems entirely practical. However, due to the increased complexity and overhead, we will only implement this plan when necessary.

      Users misapplying -UseCompressedClassPointers

      Uses of -UseCompressedClassPointers are rare (Github source search yields about 700 hits, many of them forks of the same projects). Most of these uses seem to be misapplication based on misunderstanding the switch. These cases should remove the switch. Some uses mention crashes with +UseCompressedClassPointers. If these situations are reproducible in the mainline, they should be fixed.

      Users needlessly specifying +UseCompressedClassPointers

      If we deprecate the full switch and not just its negative form (see section "Solution" below), Apps that specify +UseCompressedClassPointers will see a deprecation warning. The fix would be to remove that switch.

      32-bit platforms

      32-bit platforms use raw 32-bit Klass* pointers with -UseCompressedClassPointers being hardwired. An idea exists to let 32-bit platforms use +UseCompressedClassPointers with a "fake class space" mode in which we treat the whole 4GB address space as class space. With this idea, we would use +UseCompressedClassPointers like other platforms, but Klass structures would continue to live in non-Klass metaspace as before (introducing a class space, even a small one, would risk address-space fragmentation on 32-bit).

      However, for now, I would propose not to deprecate -UseCompressedClassPointers for 32-bit; the switch is not accepted on 32-bit platforms anyway since it is hard-coded.

      Note that 32-bit platforms are clearly on their way out. If JEP 503 [5] "Remove the 32-bit x86 port" is finished, the only remaining 32-bit platforms will be arm and the 32-bit zero JVMs. The 32-bit ports are in a sorry state and often broken for lengthy times (at the moment of writing this CSR, neither x86 nor 32-bit zero can even be built successfully). We need to decide on the fate of 32-bit platforms soon; as long as we don't do this, I hesitate to put a lot of work into them.

      Solution

      Removing the -UseCompressedClassPointers mode leaves us with just the +UseCompressedClassPointers mode (the current default). Since that would make the switch pointless, the proposed solution is to deprecate the switch.

      An alternative solution would be to deprecate/forbid the use of its negative form of -UseCompressedClassPointers. This would lessen the compatibility impact since the negative form of this switch is rarely used. In preceding discussions, however (see comments under [2]), this was not deemed necessary. The positive form (+UseCompressedClassPointers) is used much more frequently, pointlessly so, since +UseCompressedClassPointers is the default. However, deprecating the switch would yield warnings for the positive form, pointlessly so.

      Specification

      diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp
      index ab05cbeb891..173edb79387 100644
      --- a/src/hotspot/share/runtime/arguments.cpp
      +++ b/src/hotspot/share/runtime/arguments.cpp
      @@ -534,6 +534,7 @@ static SpecialFlag const special_jvm_flags[] = {
         { "MetaspaceReclaimPolicy",       JDK_Version::undefined(), JDK_Version::jdk(21), JDK_Version::undefined() },
         { "ZGenerational",                JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::undefined() },
         { "ZMarkStackSpaceLimit",         JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::undefined() },
      +  { "UseCompressedClassPointers",   JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::undefined() },
      
       #ifdef ASSERT
         { "DummyObsoleteTestFlag",        JDK_Version::undefined(), JDK_Version::jdk(18), JDK_Version::undefined() },

      Further information

      A discussion was held on hotspot-dev previously, see [2]. A JEP was created for this topic, which we will abandon in favour of this CSR [3]. The comment section of this JEP holds some valuable insights.

      [1] https://github.com/jobar/ansidoop/blob/9618e31d65089b7586ac1c4f76de5bd748e59c4b/roles/spark-common/templates/spark-defaults.conf.j2#L9-L17

      [2] https://mail.openjdk.org/pipermail/hotspot-dev/2025-February/101023.html

      [3] https://bugs.openjdk.org/browse/JDK-8350272

      [4] https://mail.openjdk.org/pipermail/lilliput-dev/2024-July/001809.html

      [5] https://openjdk.org/jeps/503

            stuefe Thomas Stuefe
            stuefe Thomas Stuefe
            Vladimir Kozlov
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated: