Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/security/util/Cache.java
38830 views
/*1* Copyright (c) 2002, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.util;2627import java.util.*;28import java.lang.ref.*;2930/**31* Abstract base class and factory for caches. A cache is a key-value mapping.32* It has properties that make it more suitable for caching than a Map.33*34* The factory methods can be used to obtain two different implementations.35* They have the following properties:36*37* . keys and values reside in memory38*39* . keys and values must be non-null40*41* . maximum size. Replacements are made in LRU order.42*43* . optional lifetime, specified in seconds.44*45* . safe for concurrent use by multiple threads46*47* . values are held by either standard references or via SoftReferences.48* SoftReferences have the advantage that they are automatically cleared49* by the garbage collector in response to memory demand. This makes it50* possible to simple set the maximum size to a very large value and let51* the GC automatically size the cache dynamically depending on the52* amount of available memory.53*54* However, note that because of the way SoftReferences are implemented in55* HotSpot at the moment, this may not work perfectly as it clears them fairly56* eagerly. Performance may be improved if the Java heap size is set to larger57* value using e.g. java -ms64M -mx128M foo.Test58*59* Cache sizing: the memory cache is implemented on top of a LinkedHashMap.60* In its current implementation, the number of buckets (NOT entries) in61* (Linked)HashMaps is always a power of two. It is recommended to set the62* maximum cache size to value that uses those buckets fully. For example,63* if a cache with somewhere between 500 and 1000 entries is desired, a64* maximum size of 750 would be a good choice: try 1024 buckets, with a65* load factor of 0.75f, the number of entries can be calculated as66* buckets / 4 * 3. As mentioned above, with a SoftReference cache, it is67* generally reasonable to set the size to a fairly large value.68*69* @author Andreas Sterbenz70*/71public abstract class Cache<K,V> {7273protected Cache() {74// empty75}7677/**78* Return the number of currently valid entries in the cache.79*/80public abstract int size();8182/**83* Remove all entries from the cache.84*/85public abstract void clear();8687/**88* Add an entry to the cache.89*/90public abstract void put(K key, V value);9192/**93* Get a value from the cache.94*/95public abstract V get(Object key);9697/**98* Remove an entry from the cache.99*/100public abstract void remove(Object key);101102/**103* Set the maximum size.104*/105public abstract void setCapacity(int size);106107/**108* Set the timeout(in seconds).109*/110public abstract void setTimeout(int timeout);111112/**113* accept a visitor114*/115public abstract void accept(CacheVisitor<K,V> visitor);116117/**118* Return a new memory cache with the specified maximum size, unlimited119* lifetime for entries, with the values held by SoftReferences.120*/121public static <K,V> Cache<K,V> newSoftMemoryCache(int size) {122return new MemoryCache<>(true, size);123}124125/**126* Return a new memory cache with the specified maximum size, the127* specified maximum lifetime (in seconds), with the values held128* by SoftReferences.129*/130public static <K,V> Cache<K,V> newSoftMemoryCache(int size, int timeout) {131return new MemoryCache<>(true, size, timeout);132}133134/**135* Return a new memory cache with the specified maximum size, unlimited136* lifetime for entries, with the values held by standard references.137*/138public static <K,V> Cache<K,V> newHardMemoryCache(int size) {139return new MemoryCache<>(false, size);140}141142/**143* Return a dummy cache that does nothing.144*/145@SuppressWarnings("unchecked")146public static <K,V> Cache<K,V> newNullCache() {147return (Cache<K,V>) NullCache.INSTANCE;148}149150/**151* Return a new memory cache with the specified maximum size, the152* specified maximum lifetime (in seconds), with the values held153* by standard references.154*/155public static <K,V> Cache<K,V> newHardMemoryCache(int size, int timeout) {156return new MemoryCache<>(false, size, timeout);157}158159/**160* Utility class that wraps a byte array and implements the equals()161* and hashCode() contract in a way suitable for Maps and caches.162*/163public static class EqualByteArray {164165private final byte[] b;166private volatile int hash;167168public EqualByteArray(byte[] b) {169this.b = b;170}171172public int hashCode() {173int h = hash;174if (h == 0) {175h = b.length + 1;176for (int i = 0; i < b.length; i++) {177h += (b[i] & 0xff) * 37;178}179hash = h;180}181return h;182}183184public boolean equals(Object obj) {185if (this == obj) {186return true;187}188if (obj instanceof EqualByteArray == false) {189return false;190}191EqualByteArray other = (EqualByteArray)obj;192return Arrays.equals(this.b, other.b);193}194}195196public interface CacheVisitor<K,V> {197public void visit(Map<K,V> map);198}199200}201202class NullCache<K,V> extends Cache<K,V> {203204final static Cache<Object,Object> INSTANCE = new NullCache<>();205206private NullCache() {207// empty208}209210public int size() {211return 0;212}213214public void clear() {215// empty216}217218public void put(K key, V value) {219// empty220}221222public V get(Object key) {223return null;224}225226public void remove(Object key) {227// empty228}229230public void setCapacity(int size) {231// empty232}233234public void setTimeout(int timeout) {235// empty236}237238public void accept(CacheVisitor<K,V> visitor) {239// empty240}241242}243244class MemoryCache<K,V> extends Cache<K,V> {245246private final static float LOAD_FACTOR = 0.75f;247248// XXXX249private final static boolean DEBUG = false;250251private final Map<K, CacheEntry<K,V>> cacheMap;252private int maxSize;253private long lifetime;254private long nextExpirationTime = Long.MAX_VALUE;255256// ReferenceQueue is of type V instead of Cache<K,V>257// to allow SoftCacheEntry to extend SoftReference<V>258private final ReferenceQueue<V> queue;259260public MemoryCache(boolean soft, int maxSize) {261this(soft, maxSize, 0);262}263264public MemoryCache(boolean soft, int maxSize, int lifetime) {265this.maxSize = maxSize;266this.lifetime = lifetime * 1000;267if (soft)268this.queue = new ReferenceQueue<>();269else270this.queue = null;271272cacheMap = new LinkedHashMap<>(1, LOAD_FACTOR, true);273}274275/**276* Empty the reference queue and remove all corresponding entries277* from the cache.278*279* This method should be called at the beginning of each public280* method.281*/282private void emptyQueue() {283if (queue == null) {284return;285}286int startSize = cacheMap.size();287while (true) {288@SuppressWarnings("unchecked")289CacheEntry<K,V> entry = (CacheEntry<K,V>)queue.poll();290if (entry == null) {291break;292}293K key = entry.getKey();294if (key == null) {295// key is null, entry has already been removed296continue;297}298CacheEntry<K,V> currentEntry = cacheMap.remove(key);299// check if the entry in the map corresponds to the expired300// entry. If not, readd the entry301if ((currentEntry != null) && (entry != currentEntry)) {302cacheMap.put(key, currentEntry);303}304}305if (DEBUG) {306int endSize = cacheMap.size();307if (startSize != endSize) {308System.out.println("*** Expunged " + (startSize - endSize)309+ " entries, " + endSize + " entries left");310}311}312}313314/**315* Scan all entries and remove all expired ones.316*/317private void expungeExpiredEntries() {318emptyQueue();319if (lifetime == 0) {320return;321}322int cnt = 0;323long time = System.currentTimeMillis();324if (nextExpirationTime > time) {325return;326}327nextExpirationTime = Long.MAX_VALUE;328for (Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();329t.hasNext(); ) {330CacheEntry<K,V> entry = t.next();331if (entry.isValid(time) == false) {332t.remove();333cnt++;334} else if (nextExpirationTime > entry.getExpirationTime()) {335nextExpirationTime = entry.getExpirationTime();336}337}338if (DEBUG) {339if (cnt != 0) {340System.out.println("Removed " + cnt341+ " expired entries, remaining " + cacheMap.size());342}343}344}345346public synchronized int size() {347expungeExpiredEntries();348return cacheMap.size();349}350351public synchronized void clear() {352if (queue != null) {353// if this is a SoftReference cache, first invalidate() all354// entries so that GC does not have to enqueue them355for (CacheEntry<K,V> entry : cacheMap.values()) {356entry.invalidate();357}358while (queue.poll() != null) {359// empty360}361}362cacheMap.clear();363}364365public synchronized void put(K key, V value) {366emptyQueue();367long expirationTime = (lifetime == 0) ? 0 :368System.currentTimeMillis() + lifetime;369if (expirationTime < nextExpirationTime) {370nextExpirationTime = expirationTime;371}372CacheEntry<K,V> newEntry = newEntry(key, value, expirationTime, queue);373CacheEntry<K,V> oldEntry = cacheMap.put(key, newEntry);374if (oldEntry != null) {375oldEntry.invalidate();376return;377}378if (maxSize > 0 && cacheMap.size() > maxSize) {379expungeExpiredEntries();380if (cacheMap.size() > maxSize) { // still too large?381Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();382CacheEntry<K,V> lruEntry = t.next();383if (DEBUG) {384System.out.println("** Overflow removal "385+ lruEntry.getKey() + " | " + lruEntry.getValue());386}387t.remove();388lruEntry.invalidate();389}390}391}392393public synchronized V get(Object key) {394emptyQueue();395CacheEntry<K,V> entry = cacheMap.get(key);396if (entry == null) {397return null;398}399long time = (lifetime == 0) ? 0 : System.currentTimeMillis();400if (entry.isValid(time) == false) {401if (DEBUG) {402System.out.println("Ignoring expired entry");403}404cacheMap.remove(key);405return null;406}407return entry.getValue();408}409410public synchronized void remove(Object key) {411emptyQueue();412CacheEntry<K,V> entry = cacheMap.remove(key);413if (entry != null) {414entry.invalidate();415}416}417418public synchronized void setCapacity(int size) {419expungeExpiredEntries();420if (size > 0 && cacheMap.size() > size) {421Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();422for (int i = cacheMap.size() - size; i > 0; i--) {423CacheEntry<K,V> lruEntry = t.next();424if (DEBUG) {425System.out.println("** capacity reset removal "426+ lruEntry.getKey() + " | " + lruEntry.getValue());427}428t.remove();429lruEntry.invalidate();430}431}432433maxSize = size > 0 ? size : 0;434435if (DEBUG) {436System.out.println("** capacity reset to " + size);437}438}439440public synchronized void setTimeout(int timeout) {441emptyQueue();442lifetime = timeout > 0 ? timeout * 1000L : 0L;443444if (DEBUG) {445System.out.println("** lifetime reset to " + timeout);446}447}448449// it is a heavyweight method.450public synchronized void accept(CacheVisitor<K,V> visitor) {451expungeExpiredEntries();452Map<K,V> cached = getCachedEntries();453454visitor.visit(cached);455}456457private Map<K,V> getCachedEntries() {458Map<K,V> kvmap = new HashMap<>(cacheMap.size());459460for (CacheEntry<K,V> entry : cacheMap.values()) {461kvmap.put(entry.getKey(), entry.getValue());462}463464return kvmap;465}466467protected CacheEntry<K,V> newEntry(K key, V value,468long expirationTime, ReferenceQueue<V> queue) {469if (queue != null) {470return new SoftCacheEntry<>(key, value, expirationTime, queue);471} else {472return new HardCacheEntry<>(key, value, expirationTime);473}474}475476private static interface CacheEntry<K,V> {477478boolean isValid(long currentTime);479480void invalidate();481482K getKey();483484V getValue();485486long getExpirationTime();487}488489private static class HardCacheEntry<K,V> implements CacheEntry<K,V> {490491private K key;492private V value;493private long expirationTime;494495HardCacheEntry(K key, V value, long expirationTime) {496this.key = key;497this.value = value;498this.expirationTime = expirationTime;499}500501public K getKey() {502return key;503}504505public V getValue() {506return value;507}508509public long getExpirationTime() {510return expirationTime;511}512513public boolean isValid(long currentTime) {514boolean valid = (currentTime <= expirationTime);515if (valid == false) {516invalidate();517}518return valid;519}520521public void invalidate() {522key = null;523value = null;524expirationTime = -1;525}526}527528private static class SoftCacheEntry<K,V>529extends SoftReference<V>530implements CacheEntry<K,V> {531532private K key;533private long expirationTime;534535SoftCacheEntry(K key, V value, long expirationTime,536ReferenceQueue<V> queue) {537super(value, queue);538this.key = key;539this.expirationTime = expirationTime;540}541542public K getKey() {543return key;544}545546public V getValue() {547return get();548}549550public long getExpirationTime() {551return expirationTime;552}553554public boolean isValid(long currentTime) {555boolean valid = (currentTime <= expirationTime) && (get() != null);556if (valid == false) {557invalidate();558}559return valid;560}561562public void invalidate() {563clear();564key = null;565expirationTime = -1;566}567}568569}570571572