-1

How I can programmatically, from within a JVM, determine the state of the heap? I know I can log GC activity via -verbose:gc and -XX:+PrintGCDetails JVM options but I want my application to be able to recognize if the JVM is about to run out of memory, if at all possible, so that I can log the fact to trace logs, inform monitoring systems, or otherwise act on the information. I guess I can log the events to a file and then read the file from the same JVM but that seems hacky.

Why would I want this? I have seen JVMs which get close to running out of memory so that they cease doing any real work but don't ever throw an OutOfMemoryError. I understand that detecting OOM ahead of time may not be possible in all situations if the JVM is out of memory and the logging methods and messages themselves are not given the needed resources.

I have tried to set memory thresholds via the MemoryPoolMXBean calls on the "Old Gen" (was called "Tenured") pool so my application can be notified when the thresholds are crossed. This doesn't seem to provide reliable measure of the memory usage because the JVMs can use almost the full pool and look full but after garbage collection only be using 1%. I'm looking to get telemetry from the garbage-collection events which seems like more useful information.

Here's some reading on the subject:

Gray
  • 115,027
  • 24
  • 293
  • 354
  • Related to https://stackoverflow.com/questions/11508310/detecting-out-of-memory-errors – Gray Apr 19 '22 at 19:22

1 Answers1

0

How I can programmatically, from within a JVM, determine the state of the heap?

I've figured out the following code can get information about the various GC collectors via JMX notifications and with the addition of some logic, my application may be able to infer that it is running out of memory.

The JMX information is most likely going to be JVM dependent (OpenJDK, Oracle/Sun, ...) as well as version (7/8/9/...) and maybe even hardware platform dependent. Any solution is most likely is not going to be a 100% solution – I'll be happy with 60%.

Right now it is a work in progress but I hope that this code helps others.

GcJmxListener listener = new GcJmxListener();
// get the JMX beans responsible for the garbage collectors
List<GarbageCollectorMXBean> beans =
    ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean bean : beans) {
    if (bean instanceof NotificationEmitter) {
        // add a notification listener on each one of them
        ((NotificationEmitter) bean).addNotificationListener(
            listener, null /* filter */, null /* handback */);
    }
}
...

/** Listener that gets called with JMX notifications of GC events */
private static class GcJmxListener implements NotificationListener {
    @Override
    public void handleNotification(Notification notif, Object handback) {
        Object userData = notif.getUserData();
        if (userData instanceof CompositeData) {
            walkComposite(null /* label */, (CompositeData) userData);
        }
    }

    /** called recursively to interrogate a CompositeData tree */
    private void walkComposite(String label, CompositeData comp) {
        for (String key : comp.getCompositeType().keySet()) {
            String keyLabel = key;
            if (label != null) {
                keyLabel = label + " -> " + key;
            }
            Object value = comp.get(key);
            if (value instanceof CompositeData) {
                // going recursive
                walkComposite(keyLabel, (CompositeData) value);
            } else if (value instanceof TabularData) {
                walkTabular(keyLabel, (TabularData) value);
            } else {
                /* NOTE: here is where we might add logic to detect problems */
                System.out.println(keyLabel + " = " + value);
            }
        }
    }

    /** called recursively to handle a TabularData tree */
    private void walkTabular(String label, TabularData tab) {
        for (Object key : tab.keySet()) {
            if (key instanceof List) {
                CompositeData value = tab.get(((List<?>) key).toArray());
                walkComposite(label + " -> " + key, value);
            }
        }
    }
}

The code is not specific to the Garbage Collector beans and may be useful to interrogate other JMX notifications and composite data trees.

Logic Thoughts

In terms of the logic itself, I think that some of the following logic may help to flag a imminent OutOfMemoryError:

  • Increased frequency of mark-sweep GC events compared to scavenge. A healthy JVM typically has many more scavenge events but as it runs out of memory, it has to do more mark-sweeps to reclaim memory. The GC algorithms may be named differently depending on the JVM/version.
  • The "Old Gen" (or "Tenured Gen") memory pool still very close to being "full" after a mark-sweep event. I'm not 100% sure if I need to determine the size of the pool or if the max information in the notification is enough here. The pool may be named differently depending on the JVM/version.
  • Maybe looking the reclaimed space in all of the memory pools is the best way to to determine if the end is near.
  • Did the "Old Gen" memory pool increase in size after a mark-sweep event. This isn't always bad given that some "Survivor" objects will move over to "Old Gen" but there may be some telemetry that will help here.

Obviously a work in progress.

Gray
  • 115,027
  • 24
  • 293
  • 354