diff --git a/common/source/java/ch/systemsx/cisd/common/jmx/JMXMemoryMonitor.java b/common/source/java/ch/systemsx/cisd/common/jmx/JMXMemoryMonitor.java new file mode 100644 index 0000000000000000000000000000000000000000..6a63be75e5774bb54e62b47d94dc6b786a48b168 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/jmx/JMXMemoryMonitor.java @@ -0,0 +1,140 @@ +/* + * Copyright 2012 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.common.jmx; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; + +/** + * A monitor for heap and non-heap memory usage. + * <p> + * A high-water mark (in percent) can be specified when a notification warning should be sent out. + * + * @author Bernd Rinn + */ +public class JMXMemoryMonitor +{ + private static final double BYTES_PER_GIGABYTE = 1024 * 1024 * 1024; + + private static final Logger machineLog = LogFactory.getLogger(LogCategory.MACHINE, + JMXMemoryMonitor.class); + + private static final Logger notifyLog = LogFactory.getLogger(LogCategory.NOTIFY, + JMXMemoryMonitor.class); + + private final long logIntervallMillis; + + private final int memoryHighwaterMarkPercent; + + private long lastLoggedMillis; + + private boolean heapMemoryExhaustionNotificationSent; + + private boolean nonHeapMemoryExhaustionNotificationSent; + + /** + * Starts the memory monitor. + * + * @param monitoringIntervallMillis Interval (in ms) for monitoring memory consumption. + * @param logIntervallMillis Interval (in ms) for regular logging of memory consumption. + * @param memoryHighWatermarkPercent High-water mark for heap and non-heap memory consumption + * (in percent of the maximal memory). If this mark is exceeded, a warning + * notification will be sent. + */ + public static final void startMonitor(long monitoringIntervallMillis, long logIntervallMillis, + int memoryHighWatermarkPercent) + { + final Timer timer = new Timer(true); + final JMXMemoryMonitor monitor = + new JMXMemoryMonitor(logIntervallMillis, memoryHighWatermarkPercent); + timer.schedule(monitor.getTimerTask(), 0L, monitoringIntervallMillis); + } + + JMXMemoryMonitor(long logIntervallMillis, int memoryHighWatermarkPercent) + { + this.logIntervallMillis = logIntervallMillis; + this.memoryHighwaterMarkPercent = memoryHighWatermarkPercent; + } + + private int percentageUsed(MemoryUsage usage) + { + return (int) Math.ceil(100 * (usage.getUsed() / (float) usage.getMax())); + } + + TimerTask getTimerTask() + { + return new TimerTask() + { + @Override + public void run() + { + final long now = System.currentTimeMillis(); + final MemoryMXBean mbean = ManagementFactory.getMemoryMXBean(); + final int percentageUsedHeap = percentageUsed(mbean.getHeapMemoryUsage()); + if (percentageUsedHeap > memoryHighwaterMarkPercent) + { + if (heapMemoryExhaustionNotificationSent == false) + { + notifyLog.warn(String.format( + "JVM exceeded high-water mark for heap memory: %.1f GB (%d%%)", + mbean.getHeapMemoryUsage().getUsed() / BYTES_PER_GIGABYTE, + percentageUsedHeap)); + heapMemoryExhaustionNotificationSent = true; + } + } else + { + heapMemoryExhaustionNotificationSent = false; + } + final int percentageUsedNonHeap = percentageUsed(mbean.getNonHeapMemoryUsage()); + if (percentageUsedNonHeap > memoryHighwaterMarkPercent) + { + if (nonHeapMemoryExhaustionNotificationSent == false) + { + notifyLog + .warn(String + .format("JVM exceeded high-water mark for non-heap memory: %.1f GB (%d%%)", + mbean.getNonHeapMemoryUsage().getUsed() + / BYTES_PER_GIGABYTE, + percentageUsedNonHeap)); + nonHeapMemoryExhaustionNotificationSent = true; + } + } else + { + nonHeapMemoryExhaustionNotificationSent = false; + } + final long timeSinceLastLoggedMillis = now - lastLoggedMillis; + if (timeSinceLastLoggedMillis > logIntervallMillis) + { + machineLog.info(String.format( + "Heap memory used: %.1f GB, non-heap memory used: %.1f GB", mbean + .getHeapMemoryUsage().getUsed() / BYTES_PER_GIGABYTE, mbean + .getNonHeapMemoryUsage().getUsed() / BYTES_PER_GIGABYTE)); + lastLoggedMillis = now; + } + } + }; + } + +}