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

co.elastic.apm.agent.sdk.weakconcurrent.WeakKeySoftValueLoadingCache Maven / Gradle / Ivy

There is a newer version: 1.52.1
Show newest version
/*
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. licenses this file to you 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 co.elastic.apm.agent.sdk.weakconcurrent;

import co.elastic.apm.agent.sdk.logging.Logger;
import co.elastic.apm.agent.sdk.logging.LoggerFactory;

import javax.annotation.Nullable;
import java.lang.ref.SoftReference;

/**
 * This cache is useful in situations when hard referencing the cache key and the value would otherwise lead to memory or class loader leaks.
 * 

* Use use-case is storing a value, * like a {@link Class} or {@link java.lang.invoke.MethodHandle} which is specific to a certain {@link ClassLoader}. * Holding a strong reference to any of these from the agent code would lead to classloader leaks. * That's why the key is wrapped in a {@link java.lang.ref.WeakReference} and the value is wrapped in a {@link SoftReference}. *

*

* An otherwise not referenced {@link ClassLoader} might still not be collected for a longer time as usual * if it is indirectly referenced by the value which is wrapped in a {@link SoftReference}. * But there is no danger of OOMEs, as the softly referenced value will be collected when memory gets scarce * or if it has not been accessed for some time. *

*

* There can also be the situation that a value is collected although it is actually still used. * In that case, the value is re-loaded transparently. * For frequently accessed values that should not be the case, * as a {@link SoftReference} works a bit like a LRU cache * (see http://xmlandmore.blogspot.com/2014/12/jdk-8-when-softly-referenced-objects.html). *

*

* If the cache key has been collected, * the underlying {@link WeakMap} makes sure that the map entry is deleted. *

* * * @param the key type * @param the value type */ public class WeakKeySoftValueLoadingCache { private static final Logger logger = LoggerFactory.getLogger(WeakKeySoftValueLoadingCache.class); private final WeakMap> cache = WeakConcurrent.buildMap(); private final ValueSupplier valueSupplier; public WeakKeySoftValueLoadingCache(ValueSupplier valueSupplier) { this.valueSupplier = valueSupplier; } @Nullable public V get(K key) { final CacheValue cacheValue = cache.get(key); if (cacheValue != null) { return cacheValue.get(key); } else { CacheValue value = new CacheValue<>(key, valueSupplier); cache.put(key, value); return value.get(key); } } /** * Supplies a value, given a key. * * @param the key type * @param the value type */ public interface ValueSupplier { /** * This supplier is expected to return the same or equal-and-stateless object if called multiple times for the same key, * for thread safety. * * @param key the cache key * @return the value for the provided cache key */ @Nullable V get(K key); } /** * A wrapper object for a cached value. *

* This is needed as a {@link java.util.concurrent.ConcurrentMap} does not allow values to be null, * but we want to be able to cache null values. * Also, this the cache value wrapps the value into a {@link SoftReference} and re-loads the value in case it has been collected. *

* * @param the key type * @param the value type */ private static class CacheValue { private final ValueSupplier valueSupplier; @Nullable private SoftReference valueReference; private CacheValue(K key, ValueSupplier valueSupplier) { this.valueSupplier = valueSupplier; V value = valueSupplier.get(key); if (value != null) { valueReference = new SoftReference<>(value); } } @Nullable public V get(K key) { if (valueReference == null) { return null; } V value = this.valueReference.get(); if (value != null) { return value; } else { logger.info("The value for the key {} has been collected, reloading it", key); // the value has been collected, so re-load it value = valueSupplier.get(key); // this is thread safe but the value might be created in multiple threads // the assumption is that this is preferable to the alternative which would involve volatile reads for each get, // even if the value has not been collected valueReference = new SoftReference<>(value); return value; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy