I've spent a bit of time over the last couple of weeks looking into the memory allocation checking functionality provided by the MQ Managed File Transfer component. This functionality is turned on by setting the agent property enableMemoryAllocationChecking to the value true. According to the information for this property in the The agent.properties file topic in the MQ section of IBM Knowledge Center, setting this property to true means that the agent checks whether there is sufficient memory available to run a transfer before the transfer is started. If there is insufficient memory available, the transfer is put into recovery, which prevents the agent from failing with an out-of-memory.
This all sounds good, but how does this actually work?
That's a good question! Let's look at this now.
What happens when the enableMemoryAllocationChecking property is set to true
Now, when the property is set, the agent will create an internal "memory map" when it starts up. The size of the "memory map" is equal to:
(The maximum heap size of the Java Runtime Environment that the agent is running in) - 50MB
The maximum heap size of the Java Runtime Environment used by the agent can be found in the agent's Event Log (output0.log). When the agent starts up, it will write a message similar to this one into the event log:
The maximum amount of memory that the Java virtual machine will attempt to use is: '512'MB
If the memory allocation checking functionality was enabled for this agent, then the size of the "memory map" would be:
512 MB - 50 MB = 462 MB
The 50MB mentioned in this calculation is the amount of memory needed for the core agent runtime. Everything else in the heap is available for managed transfers.
When the agent first starts up, the "memory map" is empty. As it starts processing managed transfers, the amount of space in the "memory map":
- Decreases by the maximum amount of memory that a managed transfer will use when that transfer starts.
- Goes back up by the same amount when that managed transfer completes.
The maximum amount of memory that the managed transfer will use is calculated by an agent when it receives a managed transfer request, and is based on the following agent properties:
- maxInputOutputMessageLength (for file-to-message and message-to-file transfers)
The formula is quite complicated, and takes into account the fact that up to three checkpoints will be stored in a state message for the managed transfer.
If there is enough available space in the "memory map" for the managed transfer request to be processed, then that space is allocated to this transfer in the "memory map". When the managed transfer has completed, the space is released.
However, if there is not enough space in the "memory map", then the agent will put the managed transfer into recovery and the following message should be written to the agent's event log:
BFGTR0067I: Transfer ID: <managed transfer identifier> entering recovery, after a short pause, due to recoverable error: BFGUL0002W: There is insufficient memory available for the transfer with transfer id <managed transfer identifier> to run.
This mechanism ensures that a managed transfer will only be started when there is enough available memory for it to run to completion.
The fact that the space allocated in the "memory map" is based on the maximum amount of memory that the managed transfer will use to run to completion is important, particularly if an agent is only processing relatively small transfer items (such as files that are only a few kilobytes in size). Although the items being transferred might be small, the maximum amount of memory needed to process the managed transfer and the items it contains will be more memory that the item's size, due to the internal workings of the agent and the checkpoints that it takes.
Probably the best way to understand how this works is to look at an example.
Let's suppose that an agent has been configured with the default Java heap size of 512MB, and the agent property enableMemoryAllocationChecking set to true. When the agent starts up, it will create a "memory map" that has a size of:
(The maximum heap size of the Java Runtime Environment that the agent is running in) - 50MB = 512MB - 50MB = 462MB
Figure 1: When the agent starts up, the "memory map" is empty
Now, the agent receives a managed transfer request that requires 100MB in order to run to completion. The agent checks the "memory map" and finds that there is enough space for this managed transfer. At this point, it allocates 100MB of space in the "memory map" for this managed transfer and then kicks it off. The "memory map" now contains 362MB of space.
Figure 2: Space for Managed Transfer 1 is allocated in the "Memory Map"
Next, the agent receives another managed transfer request. This one requires 175MB of memory in order to run to completion. There is enough space in the "memory map" for this managed transfer, so the agent allocates space for it and then sets it running. There is now 187MB of available space in the "memory map".
Figure 3: The agent receives a request for Managed Transfer 2, and allocates space for it in the "memory map".
A third managed transfer request is now submitted to the agent, which needs 225MB of memory to run from start to finish. However, the "memory map" only contains 187MB of space. As a result, the managed transfer goes into recovery.
Figure 4: Managed Transfer 3 goes into recovery, as there is not enough space for it in the "memory map".
When Managed Transfer 2 completes, the memory that was allocated to it in the "memory map" is released, which means that the "memory map" now contains 362MB of space. Managed Transfer 3 requires 225MB of space, so the agent now determines that there is enough memory for this managed transfer to run to completion. 225MB of space in the "memory map" is allocated to the managed transfer, and the agent starts processing it.
Figure 5: Managed Transfer 2 finishes, freeing up space in the "memory map" for Managed Transfer 3 to start.
How does the "memory map" relate to physical memory
Now, the important thing to note about the memory allocation checking functionality is that when entries are added to the "memory map", the corresponding amount of memory in the Java heap is not allocated straight away. Memory is allocated in the Java heap as the managed transfer progresses. This means that it is possible for an agent to put a managed transfer into recovery due to insuffient memory, even though the Java heap contains lots of available space
So, in our example above, we saw that Managed Transfer 1 required a maximum of 100MB in order to run to completion. Now, when the managed transfer first starts up, it will only be using a small amount of physical memory in the Java heap. However, as the managed transfer progresses, the amount of physical memory that it needs will grow up to a maximum of 100MB.
Now, if you were to look at the Java heap at the time when Managed Transfer 3 was submitted and then went into recovery, you might see that there was actually more than 225MB of available memory in the Java heap and so could quite justifiably ask yourself the question "Why has the managed transfer gone into recovery? There is loads of memory available". At the point in time that the managed transfer was submitted, Managed Transfers 1 and 2 hadn't used up that much memory. However, their total memory requirements (100MB and 175MB respectively) mean that if Managed Transfer 3 was allowed to start, then the agent could potentially encounter a java.lang.OutOfMemoryError if all three managed transfers require their maximum amount of memory (or close to it) at the same time.
One final thing to be aware of is APAR IT14915.
When an agent was acting as both the source and destination agent for the same managed transfer, then over time the amount of space in the "memory map" would decrease until it got so small that no new managed transfers would start. Instead, they would all go into recovery. This was due to some errors in the internal calculations used by the enableMemoryAllocationChecking functionaliy, which have now been fixed by the APAR.
Hopefully this blog post has given you a useful insight into how the enableMemoryAllocationChecking functionality works, and the benefits that it can provide. Even though it might not work how you think it does, it can be very useful!