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.
In summary, to obtain the best performance in your soft-realtime application, follow the following recommendations.
Set initial heap size as large as possible.
Set initial heap size and maximum heap size to the same value if possible.
Set the finalizer thread priority to a low value if your system has enough idle time.
If your application uses allocation bursts with sufficient CPU idle time in between two allocation bursts, set the amount of reserved memory to fit with the largest allocation burst.
If your application does not have idle time with intermittent allocation bursts, set the amount of reserved memory to 0%.
Enable the GC thread if your system has idle time that can be used for garbage collection.