This section discusses the effect of the Java Virtual Machine’s garbage collector with regards to Neo4j performance.
The heap is separated into an old generation and a young generation. New objects are allocated in the young generation, and then later moved to the old generation, if they stay live (in use) for long enough. When a generation fills up, the garbage collector performs a collection, during which all other threads in the process are paused. The young generation is quick to collect since the pause time correlates with the live set of objects, and is independent of the size of the young generation. In the old generation, pause times roughly correlates with the size of the heap. For this reason, the heap should ideally be sized and tuned such that transaction and query state never makes it to the old generation.
The heap size is configured with the
dbms.memory.heap.max_size (in MBs) setting in the neo4j.conf file.
The initial size of the heap is specified by the
dbms.memory.heap.initial_size setting, or with the
-Xms???m flag, or chosen heuristically by the JVM itself if left unspecified.
The JVM will automatically grow the heap as needed, up to the maximum size.
The growing of the heap requires a full garbage collection cycle.
It is recommended to set the initial heap size and the maximum heap size to the same value.
This way the pause that happens when the garbage collector grows the heap can be avoided.
The ratio of the size between the old generation and the new generation of the heap is controlled by the
N is typically between 2 and 8 by default.
A ratio of 2 means that the old generation size, divided by the new generation size, is equal to 2.
In other words, two thirds of the heap memory will be dedicated to the old generation.
A ratio of 3 will dedicate three quarters of the heap to the old generation, and a ratio of 1 will keep the two generations
about the same size.
A ratio of 1 is quite aggressive, but may be necessary if your transactions changes a lot of data.
Having a large new generation can also be important if you run Cypher queries that need to keep a lot of data resident, for
example when sorting big result sets.
If the new generation is too small, short-lived objects may be moved to the old generation too soon. This is called premature promotion and will slow the database down by increasing the frequency of old generation garbage collection cycles. If the new generation is too big, the garbage collector may decide that the old generation does not have enough space to fit all the objects it expects to promote from the new to the old generation. This turns new generation garbage collection cycles into old generation garbage collection cycles, again slowing the database down. Running more concurrent threads means that more allocations can take place in a given span of time, in turn increasing the pressure on the new generation in particular.
The Compressed OOPs feature in the JVM allows object references to be compressed to use only 32 bits. The feature saves a lot of memory, but is not enabled for heaps larger than 32 GB. Gains from increasing the heap size beyond 32 GB can therefore be small or even negative, unless the increase is significant (64 GB or above).
Neo4j has a number of long-lived objects, that stay around in the old generation, effectively for the lifetime of the Java process. To process them efficiently, and without adversely affecting the garbage collection pause time, we recommend using a concurrent garbage collector.
How to tune the specific garbage collection algorithm depends on both the JVM version and the workload. It is recommended to test the garbage collection settings under realistic load for days or weeks. Problems like heap fragmentation can take a long time to surface.
To gain good performance, these are the things to look into first:
Use a concurrent garbage collector. We find that -XX:+UseG1GC works well in most use-cases.
Start the JVM with the -server flag and a good sized heap.
Edit the following properties:
initial heap size (in MB)
maximum heap size (in MB)
additional literal JVM parameter