Running on Java 24-ea+21-2447 (Preview)
Home of The JavaSpecialists' Newsletter

092OutOfMemoryError Warning System

Author: Dr. Heinz M. KabutzDate: 2004-07-20Java Version: 5Category: Performance
 

Abstract: Since Java 5, we have a JVM system thread constantly monitoring our memory usage. If we exceed our specified threshold, it will send us a notification. We can use this to get an early warning of possible trouble.

 

Welcome to the 92nd edition of The Java(tm) Specialists' Newsletter. Down here at the end of Africa, tech-toys are rather expensive, so whilst I was in Germany, I purchased a 802.11g wireless router. It is linked to my ADSL line (which costs about US$ 160 per month), allowing me to surf the internet at 512kb/s whilst sitting next to my pool. Naturally, I was rather proud with my purchase. On the last day of my visit to Germany, I popped in at my aunt & uncle to chase some golf balls across the meadows (I lost about 12 balls in 18 holes!) Imagine my surprise when my uncle told me matter-of-factly, "Oh, by the way, we have a wireless network at home linked to DSL". Ahem, but that is not all. My grandmother (at the age of 91 years) now also has a wireless network at home. My Oma uses the wireless network so that she can sit in her garden and write emails on her notebook to her grandchildren. How's that?!

On another topic, there is a cool utility from Sun called jvmstat that will show you the various generational memory pools and how much time is being spent by the garbage collectors. I linked jvmstat's VisualGC to my IntelliJ IDEA and was surprised that in 2.5 days, it had only used about 3 minutes for collecting garbage! It shows you the Eden space, the Survivor spaces, the Old Generation and the Permanent Generation.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

OutOfMemoryError Warning System

In Issue 061 of my newsletter, I asked readers whether their applications had ever caused an OutOfMemoryError. I then asked them to email me if they would like to know how to receive a warning, shortly before it was about to happen. Wow, the response! The requests kept on pouring in, and so far, I have had over 200 enquiries. At the time, my ideas for a warning system were sketchy at best, and would have been hopelessly inaccurate, in comparison to what JDK 5 offers us.

JDK 5 has added some wonderful new management beans that make writing an OutOfMemoryError Warning System possible. The most difficult part was probably finding resources on the topic. Google turned up two resources: The JDK documentation and a website written in Japanese ;-)

An OutOfMemoryError (OOME) is bad. It can happen at any time, with any thread. There is little that you can do about it, except to exit the program, change the -Xmx value, and restart the JVM. If you then make the -Xmx value too large, you slow down your application. The secret is to make the maximum heap value the right size, neither too small, nor too big. OOME can happen with any thread, and when it does, that thread typically dies. Often, there is not enough memory to build up a stack trace for the OOME, so you cannot even determine where it occurred, or why. You can use the exception catching mechanism of Issue 089, but that is then an after-the-fact measure, rather than preventative.

In January this year, I was migrating a program from MySQL to MS SQL Server. The author of the original program had used some JDBC commands that caused memory leaks under the SQL Server JDBC driver. This meant that periodically, one of the application's threads would simply vanish, leaving parts of the system paralyzed. Eliminating the OOME was a major task, and only happened when I rewrote all the database access code!

Back to the issue at hand - how can we know when OOME's are about to occur? The answer lies in the java.lang.management package of JDK 5. The ManagementFactory class returns all sorts of useful JMX beans that we can use to manage the JVM. One of these beans is the MemoryMXBean. Sun's implementation of the MemoryMXBean interface also implements the interface javax.management.NotificationEmitter. The recommended way of listening to notifications by the memory bean is by downcasting the MemoryMXBean to the NotificationEmitter interface. I can hardly believe it myself, you can verify this by looking at the documentation of the MemoryMXBean.

Once you have downcast the MemoryMXBean to a NotificationEmitter, you can add a NotificationListener to the MemoryMXBean. You should verify that the notification is of type MEMORY_THRESHOLD_EXCEEDED. In my MemoryWarningSystem you add listeners that implement the MemoryWarningSystem.Listener interface, with one method memoryUsageLow(long usedMemory, long maxMemory) that will be called when the threshold is reached. In my experiments, the memory bean notifies us quite soon after the usage threshold has been exceeded, but I could not determine the granularity. Something to note is that the listener is being called by a special thread, called the Low Memory Detector thread, that is now part of the standard JVM.

What is the threshold? And which of the many pools should we monitor? The only sensible pool to monitor is the Tenured Generation (Old Space). When you set the size of the memory with -Xmx256m, you are setting the maximum memory to be used in the Tenured Generation. I could not find a neat way of finding the tenured generation, except by looking through all the pools in my findTenuredGenPool() method, and returning the first one that was of type HEAP and where I was permitted to specify a usage threshold. I do not know whether a better approach would not have been to search for the name "Tenured Gen"?

In my setPercentageUsageThreshold(double percentage) method, I specify when I would like to be notified. Note that this is a global setting since you can only have one usage threshold per Java Virtual Machine. The percentage is used to calculate the usage threshold, based on the maximum memory size of the Tenured Generation pool (not the Runtime.getRuntime().maxMemory() value!).

import javax.management.*;
import java.lang.management.*;
import java.util.*;

/**
 * This memory warning system will call the listener when we
 * exceed the percentage of available memory specified.  There
 * should only be one instance of this object created, since the
 * usage threshold can only be set to one number.
 */
public class MemoryWarningSystem {
  private final Collection<Listener> listeners =
      new ArrayList<Listener>();

  public interface Listener {
    public void memoryUsageLow(long usedMemory, long maxMemory);
  }

  public MemoryWarningSystem() {
    MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
    NotificationEmitter emitter = (NotificationEmitter) mbean;
    emitter.addNotificationListener(new NotificationListener() {
      public void handleNotification(Notification n, Object hb) {
        if (n.getType().equals(
            MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) {
          long maxMemory = tenuredGenPool.getUsage().getMax();
          long usedMemory = tenuredGenPool.getUsage().getUsed();
          for (Listener listener : listeners) {
            listener.memoryUsageLow(usedMemory, maxMemory);
          }
        }
      }
    }, null, null);
  }

  public boolean addListener(Listener listener) {
    return listeners.add(listener);
  }

  public boolean removeListener(Listener listener) {
    return listeners.remove(listener);
  }

  private static final MemoryPoolMXBean tenuredGenPool =
      findTenuredGenPool();

  public static void setPercentageUsageThreshold(double percentage) {
    if (percentage <= 0.0 || percentage > 1.0) {
      throw new IllegalArgumentException("Percentage not in range");
    }
    long maxMemory = tenuredGenPool.getUsage().getMax();
    long warningThreshold = (long) (maxMemory * percentage);
    tenuredGenPool.setUsageThreshold(warningThreshold);
  }

  /**
   * Tenured Space Pool can be determined by it being of type
   * HEAP and by it being possible to set the usage threshold.
   */
  private static MemoryPoolMXBean findTenuredGenPool() {
    for (MemoryPoolMXBean pool :
        ManagementFactory.getMemoryPoolMXBeans()) {
      // I don't know whether this approach is better, or whether
      // we should rather check for the pool name "Tenured Gen"?
      if (pool.getType() == MemoryType.HEAP &&
          pool.isUsageThresholdSupported()) {
        return pool;
      }
    }
    throw new AssertionError("Could not find tenured space");
  }
}

I have tested this with a small program called MemTest. It sets the threshold to 60%, then prints out a message when that is reached, and changes the threshold to 80%. The main thread of the program puts random Double objects into a LinkedList and just keeps on going until it runs out of memory.

import java.util.*;

public class MemTest {
  public static void main(String[] args) {
    MemoryWarningSystem.setPercentageUsageThreshold(0.6);

    MemoryWarningSystem mws = new MemoryWarningSystem();
    mws.addListener(new MemoryWarningSystem.Listener() {
      public void memoryUsageLow(long usedMemory, long maxMemory) {
        System.out.println("Memory usage low!!!");
        double percentageUsed = ((double) usedMemory) / maxMemory;
        System.out.println("percentageUsed = " + percentageUsed);
        MemoryWarningSystem.setPercentageUsageThreshold(0.8);
      }
    });

    Collection<Double> numbers = new LinkedList<Double>();
    while (true) {
      numbers.add(Math.random());
    }
  }
}

The output on my machine is, for example:

Memory usage low!!!
percentageUsed = 0.6281726667795322
Memory usage low!!!
percentageUsed = 0.8504659318016649
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

Since constructing new objects is a rather fast operation in Java, it may happen that the JVM runs out of memory before getting a chance to notify us.

Inside your listener, you can then do some preventative measures. For example, you could print out what each of the threads in your system was doing, using Thread.getAllStackTraces(). Or, you could keep a nice big chunk of memory available as reserve, and then release that when you are running low on memory, and send a warning message to the system administrator. The ability to at least get all the stack traces will make it much easier to find & destroy memory leaks in Java.

Kind regards

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...