001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.util;
020
021 import java.lang.ref.ReferenceQueue;
022 import java.lang.ref.SoftReference;
023 import java.util.*;
024 import java.util.concurrent.ConcurrentHashMap;
025 import java.util.concurrent.ConcurrentLinkedQueue;
026 import java.util.concurrent.locks.ReentrantLock;
027
028
029 /**
030 * A <code><em>Soft</em>HashMap</code> is a memory-constrained map that stores its <em>values</em> in
031 * {@link SoftReference SoftReference}s. (Contrast this with the JDK's
032 * {@link WeakHashMap WeakHashMap}, which uses weak references for its <em>keys</em>, which is of little value if you
033 * want the cache to auto-resize itself based on memory constraints).
034 * <p/>
035 * Having the values wrapped by soft references allows the cache to automatically reduce its size based on memory
036 * limitations and garbage collection. This ensures that the cache will not cause memory leaks by holding strong
037 * references to all of its values.
038 * <p/>
039 * This class is a generics-enabled Map based on initial ideas from Heinz Kabutz's and Sydney Redelinghuys's
040 * <a href="http://www.javaspecialists.eu/archive/Issue015.html">publicly posted version (with their approval)</a>, with
041 * continued modifications.
042 * <p/>
043 * This implementation is thread-safe and usable in concurrent environments.
044 *
045 * @author Les Hazlewood
046 * @since 1.0
047 */
048 public class SoftHashMap<K, V> implements Map<K, V> {
049
050 /**
051 * The default value of the RETENTION_SIZE attribute, equal to 100.
052 */
053 private static final int DEFAULT_RETENTION_SIZE = 100;
054
055 /**
056 * The internal HashMap that will hold the SoftReference.
057 */
058 private final Map<K, SoftValue<V, K>> map;
059
060 /**
061 * The number of strong references to hold internally, that is, the number of instances to prevent
062 * from being garbage collected automatically (unlike other soft references).
063 */
064 private final int RETENTION_SIZE;
065
066 /**
067 * The FIFO list of strong references (not to be garbage collected), order of last access.
068 */
069 private final Queue<V> strongReferences; //guarded by 'strongReferencesLock'
070 private final ReentrantLock strongReferencesLock;
071
072 /**
073 * Reference queue for cleared SoftReference objects.
074 */
075 private final ReferenceQueue<? super V> queue;
076
077 /**
078 * Creates a new SoftHashMap with a default retention size size of
079 * {@link #DEFAULT_RETENTION_SIZE DEFAULT_RETENTION_SIZE} (100 entries).
080 *
081 * @see #SoftHashMap(int)
082 */
083 public SoftHashMap() {
084 this(DEFAULT_RETENTION_SIZE);
085 }
086
087 /**
088 * Creates a new SoftHashMap with the specified retention size.
089 * <p/>
090 * The retention size (n) is the total number of most recent entries in the map that will be strongly referenced
091 * (ie 'retained') to prevent them from being eagerly garbage collected. That is, the point of a SoftHashMap is to
092 * allow the garbage collector to remove as many entries from this map as it desires, but there will always be (n)
093 * elements retained after a GC due to the strong references.
094 * <p/>
095 * Note that in a highly concurrent environments the exact total number of strong references may differ slightly
096 * than the actual <code>retentionSize</code> value. This number is intended to be a best-effort retention low
097 * water mark.
098 *
099 * @param retentionSize the total number of most recent entries in the map that will be strongly referenced
100 * (retained), preventing them from being eagerly garbage collected by the JVM.
101 */
102 @SuppressWarnings({"unchecked"})
103 public SoftHashMap(int retentionSize) {
104 super();
105 RETENTION_SIZE = Math.max(0, retentionSize);
106 queue = new ReferenceQueue<V>();
107 strongReferencesLock = new ReentrantLock();
108 map = new ConcurrentHashMap<K, SoftValue<V, K>>();
109 strongReferences = new ConcurrentLinkedQueue<V>();
110 }
111
112 /**
113 * Creates a {@code SoftHashMap} backed by the specified {@code source}, with a default retention
114 * size of {@link #DEFAULT_RETENTION_SIZE DEFAULT_RETENTION_SIZE} (100 entries).
115 *
116 * @param source the backing map to populate this {@code SoftHashMap}
117 * @see #SoftHashMap(Map,int)
118 */
119 public SoftHashMap(Map<K, V> source) {
120 this(DEFAULT_RETENTION_SIZE);
121 putAll(source);
122 }
123
124 /**
125 * Creates a {@code SoftHashMap} backed by the specified {@code source}, with the specified retention size.
126 * <p/>
127 * The retention size (n) is the total number of most recent entries in the map that will be strongly referenced
128 * (ie 'retained') to prevent them from being eagerly garbage collected. That is, the point of a SoftHashMap is to
129 * allow the garbage collector to remove as many entries from this map as it desires, but there will always be (n)
130 * elements retained after a GC due to the strong references.
131 * <p/>
132 * Note that in a highly concurrent environments the exact total number of strong references may differ slightly
133 * than the actual <code>retentionSize</code> value. This number is intended to be a best-effort retention low
134 * water mark.
135 *
136 * @param source the backing map to populate this {@code SoftHashMap}
137 * @param retentionSize the total number of most recent entries in the map that will be strongly referenced
138 * (retained), preventing them from being eagerly garbage collected by the JVM.
139 */
140 public SoftHashMap(Map<K, V> source, int retentionSize) {
141 this(retentionSize);
142 putAll(source);
143 }
144
145 public V get(Object key) {
146 processQueue();
147
148 V result = null;
149 SoftValue<V, K> value = map.get(key);
150
151 if (value != null) {
152 //unwrap the 'real' value from the SoftReference
153 result = value.get();
154 if (result == null) {
155 //The wrapped value was garbage collected, so remove this entry from the backing map:
156 //noinspection SuspiciousMethodCalls
157 map.remove(key);
158 } else {
159 //Add this value to the beginning of the strong reference queue (FIFO).
160 addToStrongReferences(result);
161 }
162 }
163 return result;
164 }
165
166 private void addToStrongReferences(V result) {
167 strongReferencesLock.lock();
168 try {
169 strongReferences.add(result);
170 trimStrongReferencesIfNecessary();
171 } finally {
172 strongReferencesLock.unlock();
173 }
174
175 }
176
177 //Guarded by the strongReferencesLock in the addToStrongReferences method
178
179 private void trimStrongReferencesIfNecessary() {
180 //trim the strong ref queue if necessary:
181 while (strongReferences.size() > RETENTION_SIZE) {
182 strongReferences.poll();
183 }
184 }
185
186 /**
187 * Traverses the ReferenceQueue and removes garbage-collected SoftValue objects from the backing map
188 * by looking them up using the SoftValue.key data member.
189 */
190 private void processQueue() {
191 SoftValue sv;
192 while ((sv = (SoftValue) queue.poll()) != null) {
193 //noinspection SuspiciousMethodCalls
194 map.remove(sv.key); // we can access private data!
195 }
196 }
197
198 public boolean isEmpty() {
199 processQueue();
200 return map.isEmpty();
201 }
202
203 public boolean containsKey(Object key) {
204 processQueue();
205 return map.containsKey(key);
206 }
207
208 public boolean containsValue(Object value) {
209 processQueue();
210 Collection values = values();
211 return values != null && values.contains(value);
212 }
213
214 public void putAll(Map<? extends K, ? extends V> m) {
215 if (m == null || m.isEmpty()) {
216 processQueue();
217 return;
218 }
219 for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
220 put(entry.getKey(), entry.getValue());
221 }
222 }
223
224 public Set<K> keySet() {
225 processQueue();
226 return map.keySet();
227 }
228
229 public Collection<V> values() {
230 processQueue();
231 Collection<K> keys = map.keySet();
232 if (keys.isEmpty()) {
233 //noinspection unchecked
234 return Collections.EMPTY_SET;
235 }
236 Collection<V> values = new ArrayList<V>(keys.size());
237 for (K key : keys) {
238 V v = get(key);
239 if (v != null) {
240 values.add(v);
241 }
242 }
243 return values;
244 }
245
246 /**
247 * Creates a new entry, but wraps the value in a SoftValue instance to enable auto garbage collection.
248 */
249 public V put(K key, V value) {
250 processQueue(); // throw out garbage collected values first
251 SoftValue<V, K> sv = new SoftValue<V, K>(value, key, queue);
252 SoftValue<V, K> previous = map.put(key, sv);
253 addToStrongReferences(value);
254 return previous != null ? previous.get() : null;
255 }
256
257 public V remove(Object key) {
258 processQueue(); // throw out garbage collected values first
259 SoftValue<V, K> raw = map.remove(key);
260 return raw != null ? raw.get() : null;
261 }
262
263 public void clear() {
264 strongReferencesLock.lock();
265 try {
266 strongReferences.clear();
267 } finally {
268 strongReferencesLock.unlock();
269 }
270 processQueue(); // throw out garbage collected values
271 map.clear();
272 }
273
274 public int size() {
275 processQueue(); // throw out garbage collected values first
276 return map.size();
277 }
278
279 public Set<Map.Entry<K, V>> entrySet() {
280 processQueue(); // throw out garbage collected values first
281 Collection<K> keys = map.keySet();
282 if (keys.isEmpty()) {
283 //noinspection unchecked
284 return Collections.EMPTY_SET;
285 }
286
287 Map<K, V> kvPairs = new HashMap<K, V>(keys.size());
288 for (K key : keys) {
289 V v = get(key);
290 if (v != null) {
291 kvPairs.put(key, v);
292 }
293 }
294 return kvPairs.entrySet();
295 }
296
297 /**
298 * We define our own subclass of SoftReference which contains
299 * not only the value but also the key to make it easier to find
300 * the entry in the HashMap after it's been garbage collected.
301 */
302 private static class SoftValue<V, K> extends SoftReference<V> {
303
304 private final K key;
305
306 /**
307 * Constructs a new instance, wrapping the value, key, and queue, as
308 * required by the superclass.
309 *
310 * @param value the map value
311 * @param key the map key
312 * @param queue the soft reference queue to poll to determine if the entry had been reaped by the GC.
313 */
314 private SoftValue(V value, K key, ReferenceQueue<? super V> queue) {
315 super(value, queue);
316 this.key = key;
317 }
318
319 }
320 }