// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.tools; import static org.openstreetmap.josm.tools.I18n.tr; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import org.openstreetmap.josm.Main; /** * This class allows all components of JOSM to register reclaimable amounts to memory. * <p> * It can be used to hold imagery caches or other data that can be reconstructed form disk/web if required. * <p> * Reclaimable storage implementations may be added in the future. * * @author Michael Zangl * @since 10588 */ public class MemoryManager { /** * assumed minimum JOSM memory footprint */ private static final long JOSM_CORE_FOOTPRINT = 50L * 1024L * 1024L; private static final MemoryManager INSTANCE = new MemoryManager(); private final ArrayList<MemoryHandle<?>> activeHandles = new ArrayList<>(); protected MemoryManager() { } /** * Allocates a basic, fixed memory size. * <p> * If there is enough free memory, the factory is used to procude one element which is then returned as memory handle. * <p> * You should invoke {@link MemoryHandle#free()} if you do not need that handle any more. * @param <T> The content type of the memory- * @param name A name for the memory area. Only used for debugging. * @param maxBytes The maximum amount of bytes the content may have * @param factory The factory to use to procude the content if there is sufficient memory. * @return A memory handle to the content. * @throws NotEnoughMemoryException If there is not enough memory to allocate. */ public synchronized <T> MemoryHandle<T> allocateMemory(String name, long maxBytes, Supplier<T> factory) throws NotEnoughMemoryException { if (isAvailable(maxBytes)) { T content = factory.get(); if (content == null) { throw new IllegalArgumentException("Factory did not return a content element."); } Main.info(MessageFormat.format("Allocate for {0}: {1} MB of memory. Available: {2} MB.", name, maxBytes / 1024 / 1024, getAvailableMemory() / 1024 / 1024)); MemoryHandle<T> handle = new ManualFreeMemoryHandle<>(name, content, maxBytes); activeHandles.add(handle); return handle; } else { throw new NotEnoughMemoryException(maxBytes); } } /** * Check if that memory is available * @param maxBytes The memory to check for * @return <code>true</code> if that memory is available. */ public synchronized boolean isAvailable(long maxBytes) { if (maxBytes < 0) { throw new IllegalArgumentException(MessageFormat.format("Cannot allocate negative number of bytes: {0}", maxBytes)); } return getAvailableMemory() >= maxBytes; } /** * Gets the maximum amount of memory available for use in this manager. * @return The maximum amount of memory. */ public synchronized long getMaxMemory() { return Runtime.getRuntime().maxMemory() - JOSM_CORE_FOOTPRINT; } /** * Gets the memory that is considered free. * @return The memory that can be used for new allocations. */ public synchronized long getAvailableMemory() { return getMaxMemory() - activeHandles.stream().mapToLong(MemoryHandle::getSize).sum(); } /** * Get the global memory manager instance. * @return The memory manager. */ public static MemoryManager getInstance() { return INSTANCE; } /** * Reset the state of this manager to the default state. * @return true if there were entries that have been reset. */ protected synchronized List<MemoryHandle<?>> resetState() { ArrayList<MemoryHandle<?>> toFree = new ArrayList<>(activeHandles); toFree.forEach(MemoryHandle::free); return toFree; } /** * A memory area managed by the {@link MemoryManager}. * @author Michael Zangl * @param <T> The content type. */ public interface MemoryHandle<T> { /** * Gets the content of this memory area. * <p> * This method should be the prefered access to the memory since it will do error checking when {@link #free()} was called. * @return The memory area content. */ T get(); /** * Get the size that was requested for this memory area. * @return the size */ long getSize(); /** * Manually release this memory area. There should be no memory consumed by this afterwards. */ void free(); } private class ManualFreeMemoryHandle<T> implements MemoryHandle<T> { private final String name; private T content; private final long size; ManualFreeMemoryHandle(String name, T content, long size) { this.name = name; this.content = content; this.size = size; } @Override public T get() { if (content == null) { throw new IllegalStateException(MessageFormat.format("Memory area was accessed after free(): {0}", name)); } return content; } @Override public long getSize() { return size; } @Override public void free() { if (content == null) { throw new IllegalStateException(MessageFormat.format("Memory area was already marked as freed: {0}", name)); } content = null; synchronized (MemoryManager.this) { activeHandles.remove(this); } } @Override public String toString() { return "MemoryHandle [name=" + name + ", size=" + size + ']'; } } /** * This exception is thrown if there is not enough memory for allocating the given object. * @author Michael Zangl */ public static class NotEnoughMemoryException extends Exception { NotEnoughMemoryException(long memoryBytesRequired) { super(tr("To add another layer you need to allocate at least {0,number,#}MB memory to JOSM using -Xmx{0,number,#}M " + "option (see http://forum.openstreetmap.org/viewtopic.php?id=25677).\n" + "Currently you have {1,number,#}MB memory allocated for JOSM", memoryBytesRequired / 1024 / 1024, Runtime.getRuntime().maxMemory() / 1024 / 1024)); } } }