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