package org.simantics; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryType; import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * 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. * * ( adapted from http://www.javaspecialists.eu/archive/Issue092.html ) * * @author Antti Villberg */ public class MemoryWarningSystem { public interface MemoryWarningListener { void memoryLow(long usedMemory); } private final Collection listeners = new CopyOnWriteArrayList(); private static final MemoryPoolMXBean tenuredGenPool = findTenuredGenPool(); private double percentage = 1.0; private ScheduledFuture future; private boolean disposed = false; public MemoryWarningSystem(int amount, TimeUnit unit) { // MemoryMXBean mbean = ManagementFactory.getMemoryMXBean(); // NotificationEmitter emitter = (NotificationEmitter) mbean; // emitter.addNotificationListener(new NotificationListener() { // @Override // public void handleNotification(Notification n, Object hb) { // if (n.getType().equals( // MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) { // if(!check()) { // long used = getUsed(); // for (MemoryWarningListener listener : listeners) { // listener.memoryLow(used); // } // } // setPercentageUsageThreshold(percentage); // } // } // }, null, null); future = Simantics.scheduleAtFixedRate(new Runnable() { @Override public void run() { if(!disposed && !check()) { long bytes = get(); for (MemoryWarningListener listener : listeners) { listener.memoryLow(bytes); } } } }, 0, amount, unit); } public long get() { return tenuredGenPool.getUsage().getUsed(); } public boolean check() { long max = Runtime.getRuntime().maxMemory(); long usedMemory = get(); long threshold = (long)(max * percentage); return usedMemory < threshold; } public long getUsed() { return tenuredGenPool.getUsage().getUsed(); } public boolean addListener(MemoryWarningListener listener) { return listeners.add(listener); } public boolean removeListener(MemoryWarningListener listener) { return listeners.remove(listener); } public void setPercentageUsageThreshold(double percentage) { if (percentage <= 0.0 || percentage > 1.0) { throw new IllegalArgumentException("Percentage not in range"); } this.percentage = percentage; // long maxMemory = Runtime.getRuntime().maxMemory(); // long warningThreshold = (long) (maxMemory * percentage); // tenuredGenPool.setUsageThreshold(0); // 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 IllegalStateException("Could not find tenured space"); } public void dispose() { if (!disposed) { disposed = true; future.cancel(false); listeners.clear(); } } }