2

I have a computer with 64Gb of RAM and I have few small Java applications that needed up to 4Gb of RAM.

When Java app running on 32 bit platform it can use only 4GB but it uses direct object addresses. When Java app is running on bigger RAM but less than 32Gb then it uses compressed oops (COOPS) with 8 byte padding which adds some small overhead but anyway it's ok. But when run Java app on 64Gb heap then it uses full 64 bit addresses which consumes twice more memory for storing the addresses.

So my question is: can I run a Java app in 32bit mode so it can't use more than 4Gb but it will use the memory more efficiently. And can I run multiple such apps to use the whole 64Gb address space? Maybe I can use Docker somehow to limit memory for the Java process but I'm not sure if the process will be runned in the simple 32 bit mode because it anyway needs to get access to some memory address higher than 32Gb.

Maybe Docker can virtualilze the 64Gb address space to simple 32 bit addresses.

As I see this solution: we started a Java app and give it a part of heap from 32GB to 36Gb. The specified address space is 4Gb so here is enough just 32bit pointer (4 byte integer) so internally in all fields will be used just regular 32 pointer. But 32 bit pointer can be used for addresses from 0 to 4Gb while the app's heap starts from 32Gb. So internally JVM just converts 32bit pointer to 64 bit by adding 32Gb. This is similar to COOPS but instead of 8 byte padding we alwaysjust add address offset where the app's heap begins. But here it comes another problem when dealing with native code which can use the whole 64Gb address space.

UPD According to the JVM Anatomy Quark #23: Compressed References the applications will use Non-Zero Based Mode which allows to use up to 32Gb and doing the 3 bit shift. That's ok because the pointers still will be 32-bit but in fact if the MaxRAM for the app is 4Gb then we can skip the 3-bit shift. But this looks like not needed optimization because anyway it will use the same instruction in form 0xc(%r12,%r11,8),%eax but instead of 8 it will be 0.

For experiments we can run java -version itself as any other java app with VM options to set it's memory limit. For example we can set -XX:MaxRAM=2147483648 or -Xmx2G i.e. limit max memory to 2 Gibibyte.

To see the real COOPS mode we can add the option -Xlog:gc+heap+coops=info:

$ java -XX:MaxRAM=2147483648 -XX:MaxRAMPercentage=100 -Xlog:gc+heap+coops=info -version
[0.011s][info][gc,heap,coops] Heap address: 0x0000000080000000, size: 2048 MB, Compressed Oops mode: 32-bit
openjdk version "12.0.2" 2019-07-16
OpenJDK Runtime Environment Zulu12.3+11-CA (build 12.0.2+3)
OpenJDK 64-Bit Server VM Zulu12.3+11-CA (build 12.0.2+3, mixed mode, sharing)

Here we can see that COOPS are enabled and it use 32-bit mode, i.e. all pointers are 4 byte long. In the same time note Heap address: 0x0000000080000000 where the hex address 0000000080000000 corresponds to 2147483648 i.e. 2Gb. This value is called like "heap base" or HeapBaseMinAddress final flag or NarrowOopHeapBaseMin in sources and is 2Gb by default but may be changed via flag.

That means that while 32-bit in enough to address 4Gb but the first 2Gb will be reserved by the HeapBaseMinAddress so only 2Gb left for 32-bit mode. If you try to set -XX:MaxRAM=2147483649 i.e. higher on the one byte than 2Gb (2147483648) then the 32-bit mode will be disabled and it will be used "Zero based, Oop shift amount: 3" mode instead:

$ java -XX:MaxRAM=2147483649 -XX:MaxRAMPercentage=100 -Xlog:gc+heap+coops=info -version
[0.010s][info][gc,heap,coops] Heap address: 0x000000077fe00000, size: 2050 MB, Compressed Oops mode: Zero based, Oop shift amount: 3

What is unclear for me here is that the Heap address 0x000000077fe00000 is 32210157568 i.e. 29,998046875 Gb while on my current computer where I do the test I have only 16Gb or RAM.

In the same time if we specify HeapBaseMinAddress flag then it will be used as the Heap address:

$ java -XX:MaxRAM=2147483648 -XX:MaxRAMPercentage=100 -Xlog:gc+heap+coops=info -XX:HeapBaseMinAddress=8g -version
[0.008s][info][gc,heap,coops] Heap address: 0x0000000200000000, size: 2048 MB, Compressed Oops mode: Zero based, Oop shift amount: 3

Here 0x0000000200000000 corresponds to 8589934592 i.e. 8Gb.

Also I wrote a simple app that runs as a daemon and runned two instances of it and the both started in 32-bit mode and the both share the same Heap address:

$ java -XX:MaxRAM=2147483648 -XX:MaxRAMPercentage=100  -XX:+UnlockDiagnosticVMOptions -Xlog:gc+heap+coops=info  DaemonThreadTest
[0.011s][info][gc,heap,coops] Heap address: 0x0000000080000000, size: 2048 MB, Compressed Oops mode: 32-bit

I don't know how this is possible because I thought that COOPS 32-bit mode is possible only if the app can take 3rd and 4th gibibytes of the whole address space but if first daemon already took it then the second should use some other available space out of the first 4Gb. I'm confused here.

Anyway, I'll continue my test and learning on next wee on the computer with the 64Gb of RAM. Thank you for your replies.

All available modes can be seen from the universe.hpp

  // For UseCompressedOops
  // Narrow Oop encoding mode:
  // 0 - Use 32-bits oops without encoding when
  //     NarrowOopHeapBaseMin + heap_size < 4Gb
  // 1 - Use zero based compressed oops with encoding when
  //     NarrowOopHeapBaseMin + heap_size < 32Gb
  // 2 - Use compressed oops with disjoint heap base if
  //     base is 32G-aligned and base > 0. This allows certain
  //     optimizations in encoding/decoding.
  //     Disjoint: Bits used in base are disjoint from bits used
  //     for oops ==> oop = (cOop << 3) | base.  One can disjoint
  //     the bits of an oop into base and compressed oop.
  // 3 - Use compressed oops with heap base + encoding.
  enum NARROW_OOP_MODE {
    UnscaledNarrowOop  = 0,
    ZeroBasedNarrowOop = 1,
    DisjointBaseNarrowOop = 2,
    HeapBasedNarrowOop = 3,
    AnyNarrowOopMode = 4
  };

Non-zero disjoint mode

Sergey Ponomarev
  • 2,947
  • 1
  • 33
  • 43
  • Shouldn't enforcing the compressed OOPs with `-XX:+UseCompressedOops` be enough? – Danila Kiver Aug 02 '19 at 14:45
  • Not really, COOPS works only up to 32Gb – Sergey Ponomarev Aug 02 '19 at 14:47
  • Would installing a 32-bit JRE do what you want? (It's been a while since I used Java, but I seem to remember you can do this). – TripeHound Aug 02 '19 at 15:20
  • Yes, 32-bit JRE is still supported but I'm afraid that I can use only the address space from 0 to 4Gb and for only one app. So I can't run 16 apps in 32-bit mode on 64Gb RAM, I can run only one such app. Or I can? – Sergey Ponomarev Aug 02 '19 at 15:24
  • 1) Running 32-bit JVMs on a system with 64G RAM usually makes no sense, even from the memory footprint perspective. 2) Compressed OOPs are enabled when `-Xmx` is less than 32G, regardless of the actual RAM. 3) If you want to save memory, there are definitely more efficient ways of doing this, e.g. running multiple applications in one JVM, or reducing the non-heap parts, see [this](https://stackoverflow.com/a/53624438/3448419) and [this](https://shipilev.net/jvm/anatomy-quarks/12-native-memory-tracking/). – apangin Aug 02 '19 at 18:06
  • Ok, I just checked and when MaxRAM is set to 2Gb then java switches to 32bit mode addressation and this applies to all such processes. I’ll investigate later and leave my test here – Sergey Ponomarev Aug 02 '19 at 18:52
  • The 8 byte padding does *not* add any overhead to the execution, not even a small one, simply because objects always are allocated with an 8 byte alignment, whether you use compressed OOPs or not, the alignment existed even before “Compressed OOPs” were a thing, in fact, even my old Amiga from 1986 has an 8 byte alignment for memory allocations… *But*…you can raise the alignment, e.g. via `-XX:ObjectAlignmentInBytes=16`, CompressedOOPs will work even with 64GB heap size then, which will usually compensate any overhead from the alignment. As apangin said, you only need to set the right max heap – Holger Nov 06 '19 at 16:18

1 Answers1

1

If you want to run a java application in 32bit mode on a 64bit operating system, you need a 32 bit JVM.


A 32 bit JVM will run on a 64 bit OS and will probably be able to use more of the 4GB address space than is possible on a 32bit OS. However, you may not be able to use the full address space for ... architectural reasons.

So I can't run 16 apps in 32-bit mode on 64Gb RAM, I can run only one such app. Or I can?

Yes you can!

You can run as many as you have physical memory to support. A JVM is a process. It doesn't share a virtual address space with any other processes.


For older versions of Oracle Java, you can get 32 bit JVMs. With Java 9, Oracle ceased providing distributions of 32 bit JVMs, but the code for 32 bit platforms is still in the codebase.

32 bit JVMs for some platforms are available from third-party Java suppliers; e.g. Azul have x86-32 distros for Windows 2008r2 or later. You just need to do some searching.


Based on your update, you seem to be intent on getting multiple 4GB JVMs to run in a single 64GB virtual address space. I assume that you have a sound technical reason for doing this, though I can't imagine what it is. (And you haven't shared that ... so I wouldn't discount the possibility that this an X-Y problem.)

For that requirement, something based on compressed OOPs may be the way to go. However, I don't think it is as simple as that.

  1. This won't work if the processor is in 32 bit mode. So any native libraries must be 64 bit, and must be implemented to cooperate with this unusual way of working.
  2. If the processor is in 64 bit mode then the native code part of each of the JVMs will be capable of addressing the memory of the other JVMs. I don't think there is a practical way to prevent that. So a bug that classically would cause one JVM to panic could now crash / trash everything.
  3. The OOPs mechanism only applies to references to Java objects. The JVM also provides and uses a non-managed native heap for various purposes. Ensuring that a JVM's pointers stays within its allotted 4GB of the virtual address space would be challenging. Even ensuring that a JVM doesn't use more memory than it is allowed to would be challenging.

Finally, it strikes me that you may be trying to work around the problems that older versions of Java had when running in Docker containers. If so, you should check out this: "Docker Support in Java 10".

Now (Java 10+) you should be able to carve up your 64GB of RAM into 16 Docker containers, each running a JVM with a (roughly) 4GB heap + compressed OOPs.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216