I'm an Agile and Lean Strategist specialised in coaching and managing the transformation of IT departments, from startups to enterprise-scale organisations, to highly efficient, productive and energised environments. I also have experience as senior development manager and architecture governance in large enterprises, especially in the finance sector. Marco is a DZone MVB and is not an employee of DZone and has posted 26 posts at DZone. You can read more from them at their website. View Full User Profile

OutOfMemoryError warning system with Spring

07.19.2011
| 5670 views |
  • submit to reddit

Everyone of us who has developed Java applications more involving that the HelloWorld example knows of the OOME. This happens when the Tenured Generation (old space) is filled up and there is no more available memory on the HEAP.

Heinz Kabutz, the world famous Java Champion and low-level, performant Java educator explains this approach in his newsletter 92. This article builds on his newsletter and demonstrates how to introduce, very simply, an OutOfMemoryError warning system in your applications with the help of Spring.

The beauty of this solution is that it uses features natively available in the JDK by using JMX  MBean server capabilities.

The OutOfMemoryError Warning class

The bulk of the OOME warning system is the following class:

package uk.co.jemos.experiments.oome;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryNotificationInfo;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
import java.util.ArrayList;
import java.util.Collection;

import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;

/**
 * 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>();

    private static final MemoryPoolMXBean tenuredGenPool = findTenuredGenPool();

    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);
    }

    public 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");
    }
}

 

The responsibility of this class is simple: everytime the JMX infrastructure communicates that the memory threshold has been exceeded this class notifies all its listeners passing as arguments the maximum memory available and the memory used so far.

The Listener

In this example, the listener simply prints out the memory information, but in a real production system it could, for instance, send a notifacation to a monitoring tool for the infrastructure folks to be notified, or send alarms to BlackBerries, etc.

public void memoryUsageLow(long usedMemory, long maxMemory) {
        System.out.println("Memory usage low!!!");
        double percentageUsed = (double) usedMemory / maxMemory;
        System.out.println("percentageUsed = " + percentageUsed);

}

 

The Spring configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="uk.co.jemos.experiments.oome" />

    <bean id="memoryWarningSystem">
        <property name="percentageUsageThreshold" value="0.6" />
    </bean>   

    <task:annotation-driven scheduler="myScheduler" />   

    <task:scheduler id="myScheduler" pool-size="10" />   

</beans>

 

In Spring I simply defines a bean for the Memory warning system. I externalised the declaration in XML (e.g. didn't use the @Component annotation here) because I wanted to pass the threshold percentage as property (which could be retrieved externally). Alternatively, if you want to use the annotations, you could simply declare a bean of type Double, fill it with the property value and inject it in the OOME warning bean.

The rest of the Spring configuration sets up the Task infrastructure.

The Memory HEAP filler

I created a simple task which progressively fills up the HEAP by adding random doubles to a Collection<Double>. This solution uses the new Spring 3 task feature:

package uk.co.jemos.experiments.oome;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class MemoryFillerTaskExecutor {

    private static final Collection<Double> GARBAGE = new ArrayList<Double>();

    @Scheduled(fixedDelay = 1000)
    public void addGarbage() {
        System.out.println("Adding data...");
        for (int i = 0; i < 100000; i++) {
            GARBAGE.add(Math.random());
        }

    }

}

 

The Maven Setup

I used Maven to build this project and to create an executable. This is the pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>uk.co.jemos.experiments</groupId>
  <artifactId>memory-warning-system</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>memory-warning-system</name>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.3.1</version>
        <configuration>
          <archive>
            <manifest>
              <addClasspath>true</addClasspath>
              <mainClass>uk.co.jemos.experiments.oome.MemTest</mainClass>
            </manifest>
          </archive>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>2.2.1</version>
        <executions>
          <execution>
            <id>executable</id>
            <phase>install</phase>
            <goals>
              <goal>assembly</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <outputDirectory>${project.build.directory}/executable</outputDirectory>
          <descriptors>
            <descriptor>src/main/assembly/executable.xml</descriptor>
          </descriptors>         
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.0.5.RELEASE</version>     
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.0.5.RELEASE</version>     
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>3.0.5.RELEASE</version>     
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.8.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.16</version>       
        <scope>test</scope>
    </dependency>
  </dependencies>
</project>

 

When running mvn clean install Maven will create a memory-warning-system-0.0.1-SNAPSHOT-jar-with-dependencies.jar file under target/executable. To execute the jar, open a command prompt and point to target/executable then execute:


$ jar -xvf memory-warning-system-0.0.1-SNAPSHOT-jar-with-dependencies.jar

$ java -Xmx64m -jar memory-warning-system-0.0.1-SNAPSHOT.jar

The results

This is what I see in my console:

C:\runtime\eclipse-ws\flex-java\memory-warning-system\target\executable>java -Xmx64m -jar memory-warning-system-0.0.1-SNAPSHOT.jar
17-Jul-2011 15:06:16 org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1764be1: startup date [Sun Jul 17 15:06:16 BST 2011]; root of context hierarchy
17-Jul-2011 15:06:16 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [appCtx.xml]
17-Jul-2011 15:06:16 org.springframework.scheduling.concurrent.ExecutorConfigurationSupport initialize
INFO: Initializing ExecutorService
17-Jul-2011 15:06:16 org.springframework.context.support.AbstractApplicationContext$BeanPostProcessorChecker postProcessAfterInitialization
INFO: Bean 'myExecutor' of type [class org.springframework.scheduling.config.TaskExecutorFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for exam
ple: not eligible for auto-proxying)
17-Jul-2011 15:06:16 org.springframework.context.support.AbstractApplicationContext$BeanPostProcessorChecker postProcessAfterInitialization
INFO: Bean 'myExecutor' of type [class org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor] is not eligible for getting processed by all BeanPostProcessors (for e
xample: not eligible for auto-proxying)
17-Jul-2011 15:06:16 org.springframework.scheduling.concurrent.ExecutorConfigurationSupport initialize
INFO: Initializing ExecutorService  'myScheduler'
17-Jul-2011 15:06:16 org.springframework.context.support.AbstractApplicationContext$BeanPostProcessorChecker postProcessAfterInitialization
INFO: Bean 'myScheduler' of type [class org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler] is not eligible for getting processed by all BeanPostProcessors (for
 example: not eligible for auto-proxying)
17-Jul-2011 15:06:16 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1884174: defining beans [memoryFillerTaskExecutor,memoryWarningListe
nerImpl,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springf
ramework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,memoryWarningSystem,org.springframewor
k.scheduling.annotation.internalAsyncAnnotationProcessor,org.springframework.scheduling.annotation.internalScheduledAnnotationProcessor,myExecutor,myScheduler]; root of factor
y hierarchy
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Memory usage low!!!
percentageUsed = 0.6409345630863504
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...
Adding data...

 

And then the application crashes with OOME.

 

From http://tedone.typepad.com/blog/2011/07/outofmemoryerror-warning-system-with-spring.html

Published at DZone with permission of Marco Tedone, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Marco Tedone replied on Wed, 2011/07/20 - 5:07pm

A new version of this post with an additional Thread Dumper declared as a Spring bean is available from my blog at http://tedone.typepad.com/blog/2011/07/outofmemoryerror-warning-system-with-spring.html

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.