All Downloads are FREE. Search and download functionalities are using the official Maven repository.

soot.jimple.infoflow.memory.MemoryWarningSystem Maven / Gradle / Ivy

package soot.jimple.infoflow.memory;

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.lang.management.MemoryUsage;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.management.ListenerNotFoundException;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import soot.jimple.infoflow.util.ThreadUtils;

/**
 * Notification system that triggers a callback when the JVM is about to run out
 * of memory. Inspired by code from
 * http://www.javaspecialists.eu/archive/Issue092.html.
 * 
 * Be careful, because memory allocation is usually fast. There might not be a
 * lot of time left to perform corrective measures if the warning threshold is
 * reached.
 * 
 * @author Steven Arzt, Marc Miltenberger
 *
 */
public class MemoryWarningSystem {

	private static final Logger logger = LoggerFactory.getLogger(MemoryWarningSystem.class);

	/**
	 * Interface that is invoked when a certain memory threshold has been reached.
	 * 
	 * @author Steven Arzt
	 *
	 */
	public interface OnMemoryThresholdReached {

		/**
		 * Method that is called when a certain memory threshold has been reached.
		 * 
		 * @param usedMemory The amount of memory currently in use
		 * @param maxMemory  The maximum amount of allocated memory
		 */
		public void onThresholdReached(long usedMemory, long maxMemory);

	}

	private final static MemoryPoolMXBean tenuredGenPool = findTenuredGenPool();

	private final Set listeners = new HashSet<>();
	private final static NotificationListener memoryListener;
	private boolean isClosed = false;

	private long threshold;

	private static Thread thrLowMemoryWarningThread;

	private static TreeSet warningSystems = new TreeSet<>(new Comparator() {

		@Override
		public int compare(MemoryWarningSystem o1, MemoryWarningSystem o2) {
			return Long.compare(o1.threshold, o2.threshold);
		}
	});

	static {
		memoryListener = new NotificationListener() {

			@Override
			public void handleNotification(Notification notification, Object handback) {
				if (notification.getType().equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) {
					triggerNotification();
				}
			}
		};

		MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
		((NotificationEmitter) memoryBean).addNotificationListener(memoryListener, new NotificationFilter() {

			private static final long serialVersionUID = -3755179266517545663L;

			@Override
			public boolean isNotificationEnabled(Notification notification) {
				return notification.getType().equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED);
			}

		}, null);

	}

	private static long triggerNotification() {
		long maxMemory = tenuredGenPool.getUsage().getMax();
		long usedMemory = tenuredGenPool.getUsage().getUsed();

		Runtime runtime = Runtime.getRuntime();
		long usedMem = runtime.totalMemory() - runtime.freeMemory();

		synchronized (warningSystems) {
			Iterator it = warningSystems.iterator();
			while (it.hasNext()) {
				MemoryWarningSystem ws = it.next();
				if (ws.threshold <= usedMemory) {
					logger.info("Triggering memory warning at " + (usedMem / 1000 / 1000) + " MB ("
							+ (maxMemory / 1000 / 1000) + " MB max, " + (usedMemory / 1000 / 1000)
							+ " in watched memory pool)...");
					for (OnMemoryThresholdReached listener : ws.listeners)
						listener.onThresholdReached(usedMemory, maxMemory);
					it.remove();
				} else {
					// At least one warning systme has a higher threshold
					tenuredGenPool.setUsageThreshold(ws.threshold);
					return ws.threshold;
				}
			}
			return -1;
		}
	}

	@Override
	protected void finalize() throws Throwable {
		close();
		super.finalize();
	}

	/**
	 * Adds a listener that is called when the memory usage threshold defined using
	 * setWarningThreshold() has been reached
	 * 
	 * @param listener The listener to add
	 */
	public void addListener(OnMemoryThresholdReached listener) {
		this.listeners.add(listener);
	}

	/**
	 * Tenured Space Pool can be determined by it being of type HEAP and by it being
	 * possible to set the usage threshold.
	 */
	public static MemoryPoolMXBean findTenuredGenPool() {
		List usablePools = new ArrayList<>();
		for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
			// Can we put a threshold on this pool?
			if (pool.getType() == MemoryType.HEAP && pool.isUsageThresholdSupported()) {
				usablePools.add(pool);

				// We use the tenured generation as an indicator if we can
				if (pool.getName().equals("Tenured Gen"))
					return pool;
			}
		}

		// Apparently, we did not find the tenured generation. Take some other pool
		// instead.
		if (!usablePools.isEmpty())
			return usablePools.get(0);

		// Without any usable pool at all, we're out of luck.
		throw new AssertionError("Could not find tenured space");
	}

	/**
	 * Sets the one single global warning threshold for memory usage. If at least
	 * the given fraction of the overall tenured pool is in use, the registered
	 * hanlders will be invoked.
	 * 
	 * @param percentage
	 */
	public void setWarningThreshold(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);
		synchronized (warningSystems) {
			// If element has changed, it needs to removed first
			warningSystems.remove(this);
			this.threshold = warningThreshold;
			logger.info(MessageFormat.format("Registered a memory warning system for {0} MiB",
					(threshold / 1024D / 1024D)));
			warningSystems.add(this);
			MemoryUsage usage = tenuredGenPool.getUsage();
			long threshold = warningSystems.iterator().next().threshold;
			boolean useOwnImplementation = !tenuredGenPool.isUsageThresholdSupported();
			if (!useOwnImplementation && usage != null && usage.getUsed() > threshold) {
				tenuredGenPool.setUsageThreshold(threshold);
			} else {
				// when the usage is already above the threshold, we use our own implementation,
				// since the jvm implementation does not seem to get called in that case.
				useOwnImplementation = true;
			}
			if (useOwnImplementation) {
				// No JVM support is available, use our own implementation
				if (thrLowMemoryWarningThread == null) {
					thrLowMemoryWarningThread = ThreadUtils.createGenericThread(new Runnable() {

						@Override
						public void run() {
							while (true) {
								MemoryWarningSystem l;
								synchronized (warningSystems) {
									if (warningSystems.isEmpty()) {
										thrLowMemoryWarningThread = null;
										return;
									}
									l = warningSystems.iterator().next();
								}
								long nextThreshold = l.threshold;
								MemoryUsage usage = tenuredGenPool.getUsage();
								if (usage == null) {
									logger.warn(MessageFormat.format("Memory usage of {0} could not be estimated",
											tenuredGenPool.getName()));
									return;
								} else {
									long used = usage.getUsed();
									if (used >= l.threshold) {
										nextThreshold = triggerNotification();
										if (nextThreshold == -1) {
											synchronized (warningSystems) {
												if (warningSystems.isEmpty()) {
													thrLowMemoryWarningThread = null;
													return;
												}
											}
										}
									}
								}
								long used = usage.getUsed();
								// Depending on how far we are from the next threshold, we can rest longer
								// or shorter
								long missing = nextThreshold - used;
								if (missing <= 0)
									continue;
								try {
									long wait = (long) ((missing / (double) tenuredGenPool.getUsage().getMax()) * 500);
									Thread.sleep(wait);
								} catch (InterruptedException e) {
								}
							}
						}

					}, "Low memory monitor", true);
					thrLowMemoryWarningThread.setPriority(Thread.MIN_PRIORITY);
					thrLowMemoryWarningThread.start();
				}
			}
		}
	}

	/**
	 * Closes this warning system instance. It will no longer notify any listeners
	 * of memory shortages.
	 */
	public void close() {
		// Avoid duplicate attempts to unregister the listener
		if (isClosed)
			return;
		logger.info("Shutting down the memory warning system...");
		synchronized (warningSystems) {
			warningSystems.remove(this);
		}

		MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
		try {
			((NotificationEmitter) memoryBean).removeNotificationListener(memoryListener);
		} catch (ListenerNotFoundException e) {
			// Doesn't matter, we wanted to get rid of it anyway
		}
		isClosed = true;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy