001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003import static org.openstreetmap.josm.tools.I18n.tr; 004 005import java.text.MessageFormat; 006import java.util.ArrayList; 007import java.util.List; 008import java.util.function.Supplier; 009 010import org.openstreetmap.josm.Main; 011 012/** 013 * This class allows all components of JOSM to register reclaimable amounts to memory. 014 * <p> 015 * It can be used to hold imagery caches or other data that can be reconstructed form disk/web if required. 016 * <p> 017 * Reclaimable storage implementations may be added in the future. 018 * 019 * @author Michael Zangl 020 * @since 10588 021 */ 022public class MemoryManager { 023 /** 024 * assumed minimum JOSM memory footprint 025 */ 026 private static final long JOSM_CORE_FOOTPRINT = 50L * 1024L * 1024L; 027 028 private static final MemoryManager INSTANCE = new MemoryManager(); 029 030 private final ArrayList<MemoryHandle<?>> activeHandles = new ArrayList<>(); 031 032 protected MemoryManager() { 033 } 034 035 /** 036 * Allocates a basic, fixed memory size. 037 * <p> 038 * If there is enough free memory, the factory is used to procude one element which is then returned as memory handle. 039 * <p> 040 * You should invoke {@link MemoryHandle#free()} if you do not need that handle any more. 041 * @param <T> The content type of the memory- 042 * @param name A name for the memory area. Only used for debugging. 043 * @param maxBytes The maximum amount of bytes the content may have 044 * @param factory The factory to use to procude the content if there is sufficient memory. 045 * @return A memory handle to the content. 046 * @throws NotEnoughMemoryException If there is not enough memory to allocate. 047 */ 048 public synchronized <T> MemoryHandle<T> allocateMemory(String name, long maxBytes, Supplier<T> factory) throws NotEnoughMemoryException { 049 if (isAvailable(maxBytes)) { 050 T content = factory.get(); 051 if (content == null) { 052 throw new IllegalArgumentException("Factory did not return a content element."); 053 } 054 Main.info(MessageFormat.format("Allocate for {0}: {1} MB of memory. Available: {2} MB.", 055 name, maxBytes / 1024 / 1024, getAvailableMemory() / 1024 / 1024)); 056 MemoryHandle<T> handle = new ManualFreeMemoryHandle<>(name, content, maxBytes); 057 activeHandles.add(handle); 058 return handle; 059 } else { 060 throw new NotEnoughMemoryException(maxBytes); 061 } 062 } 063 064 /** 065 * Check if that memory is available 066 * @param maxBytes The memory to check for 067 * @return <code>true</code> if that memory is available. 068 */ 069 public synchronized boolean isAvailable(long maxBytes) { 070 if (maxBytes < 0) { 071 throw new IllegalArgumentException(MessageFormat.format("Cannot allocate negative number of bytes: {0}", maxBytes)); 072 } 073 return getAvailableMemory() >= maxBytes; 074 } 075 076 /** 077 * Gets the maximum amount of memory available for use in this manager. 078 * @return The maximum amount of memory. 079 */ 080 public synchronized long getMaxMemory() { 081 return Runtime.getRuntime().maxMemory() - JOSM_CORE_FOOTPRINT; 082 } 083 084 /** 085 * Gets the memory that is considered free. 086 * @return The memory that can be used for new allocations. 087 */ 088 public synchronized long getAvailableMemory() { 089 return getMaxMemory() - activeHandles.stream().mapToLong(MemoryHandle::getSize).sum(); 090 } 091 092 /** 093 * Get the global memory manager instance. 094 * @return The memory manager. 095 */ 096 public static MemoryManager getInstance() { 097 return INSTANCE; 098 } 099 100 /** 101 * Reset the state of this manager to the default state. 102 * @return true if there were entries that have been reset. 103 */ 104 protected synchronized List<MemoryHandle<?>> resetState() { 105 ArrayList<MemoryHandle<?>> toFree = new ArrayList<>(activeHandles); 106 toFree.forEach(MemoryHandle::free); 107 return toFree; 108 } 109 110 /** 111 * A memory area managed by the {@link MemoryManager}. 112 * @author Michael Zangl 113 * @param <T> The content type. 114 */ 115 public interface MemoryHandle<T> { 116 117 /** 118 * Gets the content of this memory area. 119 * <p> 120 * This method should be the prefered access to the memory since it will do error checking when {@link #free()} was called. 121 * @return The memory area content. 122 */ 123 T get(); 124 125 /** 126 * Get the size that was requested for this memory area. 127 * @return the size 128 */ 129 long getSize(); 130 131 /** 132 * Manually release this memory area. There should be no memory consumed by this afterwards. 133 */ 134 void free(); 135 } 136 137 private class ManualFreeMemoryHandle<T> implements MemoryHandle<T> { 138 private final String name; 139 private T content; 140 private final long size; 141 142 ManualFreeMemoryHandle(String name, T content, long size) { 143 this.name = name; 144 this.content = content; 145 this.size = size; 146 } 147 148 @Override 149 public T get() { 150 if (content == null) { 151 throw new IllegalStateException(MessageFormat.format("Memory area was accessed after free(): {0}", name)); 152 } 153 return content; 154 } 155 156 @Override 157 public long getSize() { 158 return size; 159 } 160 161 @Override 162 public void free() { 163 if (content == null) { 164 throw new IllegalStateException(MessageFormat.format("Memory area was already marked as freed: {0}", name)); 165 } 166 content = null; 167 synchronized (MemoryManager.this) { 168 activeHandles.remove(this); 169 } 170 } 171 172 @Override 173 public String toString() { 174 return "MemoryHandle [name=" + name + ", size=" + size + ']'; 175 } 176 } 177 178 /** 179 * This exception is thrown if there is not enough memory for allocating the given object. 180 * @author Michael Zangl 181 */ 182 public static class NotEnoughMemoryException extends Exception { 183 NotEnoughMemoryException(long memoryBytesRequired) { 184 super(tr("To add another layer you need to allocate at least {0,number,#}MB memory to JOSM using -Xmx{0,number,#}M " 185 + "option (see http://forum.openstreetmap.org/viewtopic.php?id=25677).\n" 186 + "Currently you have {1,number,#}MB memory allocated for JOSM", 187 memoryBytesRequired / 1024 / 1024, Runtime.getRuntime().maxMemory() / 1024 / 1024)); 188 } 189 } 190}