package at.ac.tuwien.dsg.scaledom.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.Closeable;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
import javax.management.ListenerNotFoundException;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A low memory detector, based upon https://techblug.wordpress.com/2011/07/21/detecting-low-memory-in-java-part-2/ and
* http://grepcode.com/file/repo1.maven.org/maven2/org.apache.pig/pig/0.10.1/org/apache/pig/impl/util/
* SpillableMemoryManager.java?av=f
*
* @author Dominik Rauch
*/
public class LowMemoryDetector implements NotificationListener, Closeable {
/** Logger. */
private final static Logger log = LoggerFactory.getLogger(LowMemoryDetector.class);
/** MX bean for the tenured heap. */
private final MemoryPoolMXBean tenuredHeap;
/** Heap size in bytes. */
private final long tenuredHeapSize;
/** Heap threshold in bytes. */
private final long memoryThreshold;
/** Flag whether the system is low on memory. */
private boolean isLowMemory;
/**
* Default constructor.
*
* @param memoryThresholdFactor amount of memory to be considered 'enough memory'.
* @throws IllegalArgumentException If memoryThresholdFactor is not strictly between 0.0 and 1.0.
*/
public LowMemoryDetector(final double memoryThresholdFactor) {
checkArgument(memoryThresholdFactor > 0.0 && memoryThresholdFactor < 1.0,
"Expected memoryThresholdFactor to be between 0.0 and 1.0, %s is not.", memoryThresholdFactor);
isLowMemory = false;
// Find the tenured heap
tenuredHeap = findTenuredHeap();
checkNotNull(tenuredHeap, "Expected tenuredHeap to be not null.");
tenuredHeapSize = tenuredHeap.getUsage().getMax();
log.debug("Determined the tenured heap as '{}' (size: {} B).", tenuredHeap.getName(), tenuredHeapSize);
// Monitor tenured heap
memoryThreshold = (long) (tenuredHeapSize * memoryThresholdFactor);
tenuredHeap.setCollectionUsageThreshold(memoryThreshold);
log.debug("Low memory threshold is {} B.", memoryThreshold);
// Add notification listener
final NotificationEmitter notificationEmitter = (NotificationEmitter) ManagementFactory.getMemoryMXBean();
notificationEmitter.addNotificationListener(this, null, null);
}
/**
* Returns whether the system has run into low-memory problems at least once.
*
* @return true if a low-memory situation has occurred at least once, false otherwise.
*/
public boolean isLowMemory() {
return isLowMemory;
}
@Override
public void close() throws IOException {
try {
// Remove notification listener
final NotificationEmitter notificationEmitter = (NotificationEmitter) ManagementFactory.getMemoryMXBean();
notificationEmitter.removeNotificationListener(this);
} catch (final ListenerNotFoundException ex) {
log.warn("Somebody else already removed the notification listener from the MemoryMXBean.", ex);
}
}
@Override
public void handleNotification(final Notification notification, final Object handback) {
if (isLowMemory) {
return;
}
final long currentUsage = tenuredHeap.getCollectionUsage().getUsed();
if (currentUsage > memoryThreshold) {
isLowMemory = true;
log.debug("System is running low on memory! {} of {} B are already in use.", currentUsage, tenuredHeapSize);
}
}
private static MemoryPoolMXBean findTenuredHeap() {
// Find tenured heap by assuming it is the biggest heap in the system
MemoryPoolMXBean biggestMemoryPool = null;
long biggestMemoryPoolSize = Long.MIN_VALUE;
for (final MemoryPoolMXBean memoryPool : ManagementFactory.getMemoryPoolMXBeans()) {
if (memoryPool.getType() != MemoryType.HEAP) {
continue;
}
// Check if collection usage threshold is supported
final String memoryPoolName = memoryPool.getName();
final long memoryPoolSize = memoryPool.getUsage().getMax();
if (!memoryPool.isCollectionUsageThresholdSupported()) {
log.debug("Found heap '{}' (size: {}), but no support for collection usage threshold.", memoryPoolName,
Utils.toHumanReadableByteCount(memoryPoolSize));
continue;
}
log.debug("Found heap '{}' (size: {}).", memoryPoolName, Utils.toHumanReadableByteCount(memoryPoolSize));
if (memoryPoolSize > biggestMemoryPoolSize) {
biggestMemoryPool = memoryPool;
biggestMemoryPoolSize = memoryPoolSize;
}
}
return biggestMemoryPool;
}
}