All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.lib.commons.cache4j.backend.SoftRefCacheBackend Maven / Gradle / Ivy

There is a newer version: 2024.7.2
Show newest version
/*
 * Copyright (c) CQSE GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.conqat.lib.commons.cache4j.backend;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Backend implementing a memory sensitive cache using {@link SoftReference}s. This implementation
 * has two drawbacks. First, keys are not freed automatically and second there are situations with
 * large heaps, where the VM exits with GC problems although sufficient memory is available. To deal
 * with the first issue, we implement regular cleaning intervals that sweep entries with stale
 * values. For the second problem, you can adjust the VM parameters as described in the ConQAT book.
 */
/* package */class SoftRefCacheBackend implements ICacheBackend {

	/**
	 * Number of minimum calls to {@link #store(Object, Object)} after which {@link #cleanUp()} is
	 * called.
	 */
	private static final int CLEANUP_INTERVAL = 1000;

	/** Counts the number of calls to {@link #store(Object, Object)}. */
	private int storeCalls = 0;

	/** The actual cache. */
	private final Map> cache = new HashMap<>();

	/** {@inheritDoc} */
	@Override
	public void store(K key, V value) {
		cache.put(key, new SoftReference<>(value));
		storeCalls += 1;

		// we perform a cleanup only every CLEANUP_INTERVAL steps. To avoid bad
		// performance for large caches, we also use the cache size as a lower
		// limit. This way we run in amortized constant time.
		if (storeCalls >= Math.max(CLEANUP_INTERVAL, cache.size())) {
			storeCalls = 0;
			cleanUp();
		}
	}

	/**
	 * Performs a clean up to discard unused keys. This is called regularly, as otherwise the keys
	 * (which are not garbage collected) can accumulate and waste memory.
	 */
	private void cleanUp() {
		List staleKeys = new ArrayList<>();
		for (Entry> entry : cache.entrySet()) {
			if (entry.getValue() == null || entry.getValue().get() == null) {
				staleKeys.add(entry.getKey());
			}
		}

		for (K key : staleKeys) {
			cache.remove(key);
		}
	}

	/** {@inheritDoc} */
	@Override
	public V retrieve(K key) {
		SoftReference softReference = cache.get(key);
		if (softReference == null) {
			return null;
		}
		return softReference.get();
	}

	/** {@inheritDoc} */
	@Override
	public SoftRefCacheBackend newInstance() {
		return new SoftRefCacheBackend<>();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy