DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Frameworks
  4. OutOfMemoryError warning system with Spring

OutOfMemoryError warning system with Spring

Marco Tedone user avatar by
Marco Tedone
·
Jul. 20, 11 · Interview
Like (0)
Save
Tweet
Share
10.86K Views

Join the DZone community and get the full member experience.

Join For Free

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

Spring Framework

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Create a CLI Chatbot With the ChatGPT API and Node.js
  • Strategies for Kubernetes Cluster Administrators: Understanding Pod Scheduling
  • 5 Steps for Getting Started in Deep Learning
  • OpenVPN With Radius and Multi-Factor Authentication

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: