| JamaicaVM 3.2 — User Documentation: The Virtual Machine for Realtime and Embedded Systems | ||
|---|---|---|
| Prev | Chapter 7. Memory Management Configuration | Next |
For predictable execution of memory allocation, more care is needed when selecting memory related options. No dynamic heap size increments should be used if the break introduces by the heap size expansion can harm the realtime guarantees required by the application. Also, the heap size must be set such that the implied garbage collection work is tolerable.
The memory analyzer tool is used to determine the garbage collector settings during a runtime measurement. Together with the numblocks command, they make possible an accurate prediction of the time required for each memory allocation. The following sections explain the required configuration of the system.
The Memory Analyzer is a tool for fine tuning an application's memory requirements and the realtime guarantees that can be given when allocating objects within Java code running on the Jamaica Virtual Machine.
The Memory Analyzer is integrated into the Builder tool. It can be activated by setting the command line option -analyze <accuracy>.
Using the Memory Analyzer Tool is a three-step process: First, an application is built using the Memory Analyzer. The resulting executable file can then be executed to determine its memory requirements. Finally, the result of the execution can be used to fine tune the final version of the application.
As an example, we will build the HelloWorld example application that was presented on the Builder page. This can be done by providing the option -analyze to the builder and giving the required accuracy of the analysis in percent. In this example, we use an accuracy of 5%:
> jamaica -analyze=5 HelloWorld
Reading configuration from '/usr/local/jamaica/etc/jamaica.conf'...
Jamaica Builder Tool 3.0 Release 1
Generating code for target 'linux-gnu-i686', optimisation 'none'
+ HelloWorld__.c
+ HelloWorld__.h
Class file compaction gain: 66.333336% (7189676 ==> 2420524)
+ HelloWorld__nc.o
* C compiling 'HelloWorld__.c'
* linking
* stripping
|
The build process is performed exactly as it would be without the -analyze option, except that the garbage collector is told to measure the application's memory usage with the given accuracy. The result of this measurement is printed to the console after execution of the application:
> ./HelloWorld
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
...
### Application used at most 1125600 bytes for the Java
### heap (accuracy 5%).
###
### Worst case allocation overhead:
### heapSize dynamic GC const GC work
### 3804K 6 3
### 3177K 7 4
### 2776K 8 4
### 2504K 9 4
### 2305K 10 4
### 2036K 12 5
### 1867K 14 5
### 1745K 16 6
### 1658K 18 6
### 1591K 20 7
### 1496K 24 8
### 1432K 28 9
### 1387K 32 10
### 1351K 36 11
### 1323K 40 12
### 1283K 48 14
### 1255K 56 17
### 1236K 64 19
### 1189K 96 27
### 1166K 128 36
### 1143K 192 53
### 1133K 256 69
### 1122K 384 100
|
In this example, the application uses a maximum of 1125600 bytes of memory for the Java heap. The specified accuracy of 5% means that the actual memory usage of the application will be up to 5% less than the measured value, but not higher. JamaicaVM uses the Java heap to store all dynamic data structures internal to the virtual machine (as Java stacks, classes, etc.), which explains the relatively high memory demand for this small application.
In addition to printing the measured memory requirements of the application, in analyze mode Jamaica also prints a small table of possible heap sizes and corresponding worst case allocation overheads. The worst case allocation overhead is given in units of garbage collection work that are needed to allocate one block of memory (typically 32 bytes). The amount of time in which these units of garbage collection work can be done is platform dependent. On the PowerPC processor, a unit corresponds to the execution of about 160 machine instructions.
From this printed table, we can choose the minimum heap size that corresponds to the desired worst case execution time for the allocation of one block of memory. A heap size of 1591K corresponds to a worst case of 20 units of garbage collection work (3200 machine instructions on the PowerPC) per block allocation, while a smaller heap size of, for example, 1323K can only guarantee a worst case execution time of 40 units of garbage collection work (6400 PowerPC- instructions) per block allocation.
If we find that for our application 14 units of garbage collection work per allocation is sufficient to satisfy all realtime requirements, one can build the final application using a heap of 1867K:
> jamaica -heapSize=1867K -maxHeapSize=1867K HelloWorld
Reading configuration from '/usr/local/jamaica/etc/jamaica.conf'...
Jamaica Builder Tool 3.0 Release 1
Generating code for target 'linux-gnu-i686', optimisation 'none'
+ HelloWorld__.c
+ HelloWorld__.h
Class file compaction gain: 66.333336% (7189676 ==> 2420524)
+ HelloWorld__nc.o
* C compiling 'HelloWorld__.c'
* linking
* stripping
|
Note that both options, heapSize and maxHeapSize, are set to the same value. This creates an application that has the same initial heap size and maximum heap size, i.e., the heap size is not increased dynamically. This is required to ensure that the maximum of 14 units of garbage collection work per unit of allocation is respected during the whole execution of the application. With a dynamically growing heap size, an allocation that happens to require increasing the heap size will otherwise be blocked until the heap size is increased sufficiently.
The resulting application will now run with the minimum amount of memory that guarantees the selected worst case execution time for memory allocation. The actual amount of garbage collection work that is performed is determined dynamically depending on the current state of the application (including, for example, its memory usage) and will in most cases be significantly lower than the described worst case behavior, so that on average an allocation is significantly cheaper than the worst case allocation cost.
For applications that require best worst case execution times, where average case execution time is less important, Jamaica also provides the option to statically select the amount of garbage collection work. This forces the given amount of garbage collection work to be performed at any allocation, without regard to the current state of the application. The advantage of this static mode is that worst case execution times are lower than using dynamic determination of garbage collection work. The disadvantage is that any allocation requires this worst case amount of garbage collection work.
The output generated using the option -analyze also shows possible values for the constant garbage collection option. In the example above, the amount of garbage collection work required varies from 3 to 100 units for heap sizes between 3804k and 1122k bytes. A unit of garbage collection work is the same as in the dynamic case, i.e., about 160 machine instructions on the PowerPC processor.
Similarly, if we want to give the same guarantee of 14 units of work for the worst case execution time of the allocation of a block of memory, a heap size of 1283k bytes is sufficient. To inform the builder that constant garbage collection work should be used, the option -constGCwork and the number of units of work should be specified when building the application:
> jamaica -heapSize=1283K -maxHeapSize=1283K
-constGCwork=14 HelloWorld
Reading configuration from '/usr/local/jamaica/etc/jamaica.conf'...
Jamaica Builder Tool 3.0 Release 1
Generating code for target 'linux-gnu-i686', optimisation 'none'
+ HelloWorld__.c
+ HelloWorld__.h
Class file compaction gain: 66.333336% (7189676 ==> 2420524)
+ HelloWorld__nc.o
* C compiling 'HelloWorld__.c'
* linking
* stripping
|
Which option you should choose (dynamic mode or constant garbage collection) depends strongly on the kind of application. If worst case execution time and low jitter are the most important criteria, constant garbage collection work will usually provide the better performance with smaller heap sizes. But if average case execution time is also an issue, dynamic mode will typically give better overall throughput, even though for equal heap sizes the guaranteed worst case execution time is longer with dynamic mode than with constant garbage collection work.
Gradual degradation may also be important. Dynamic mode and constant garbage collection work differ significantly when the application does not stay within the memory bounds that were fixed when the application was built.
There are a number of reasons an application might be using more memory:
The application input data might be bigger than originally anticipated.
The application was built with an incorrect or outdated -heapSize argument.
A bug in the application may be causing a memory leak and gradual use of more memory than expected.
Whatever the reason, it may be important in some environments to understand the behavior of memory management in the case the application exceeds the assumed heap usage.
In dynamic mode, the worst-case execution time for an allocation can no longer be guaranteed as soon as the application uses more memory. But as long as the excess heap used stays small, the worst-case execution time will increase only slightly. This means that the original worst-case execution time may not be exceeded at all or only by a small amount. However, the garbage collector will still work properly and recycle enough memory to keep the application running.
If the constant garbage collection work option is chosen, the amount of garbage-collection work will not increase even if the application uses more memory than originally anticipated. Allocations will still be made within the same worst-case execution time. Instead, the collector cannot give a guarantee that it will recycle memory fast enough. This means that the application may fail abruptly with an out-of-memory error. Static mode does not provide graceful degradation of performance in this case, but may cause abrupt failure even if the application exceeds the expected memory requirements only slightly.
As we have just seen, the worst case execution time of an allocation depends on the amount of garbage collection work that has to be performed for the allocation. The configuration of the heap as shown above gives a worst case number of garbage collection work units that need to be performed for the allocation of one block of memory. In order to determine the actual time an allocation might take in the worst case, it is also necessary to know the number of blocks that will be allocated and the platform dependent worst case execution time of one unit of garbage collection work.
For an allocation statement S we get the following equation to calculate the worst case-execution time:
wcet(S) = numblocks(S) · max_gc_units · wcet_of_gc_unit
Where
wcet(S) is the worst case execution time of the allocation
numblocks(S) gives the number of blocks that need to be allocated
max_gc_units is the maximum number of garbage collection units that need to be performed for the allocation of one block
wcet_of_gc_unit is the platform dependent worst case execution time of a single unit of garbage collection work.
An important value required to calculate the worst case execution time of an allocation is the number of blocks required to represent the allocated object. Jamaica provides the NumBlocks tool to determine this number of blocks. The following section describes the usage of this tool in detail.
A variety of arguments can be provided to control the NumBlocks tool. The arguments can be provided directly to numblocks, or using the property file numblocks.conf.
The syntax is as follows:
numblocks [-help (--help, -h, -?)] [-Xhelp] [-version]
[-verbose] [-showSettings] [-saveSettings=<file>]
[-configuration=<file>] [-all] [-classpath
(-cp)=<path>] [-bootclasspath
(-Xbootclasspath)=<path>] [-numThreads=<n>]
class1{"["<len>"]"} [... classn{"["<len>"]"}] |
These are general options providing information about numblocks itself or enabling the use of script files that specify further options
The help option displays the usage of the NumBlocks tool and a short description of all possible standard command line options
The Xhelp option displays the usage of the NumBlocks tool and a short description of all possible standard and extended command line options. Extended command line options are not needed for normal control of the numblocks command. They are used to configure tools and options to provide tools required internally for JamaicaVM development.
The version option prints the version of the Jamaica Builder Tool and exits.
The verbose option sets verbose level. If verbose level is greater than 0, additional output on the state of the build process is printed to standard out.
The currently used options of the JamaicaVM Builder are written to stdout in property file format. To make these the default settings, copy these options into <JAMAICA_HOME>/etc/numblocks.conf.
The currently used options of the JamaicaVM Builder are written to stdout in property file format. To make these the default settings, copy these options into <JAMAICA_HOME>/etc/numblocks.conf.
The set of options used to perform numblocks are read from the provided file. The format used to define options must be identical to the default configuration file <JAMAICA_HOME>/etc/numblocks.conf.
These options allow to specify classes and paths to be used by numblocks.
If the all option is set, the number of blocks required for the allocation of objects of all classes in this application will be displayed.
The classpath option specifies the paths that are to be used to search for class files. A list of paths separated by the path separator char (`:' on Unix systems) can be specified. This list will be traversed from left to right when the Builder tries to load a class.
The bootclasspath option specifies the default path used for loading system classes.
The numThreads Builder option has an influence on the number of blocks required for some objects. Consequently, it must be provided to numblocks for all applications that are built with a specified number of threads.
Imagine that we want to determine the worst-case-execution-time of an allocation of a StringBuffer object, as was done in the HelloWorld.java-example shown above. If this example was built with the dynamic garbage-collection option and a heap size of 443K bytes, we get
max_gc_units = 14
as has been shown above.
If our target platform gives a worst case execution time for one unit of garbage collection work of 1.6μs, we have
wcet_of_gc_unit = 1.6μs
We use the numblocks tool to find the number of blocks required for the allocation of a java.lang.StringBuffer object:
> numblocks java.lang.StringBuffer 1 |
A StringBuffer object requires just a single block of memory, so that
numblocks(new StringBuffer()) = 1
and the total worst case-execution time of the allocation becomes
wcet(new StringBuffer()) = 1· 14· 1.6μs = 22.4μs
If we had used the constant garbage collection option with the same heap size, the amount of garbage collection work on an allocation of one block could have been fixed at 6 units. In that case the worst-case-execution-time of the allocation becomes
wcetconstGCwork(new StringBuffer()) = 1· 6· 1.6μs = 9.6μs
After creation of the java.lang.StringBuffer object, a character array of 16 elements is allocated during the execution of StringBuffer's initialization routine. For this allocation, we can determine the worst-case-execution-time by first determining the number of blocks required:
> numblocks "char[16]" 2 |
and we get
wcet(new char[16]) = 2· 14· 1.6μs = 44.8μs
and
wcetconstGCwork(new char[16]) = 2· 6· 1.6μs = 19.2μs
Here are some typical values for the number of blocks required for the allocation of different objects or arrays:
Table 7-1. Typical number of blocks for objects
| Class | numblocks |
|---|---|
| new java.util.Vector() | 1 |
| new boolean[1024] | 5 |
| new byte[64] | 3 |
| new char[256] | 19 |
| new int[1024] | 147 |
| new Object[1000000] | 142860 |
The output of numblocks when using the -all option can be used to get a quick overview on the number of blocks for all classes used by an application:
> numblocks -all HelloWorld
Class: Blocks:
HelloWorld 1
com/aicas/jamaica/AccessCheck 1
com/aicas/jamaica/Archive 1
com/aicas/jamaica/Archive$PathVisitor 1
com/aicas/jamaica/Archive$Visitor 1
com/aicas/jamaica/ArchiveDirectory 1
com/aicas/jamaica/ArchiveEntry 1
com/aicas/jamaica/BuilderError 2
com/aicas/jamaica/DirectoryEntry 2
com/aicas/jamaica/NYIException 2
com/aicas/jamaica/lang/CpuTime 1
com/aicas/jamaica/lang/Debug 1
com/aicas/jamaica/lang/LowLevelRTSJ 1
com/aicas/jamaica/lang/Process 1
com/aicas/jamaica/lang/Profile 1
com/aicas/jamaica/lang/Profile$1 5
com/aicas/jamaica/lang/Profile$2 4
com/aicas/jamaica/lang/Profile$3 1
com/aicas/jamaica/lang/Profile$Count 1
com/aicas/jamaica/lang/Wait 1
com/aicas/jamaica/util/False 1
com/aicas/java/net/protocol/rom/Handler 1
com/aicas/java/net/protocol/rom/RomURLCnctn 1
...
|