Статьи

OutOfMemoryError система предупреждения с помощью Spring

Каждый из нас, кто разрабатывал Java-приложения, более вовлеченные в пример HelloWorld, знает о OOME. Это происходит, когда заполненное поколение (старое пространство) заполнено и в HEAP больше нет доступной памяти.

Хайнц Кабуц, всемирно известный Чемпион Java и низкоуровневый, эффективный преподаватель Java, объясняет этот подход в своем информационном бюллетене 92 . Эта статья основана на его новостной рассылке и демонстрирует, как очень просто внедрить систему предупреждений OutOfMemoryError в ваших приложениях с помощью Spring.

Прелесть этого решения в том, что оно использует функции, изначально доступные в JDK, с использованием возможностей сервера JMX MBean.

Класс предупреждения OutOfMemoryError

Основная часть системы предупреждений OOME — это следующий класс:

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

 

Ответственность этого класса проста: каждый раз, когда инфраструктура JMX сообщает, что порог памяти превышен, этот класс уведомляет всех своих слушателей, передавая в качестве аргументов максимальную доступную память и используемую до сих пор память.

Слушатель

В этом примере слушатель просто распечатывает информацию о памяти, но в реальной производственной системе он может, например, отправлять уведомление в инструмент мониторинга для уведомлений сотрудников инфраструктуры или отправлять аварийные сигналы в BlackBerries и т. Д.

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

}

 

Конфигурация Spring

<?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>

 

Весной я просто определяю bean-компонент для системы предупреждения Memory. Я экспортировал объявление в XML (например, не использовал аннотацию @Component здесь), потому что я хотел передать пороговый процент как свойство (которое можно было получить извне). В качестве альтернативы, если вы хотите использовать аннотации, вы можете просто объявить bean-компонент типа Double, заполнить его значением свойства и вставить его в bean-компонент OOME.

Остальная часть конфигурации Spring устанавливает инфраструктуру Task.

Наполнитель HEAP памяти

Я создал простую задачу, которая постепенно заполняет HEAP, добавляя случайные двойники в Collection <Double>. Это решение использует новую функцию задачи Spring 3:

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

    }

}

 

Установка Maven

Я использовал Maven, чтобы построить этот проект и создать исполняемый файл. Это 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>

 

При запуске mvn clean install Maven создаст файл memory-warning-system-0.0.1-SNAPSHOT-jar-with-dependencies.jar в папке target / исполняемый файл. Чтобы выполнить jar, откройте командную строку и укажите целевой / исполняемый файл, а затем выполните:

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

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

Результаты

Вот что я вижу в своей консоли:

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...

 

И тогда приложение вылетает с OOME.

 

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