Why are 32-bit machines limited to 4GB memory?
On a 32-bit machine there are only 2^32 (~= 4 billion) distinct memory addresses.Each memory address can hold 8 bits (= 1 byte).
That means that in total we can reference ~ 4 billion bytes which is approximately equal to 4 GB.
In practice, all of this memory won't be available to the JVM. Some of it will be used by the OS and any other processes running on the machine. Even less will be available to the heap because the JVM needs to store lots of other things including thread stacks, GC info, native memory, compiled code etc.
So 64-bit machines are the solution?
64 bit machines can reference 2^64 memory addresses which is more than 16 million terabytes of data. Therefore, it should be fine for a Java heap right?The problem with this addressing is that you end up with huge pointers to objects that you have to store in the heap! The addresses suddenly take twice the amount of memory, which isn't terribly efficient.
What if you don't actually want a multi-terabyte heap? Is there some kind of middle ground?
The middle ground: Compressed Oops
Imagine we have 35-bit addressing. That would mean that we could store up to 2^35 (~= 32GB) memory addresses in heap.Wouldn't that be great?! Obviously it isn't going to be easy because there aren't any 35-bit machines with 35-bit registers in which to hold memory addresses.
However, the JVM can do something very clever, where it assumes that is has a 35 bit number but the last 3 bits are all zero. When it writes to the memory address it adds the three zeros to lookup the address correctly but when it's just holding the memory address, it only stores 32 bits.
However this means that the JVM can only access every 8th memory address:
000 (0), 1_000 (8), 10_000 (16), 11_000 (24), 100_000 (32), 101_000 (40) etc
This means that the JVM needs to allocate memory in 8-byte chunks (because each memory address holds 1 byte and we are dealing with the memory addresses in chunks of 8).
However this is how the JVM works anyway, so it's all fine and nothing is lost. Convenient, eh?
There is a little bit of fragmentation. If you allocate an object that is 7 bytes then you have 1 empty byte but the impact isn't too big. Though this is probably why we don't try and use 2^36 addresses. That would mean being 16-byte aligned which would likely have much worse fragmentation and wasted memory gaps.
When does the JVM use Compressed Oops?
From Java 7 onwards, by default if the heap size is >4GB but <32GB then the flag, +XX:UseCompressedOops is switched on.Jol
To see how objects are aligned in memory, Jol is a cute little tool.Example 1:
import org.openjdk.jol.info.ClassLayout;import org.openjdk.jol.vm.VM;import static java.lang.System.out; public class Jol { public static void main(String[] args) throws Exception { out.println(VM.current().details()); out.println(ClassLayout.parseClass(A.class).toPrintable()); } public static class A { boolean f; } }
Gives the following output:
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
com.ojha.Jol$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 1 boolean A.f N/A
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
We can see that the object header took 12 bytes, the boolean took 1 byte and 3 bytes were wasted
Example 2 (Same as above but add in a little integer)
import org.openjdk.jol.info.ClassLayout;import org.openjdk.jol.vm.VM;import static java.lang.System.out; public class Jol { public static void main(String[] args) throws Exception { out.println(VM.current().details()); out.println(ClassLayout.parseClass(A.class).toPrintable()); } public static class A { boolean f; int j; } }
Output:
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
com.ojha.Jol$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 int A.j N/A
16 1 boolean A.f N/A
17 7 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total