I'd like to detect when free memory is low (say 15% free) and take some action.
I tried using the Runtime memory indicators, but I seem to be getting inconsistent behavior.
Here's my test app:
import java.util.*;
public class Memory
{
static final int BYTES_PER_MB = 1024*1024;
public static void main(String[] args) {
Queue<byte[]> q = new LinkedList<>();
int allocated = 0;
while (true) {
q.add(new byte[BYTES_PER_MB * 100]);
allocated += 100;
System.out.printf("Allocated 100m (total allocated=%d)%n", allocated);
printMem();
}
}
public static void printMem() {
long max = Runtime.getRuntime().maxMemory() / BYTES_PER_MB;
long total = Runtime.getRuntime().totalMemory() / BYTES_PER_MB;
long free = Runtime.getRuntime().freeMemory() / BYTES_PER_MB;
long totalFree = free+(max-total); // allocated free + memory that can be allocated
System.out.printf("MEM: max=%dm, total=%dm, free=%d, (total free=%d)%n", max, total, free, totalFree);
}
}
Here are the results I'm getting:
JVM | Command | Total free before OOM
--------------------------------------------------------------------------------------------------
Oracle Java 1.8.0_191-b12 64-Bit Server | java -Xmx1024m -Xms10m Memory | 309
Oracle Java 1.8.0_191-b12 64-Bit Server | java -Xmx1024m Memory | 205
Oracle Java 11.0.2+9-LTS 64-Bit Server | java -Xmx1024m -Xms10m Memory | 12
Oracle Java 11.0.2+9-LTS 64-Bit Server | java -Xmx1024m Memory" | 12
The results make sense for Java 11, but I don't understand the results for Java 8.
-Xmx? Shouldn't it only affect the initial heap size?..
To better understand the memory breakdown between JVM versions it may be helpful to look at some visualizations. The below charts show the memory breakdown as memory is allocated by a similar program to the one included above in Java 8 and Java 15 (source included below):



Legend Description for Charts:
1) Total Free - Free memory including already allocated free memory and memory which may still be requested from the system up to Max memory. (totFre in test)
2) JVM Allocated (Free) - The memory which is available for usage within the memory already allocated for the JVM (Total Memory above). (freMem in test)
3) JVM Allocated - The memory currently allocated for the JVM. This may vary as objects are created or more space is needed up to Max Memory. (totMem in test)
4) Max Memory - The maximum memory the JVM will allow itself to use. Can be configured via -Xmx flag. (maxMem in test)
..
Why the discrepancy between Java 8 and 15?
When Java 15 is configured to use the same garbage collector used in Java 8 (Parallel GC), the heap grows similarly to how it does in Java 8. One difference is that in Java 15, all memory is consumed before failing with Out of Memory. This suggests that both the GC mechanism and parameters used impact the amount by which the heap is grown when more heap space is needed and the total effective heap capacity which can be used. This is further evidenced by running the same test in Java 8 with Serial GC in which case all memory is properly used. In the sample test, configuring the Parallel GC to use '-XX:+UseAdaptiveGCBoundary' improved max memory utilization before failure.
..
Test Source:
public static void main(String[] args)
{
long totalProgramAllocated = 0;
Queue<byte[]> dataQueue = new LinkedList<>();
while (true)
{
System.out.println("Total Allocated: " + (totalProgramAllocated >> 20));
byte[] nextAllocatedChunk = createArray(25);
dataQueue.add(nextAllocatedChunk);
totalProgramAllocated += nextAllocatedChunk.length;
System.out.println(new MemoryUsage());
}
}
private static final Random random = new Random();
private static byte[] createArray(int megabytes){ byte[] data = new byte[(1 << 20) * megabytes]; random.nextBytes(data); return data; }
public static final class MemoryUsage
{
public final long maxMem = Runtime.getRuntime().maxMemory();
public final long totMem = Runtime.getRuntime().totalMemory();
public final long freMem = Runtime.getRuntime().freeMemory();
public final long totFre = maxMem - (totMem - freMem);
@Override public String toString(){ return String.join(",", Long.toString(maxMem), Long.toString(totMem), Long.toString(freMem), Long.toString(totFre)); }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With