Going over Xmx32G heap boundary means you will have less memory available

by Mikhail Vorontsov

This small article will remind you what happens to Oracle JVM once your heap setting goes over 32G. By default, all references in JVM occupy 4 bytes on heaps under 32G. This decision is made by JVM at start-up time. You can use 8 byte references on small heaps if you will clear the -XX:-UseCompressedOops JVM option (it does not make any sense for production systems!).

Once your heap exceeds 32G, you are in 64 bit land, so your object references will now use 8 bytes instead of 4. As Scott Oaks mentioned in his “Java Performance: The Definitive Guide” book (pages 234-236, read my review of this book here), an average Java program uses about 20% of heap for object references. It means that by setting anything between Xmx32G and Xmx37G – Xmx38G you will actually reduce the amount of heap available for your application (actual numbers, of course, depend on your application). This may become a big surprise for anyone thinking that adding extra memory will let his/her application to process more data 🙂

Test – populating a LinkedList<Integer>

I have decided to test the worst case scenario – populating a LinkedList<Integer> with increasing consecutive values. It is an interesting exercise: calculate how much heap do you need to insert 2 billion Integer values into a LinkedList. I’ll leave it to you 🙂

The test code is extremely simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Mem32Test {
    public static void main(String[] args) {
        List<Integer> lst = new LinkedList<>();
        int i = 0;
        while ( true )
        {
            lst.add( new Integer( i++ ) );
            if ( ( i & 0xFFFF ) == 0 )
                System.out.println( i ); //shows where you are 🙂
            if ( i == System.currentTimeMillis() )
                break; //otherwise will not compile
        }
        System.out.println( lst.size() ); //needed to avoid dead code optimizations
    }
}
public class Mem32Test {
    public static void main(String[] args) {
        List<Integer> lst = new LinkedList<>();
        int i = 0;
        while ( true )
        {
            lst.add( new Integer( i++ ) );
            if ( ( i & 0xFFFF ) == 0 )
                System.out.println( i ); //shows where you are 🙂
            if ( i == System.currentTimeMillis() )
                break; //otherwise will not compile
        }
        System.out.println( lst.size() ); //needed to avoid dead code optimizations
    }
}

You should run this code with Xmx and verbose:gc (or -XX:+PrintGCDetails) options. You need to see the garbage collection logs to understand when you run out of memory (it may be pretty long before you’ll get an actual OOM).

First of all, I’ve found an exact spot where JVM switches to 64 bit references – it is Xmx32767M (surprisingly, 1Mb less than 32G). After that I have noticed that the amount of memory actually available to the application does not increase linearly with your heap. Instead it seems to grow in steps (see what happens between Xmx49200M and Xmx49500M) – this is something I want to investigate further.

Test results

Number of elements in the LinkedList Heap size
666,697,728 Xmx32700M
667,287,552 Xmx32730M
667,680,768 Xmx32750M
667,877,376 Xmx32760M
668,008,448 Xmx32764M
668,139,520 Xmx32765M
668,008,448 Xmx32766M
422,510,592 Xmx32767M
429,391,872 Xmx33700M
535,166,976 Xmx42000M
639,041,536 Xmx48700M
643,039,232 Xmx49200M
731,578,368 Xmx49500M
734,658,560 Xmx49700M
1,442,119,680 Xmx110000M

As you can see, a number of list elements drops dramatically from 668 millions to 422 millions at Xmx32767M due to switching to 64 bit references.

Let’s see why we have such a large drop in number of elements we can insert fit into a LinkedList. A JDK LinkedList is a double linked list. So, each Node contains prev and next references as well as a data item (an object reference as well).

Each Java object in 32 bit mode contains a 12 byte header followed by object fields. Each object memory consumption is aligned by 8 bytes. So, a Node occupies 12 + 4 * 3 = 24 bytes in 32 bit mode. An Integer needs 12 + 4 = 16 bytes (no alignment padding is required in both those cases).

Once you enter 64 bit territory, an Object header occupies 16 bytes instead of 12. Each Object reference now uses 8 bytes instead of 4. And do not forget of 8 bytes alignment. As a result, a Node occupies 16 + 3 * 8 = 40 bytes in 64 bit mode. An Integer occupies 16 + 4 = 20 bytes, which is aligned to 24 bytes.

As a result, each LinkedList element size grows from 40 to 64 bytes once you have switched from 32 to 64 bits.

Some memory tuning hints

As I have mentioned above, using JVM with over 32G heaps means a rather large performance penalty. Besides increased application footprint, JVM garbage collector will also have to deal with all these objects (add -XX:+PrintGCDetails option to your JVM to see the impact of garbage collection on your application).

I have already written about a few simple tricks you can apply to a not optimized application in order to reduce its memory footprint (in some cases memory gains could be quite huge, so do not ignore these advices!):

  • Your application may contain a large number of strings with equal contents. If you are on Java 7 and newer, you should consider string interning – it is an ultimate tool for getting rid of duplicate strings, but it should be used with caution – you should intern only medium to long living strings, which are likely to be duplicates. If you use Java 8 update 20 or newer, try using string deduplication – JVM will take care of string duplicates on its own (you must use G1 garbage collector for this feature!)
  • If you have a lot of numeric wrappers in heap, like Integer or Double, you are likely to keep them inside collections. There is no excuse in 2015 to avoid using primitive collections! I have recently written a large overview of hash maps in various primitive collection libraries. You may also take a look at my older Trove article.
  • Finally, look through a series of general memory consumption / memory saving articles I have written some time ago ( part 1, part 2, part 3, part 4).

Summary

  • Be careful when you increase your application heap size over 32G (from under 32G to over 32G) – JVM switches to 64 bit object references at that moment, which means that your application may end up with less available heap space. A rule of thumb is to jump from 32G right to 37-38G and continue adding memory from that point. The actual area of “grey” territory depends on your application – the bigger an average Java object in your application, the smaller is the overhead.
  • It may be wise to reduce your application memory footprint below 32G instead of dealing with a bigger heap. Look at my articles for some ideas: String interning, String deduplication, Hash maps and other primitive collections, Trove).

One thought on “Going over Xmx32G heap boundary means you will have less memory available

  1. Pingback: String packing: conversion to more compact data types

Leave a Reply

Your email address will not be published. Required fields are marked *