Chapter 7. Memory Management Configuration

JamaicaVM provides the only efficient hard-realtime garbage collector
available for Java implementations on the marked today. This chapter will
first explain how this garbage collection technology can be used to obtain the best
results for applications that have soft-realtime requirements before explaining
the more fine-grained tuning required for realtime applications.

For most non-realtime applications, the default memory management settings of
JamaicaVM perform well: The heap size is set to a small starting size and
is extended up to a maximum size automatically whenever the heap is not
sufficient or the garbage collection work becomes too high. However, in some
situations, some specific settings may help to improve the performance of a
soft-realtime application.

The default initial heap size is a small value. The heap size is increased
on demand when the application exceeds the available memory or the garbage
collection work required to collect memory in this small heap becomes
too high. This means that an application that on startup requires
significantly more memory than the initial heap size will see its startup time
increased by repeated incremental heap size expansion.

The obvious solution here is to set the initial heap size to a value large
enough for the application to start. The Jamaica builder option
heapSize (see Chapter 6) and
the virtual machine option Xms<size>
can be employed to set a higher size.

Starting off with a larger initial heap not only prevents the overhead of
incremental heap expansion, but it also reduces the garbage collection work
during startup. The garbage collector determines the amount of garbage
collection work from the amount of free memory. With an larger initial heap
size, the initial amount of free memory is larger, reducing the amount of garbage
collection work during startup.

The maximum heap size specified via builder option
maxHeapSize (see
Chapter 6) and the virtual machine option
Xmx should be set to the maximum amount of
memory on the target system that should be available to the Java application.
Setting this option has no direct impact on the performance of the application
as long as the application's memory demand does not come close to this limit.
If the maximum heap size is not sufficient, the application will receive an
OutOfMemoryError at runtime.

However, it may make sense to set the initial heap size to the same value as
the maximum heap size whenever the initial heap demand of the application is of
no importance for the remaining system. Setting initial heap size and maximum
heap size to the same value has two main consequences. First, as has been seen
in the Section called Initial heap size above, setting the initial heap size
to a higher value avoids the overhead of dynamically expanding the heap and
reduces the amount of garbage collection work during startup. Second,
JamaicaVM's memory management code contains some optimizations that are only
applicable to a non-increasing heap memory space, so overall memory
management overhead will be reduced if the same value is chosen for the initial and
the maximum heap size.

Before the memory used by an object that has a
finalize method can be reclaimed, this
finalize method needs to be executed. A
dedicated thread, the FinalizerThread executes
these finalize methods and otherwise sleeps
waiting for the garbage collector to find objects to be finalized.

In order to prevent the system from running out of memory, the
FinalizerThread must receive sufficient CPU time.
Its default priority is therefore set to 10, the highest priority a Java thread
may have. Consequently, any thread with a lower priority will be preempted
whenever an object is found to require finalization.

Selecting a lower finalizer thread priority may cause the finalizer thread to
starve if a higher priority thread does not yield the CPU for a longer period
of time. However, if it can be guaranteed that the finalizer thread will not
starve, system performance may be improved by running the finalizer thread
at a lower priority. Then, a higher priority thread that performs memory
allocation will not be preempted by finalizer thread execution.

The builder option finalizerPri or the
environment variable JAMAICAVM_FINALIZERPRI
can be used to set this priority to a lower value. In an application that has
sufficient idle CPU time in between urgent activities, a finalizer priority
lower than the priority of all other threads may be sufficient.

JamaicaVM's default behavior is to perform garbage collection work at
memory allocation time. This ensures a fair accounting of the garbage
collection work: Those threads with the highest allocation rate will perform
correspondingly more garbage collection work.

However, this approach may slow down threads that run only occasionally and
perform some allocation bursts, e.g., changing the input mask or opening a new
window in a graphical user interface.

To avoid penalizing these time-critical tasks by allocation work, JamaicaVM
uses a low priority memory reservation thread that runs to pre-allocate a given
percentage of the heap memory. This reserved memory can then be allocated by
any allocation bursts without the need to perform garbage collection work.
Consequently, an application with bursts of allocation activity with sufficient
idle time between these bursts will see an improved performance.

The maximum amount of memory that will be reserved by the memory reservation thread is
given as a percentage of the total memory. The default value for this
percentage is 10%. It can be set via the builder options
-reservedMemory
and -reservedMemoryFromEnv, or for the virtual machine
via the environment variable JAMAICAVM_RESERVEDMEMORY.

An allocation burst that exceeds the amount of reserved memory will have to
fall back to perform garbage collection work as soon as the amount of reserved
memory is exceeded. This may occur if the maximum amount of reserved memory is
less than the memory allocated during the burst or if there is too little idle
time in between consecutive bursts such as when the reservation thread cannot
catch up and reserve the maximum amount of memory.

For an application that cannot guarantee sufficient idle time for the memory
reservation thread, the amount of reserved memory should not be set to a high
percentage. Higher values will increase the worst case garbage collection work
that will have to be performed on an allocation, since after the reserved
memory was allocated, there is less memory remaining to perform sufficient
garbage collection work to reclaim memory before the free memory is
exhausted.

A realtime application without allocation bursts and sufficient idle time should therefor
run with the maximum amount of reserved memory set to 0%.

The priority default of the memory reservation thread is the Java priority 1
with the scheduler instructed to give preference to other Java threads that run
at priority 1 (i.e., with a priority micro adjustment of
-1).
The priority can be changed by setting the Java property
jamaica.reservation_thread_priority to an integer value
larger than or equal to 0. If set, the memory reservation
thread will run at the given Java priority. A value of 0 will result at a Java priority 1 with micro adjustment
-1, i.e., the scheduler will give preference to other threads running at
priority 1.

In JamaicaVM, the garbage collection work is by default performed in the
application threads, so there is no need for a dedicated garbage collection
thread. However, in an application that provides idle CPU time, one might wish
to use this idle time to take load from the main threads and perform garbage
collection work during idle time. JamaicaVM permits this by enabling the use
of a garbage collection thread (GC thread).

The GC thread is by default not activated. It can be activated by setting a
Java system property jamaica.gcthread_pri. The
value of this property must be the desired thread priority the GC thread should
run at. Typically, the lowest Java thread priority
1 is the best value to use an application's
idle time.

Since the application may run other Java threads at priority
1, the property may be set to 0, which
results in a GC thread Java priority 1 and the scheduler set to give preference
to other Java threads running at priority 1.

The GC thread uses this idle time to perform garbage collection
work so that the amount of free memory is larger and the application threads
can on average perform allocations faster. However, additional CPU time is
taken from any other applications on the system that may run at lower
priorities.

Even when a GC thread is used, not all of the available CPU time is
necessarily used by the Java application. The GC thread will periodically stop
its activity when only a little memory was reclaimed during a GC cycle. Lower
priority threads may therefore still obtain some CPU time even if a GC thread
is used.

For applications that do not have any realtime constraints, but that require
the best average time performance, JamaicaVM's builder provides options to
disable realtime garbage collection, and to use a stop-the-world garbage
collector instead.

In stop-the-world mode, no garbage collection work will be performed until the
system runs out of free memory. Then, all threads that perform memory
allocation will be stopped to perform garbage collection work until a complete
garbage collection cycle is finished and memory was reclaimed. Any thread that
does not perform memory allocation may, however, continue execution even while
the stop-the-world garbage collector is running.

The stop-the-world garbage collector is enabled via the builder option
-stopTheWorldGC. Alternatively, the builder option
-constGCwork=-1 can be used, or
-constGCworkFromEnv=var with the environment variable
var set to -1.

JamaicaVM additionally provides an atomic garbage collector that requires
stopping of all threads of the Java application during a stop-the-world
garbage collection cycle. This has the disadvantage that even threads that do
not allocate heap memory will have to be stopped during the GC cycle. However,
it avoids the need to track heap modifications performed by threads running
parallel to the garbage collector (so called write-barrier code). The result
is a slightly increased performance of compiled code.

The atomic garbage collector is enabled via the builder option
-atomicGC. Alternatively, the builder option
-constGCwork=-2 can be used, or
-constGCworkFromEnv=var with the environment variable
var set to -2.

Please note the using of the memory reservation thread or the GC thread should
be disabled when stop-the-world or atomic GC is used.