/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tools;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Heap utilization aware reference cache of the given size. The cache uses the LRU strategy to
* displace entries.
* <p>
* New cache entries can be created via {@link newReference()}. A newly created reference behaves
* like a {@link SoftReference} as long as the reference is cached. Once displaced from the cache,
* the references behaves like a {@link WeakReference}.
* <p>
* The cache is heap utilization aware in the sense that it displaces entries (if any) if the load
* factor of the heap is higher than {@value #MAX_HEAP_LOAD_FACTOR}.
*
* @author Michael Knopf, Gisa Schaefer
* @since 7.1.0
*/
public class ReferenceCache<T> {
/** Cache entries are displaced if the heap load factor is higher than {@value}. */
private static final double MAX_HEAP_LOAD_FACTOR = 0.5;
/** LRU cache for the (stronger) {@link SoftReference}s. */
private final Map<TransparentWeakReference, SoftReference<T>> cache;
/** The size of this cache. */
private final int size;
/**
* A heap utilization aware reference object similar in its interface to {@link WeakReference}s
* and {@link SoftReference}s. In most scenarios, it is stronger than a {@link WeakReference}
* but weaker than a {@link SoftReference}.
*
* @see ReferenceCache
*/
public class Reference {
/** Weak reference that lives is never cleared manually (fall back for the cache). */
private final TransparentWeakReference weak;
/**
* Creates a new cached reference to the given object.
*
* @param the
* object to be cached
*/
private Reference(T value) {
weak = new TransparentWeakReference(value);
if (value != null) {
synchronized (cache) {
checkHeapUtilization();
cache.put(weak, new SoftReference<>(value));
}
}
}
/**
* Returns this reference object's referent. If this reference object has been cleared,
* either by the program or by the garbage collector, then this method returns {@code null}.
* <p>
* If the reference has not been cleared yet, the corresponding cache entry will be updated.
* This makes it more likely that subsequent look ups will be successful.
*
* @return the object to which this reference refers, or {@code null} if this reference
* object has been cleared
*/
public T get() {
SoftReference<T> soft;
synchronized (cache) {
checkHeapUtilization();
soft = cache.get(weak);
}
if (soft == null) {
return weak.get();
} else {
T value = soft.get();
if (value != null) {
return value;
} else {
return weak.get();
}
}
}
/**
* Returns this reference object's referent. If this reference object has been cleared,
* either by the program or by the garbage collector, then this method returns {@code null}.
* <p>
* Unlike {@link #get()}, invoking this method does not update the cache and thus does not
* increase the likelihood of successful subsequent look ups.
*
* @return the object to which this reference refers, or {@code null} if this reference
* object has been cleared
*/
public T weakGet() {
return weak.get();
}
}
/**
* A {@link WeakReference} that is transparent with respect to {@link #hashCode()} and
* {@link #equals(Object)}. It inherits its referent's initial hash code and determines equality
* by using the referents' implementations.
*/
private class TransparentWeakReference extends WeakReference<T> {
/** The referent's initial hash code. */
private final int hashCode;
private TransparentWeakReference(T referent) {
super(referent);
// remember the referents initial hash code
hashCode = referent == null ? 0 : referent.hashCode();
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ReferenceCache.TransparentWeakReference) {
WeakReference<?> other = (WeakReference<?>) obj;
Object thisReferrent = get();
Object otherReferrent = other.get();
return thisReferrent == null ? otherReferrent == null : thisReferrent.equals(otherReferrent);
} else {
return false;
}
}
}
/**
* A simple LRU cache that is implemented by overriding {@link LinkedHashMap#removeEldestEntry}.
*/
private class LRUCache extends LinkedHashMap<TransparentWeakReference, SoftReference<T>> {
private static final long serialVersionUID = 1L;
private LRUCache() {
// Use low load factor to prevent collisions and an access order map to enforce LRU
// properties.
super(size, 0.5f, true);
}
@Override
public void clear() {
for (SoftReference<T> value : values()) {
value.clear();
}
super.clear();
}
@Override
protected boolean removeEldestEntry(Map.Entry<TransparentWeakReference, SoftReference<T>> eldest) {
if (size() > size) {
// clear eldest entry and remove it from the cache
eldest.getValue().clear();
return true;
}
return false;
}
}
/**
* Creates a new heap utilization aware reference cache of the given size.
*
* @param size
* the size of the new cache
*/
public ReferenceCache(final int size) {
this.size = size;
cache = new LRUCache();
}
public Reference newReference(T value) {
return new Reference(value);
}
/**
* Checks the current heap utilization and clears the cache if necessary.
*/
private void checkHeapUtilization() {
long heapMaxSize = Runtime.getRuntime().maxMemory();
long heapSize = Runtime.getRuntime().totalMemory();
long freeSize = Runtime.getRuntime().freeMemory();
double loadFactor = (double) (heapSize - freeSize) / heapMaxSize;
if (loadFactor > MAX_HEAP_LOAD_FACTOR) {
cache.clear();
}
}
}