Каждый из нас, кто разрабатывал 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