8152115: (proxy) Examine performance of dynamic proxy creation
Redesign caching of dynamic Proxy classes Reviewed-by: mchung
This commit is contained in:
parent
aaebc5882b
commit
589c46da46
@ -2625,6 +2625,25 @@ public abstract class ClassLoader {
|
|||||||
// the ServiceCatalog for modules associated with this class loader.
|
// the ServiceCatalog for modules associated with this class loader.
|
||||||
private volatile ServicesCatalog servicesCatalog;
|
private volatile ServicesCatalog servicesCatalog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ConcurrentHashMap used as a storage for ClassLoaderValue(s)
|
||||||
|
* associated with this ClassLoader, creating it if it doesn't already exist.
|
||||||
|
*/
|
||||||
|
ConcurrentHashMap<?, ?> createOrGetClassLoaderValueMap() {
|
||||||
|
ConcurrentHashMap<?, ?> map = classLoaderValueMap;
|
||||||
|
if (map == null) {
|
||||||
|
map = new ConcurrentHashMap<>();
|
||||||
|
boolean set = trySetObjectField("classLoaderValueMap", map);
|
||||||
|
if (!set) {
|
||||||
|
// beaten by someone else
|
||||||
|
map = classLoaderValueMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the storage for ClassLoaderValue(s) associated with this ClassLoader
|
||||||
|
private volatile ConcurrentHashMap<?, ?> classLoaderValueMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to atomically set a volatile field in this object. Returns
|
* Attempts to atomically set a volatile field in this object. Returns
|
||||||
|
@ -49,6 +49,7 @@ import java.security.AccessController;
|
|||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.nio.channels.Channel;
|
import java.nio.channels.Channel;
|
||||||
import java.nio.channels.spi.SelectorProvider;
|
import java.nio.channels.spi.SelectorProvider;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -2026,6 +2027,9 @@ public final class System {
|
|||||||
public ServicesCatalog createOrGetServicesCatalog(ClassLoader cl) {
|
public ServicesCatalog createOrGetServicesCatalog(ClassLoader cl) {
|
||||||
return cl.createOrGetServicesCatalog();
|
return cl.createOrGetServicesCatalog();
|
||||||
}
|
}
|
||||||
|
public ConcurrentHashMap<?, ?> createOrGetClassLoaderValueMap(ClassLoader cl) {
|
||||||
|
return cl.createOrGetClassLoaderValueMap();
|
||||||
|
}
|
||||||
public Class<?> findBootstrapClassOrNull(ClassLoader cl, String name) {
|
public Class<?> findBootstrapClassOrNull(ClassLoader cl, String name) {
|
||||||
return cl.findBootstrapClassOrNull(name);
|
return cl.findBootstrapClassOrNull(name);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,431 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code 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 General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.lang.reflect;
|
||||||
|
|
||||||
|
import jdk.internal.loader.BootLoader;
|
||||||
|
import jdk.internal.misc.JavaLangAccess;
|
||||||
|
import jdk.internal.misc.SharedSecrets;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractClassLoaderValue is a superclass of root-{@link ClassLoaderValue}
|
||||||
|
* and {@link Sub sub}-ClassLoaderValue.
|
||||||
|
*
|
||||||
|
* @param <CLV> the type of concrete ClassLoaderValue (this type)
|
||||||
|
* @param <V> the type of values associated with ClassLoaderValue
|
||||||
|
*/
|
||||||
|
abstract class AbstractClassLoaderValue<CLV extends AbstractClassLoaderValue<CLV, V>, V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sole constructor.
|
||||||
|
*/
|
||||||
|
AbstractClassLoaderValue() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the key component of this ClassLoaderValue. The key component of
|
||||||
|
* the root-{@link ClassLoaderValue} is the ClassLoaderValue itself,
|
||||||
|
* while the key component of a {@link #sub(Object) sub}-ClassLoaderValue
|
||||||
|
* is what was given to construct it.
|
||||||
|
*
|
||||||
|
* @return the key component of this ClassLoaderValue.
|
||||||
|
*/
|
||||||
|
public abstract Object key();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs new sub-ClassLoaderValue of this ClassLoaderValue with given
|
||||||
|
* key component.
|
||||||
|
*
|
||||||
|
* @param key the key component of the sub-ClassLoaderValue.
|
||||||
|
* @param <K> the type of the key component.
|
||||||
|
* @return a sub-ClassLoaderValue of this ClassLoaderValue for given key
|
||||||
|
*/
|
||||||
|
public <K> Sub<K> sub(K key) {
|
||||||
|
return new Sub<>(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this ClassLoaderValue is equal to given {@code clv}
|
||||||
|
* or if this ClassLoaderValue was derived from given {@code clv} by a chain
|
||||||
|
* of {@link #sub(Object)} invocations.
|
||||||
|
*
|
||||||
|
* @param clv the ClassLoaderValue to test this against
|
||||||
|
* @return if this ClassLoaderValue is equal to given {@code clv} or
|
||||||
|
* its descendant
|
||||||
|
*/
|
||||||
|
public abstract boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value associated with this ClassLoaderValue and given ClassLoader
|
||||||
|
* or {@code null} if there is none.
|
||||||
|
*
|
||||||
|
* @param cl the ClassLoader for the associated value
|
||||||
|
* @return the value associated with this ClassLoaderValue and given ClassLoader
|
||||||
|
* or {@code null} if there is none.
|
||||||
|
*/
|
||||||
|
public V get(ClassLoader cl) {
|
||||||
|
Object val = AbstractClassLoaderValue.<CLV>map(cl).get(this);
|
||||||
|
try {
|
||||||
|
return extractValue(val);
|
||||||
|
} catch (Memoizer.RecursiveInvocationException e) {
|
||||||
|
// propagate recursive get() for the same key that is just
|
||||||
|
// being calculated in computeIfAbsent()
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// don't propagate exceptions thrown from Memoizer - pretend
|
||||||
|
// that there was no entry
|
||||||
|
// (computeIfAbsent invocation will try to remove it anyway)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associates given value {@code v} with this ClassLoaderValue and given
|
||||||
|
* ClassLoader and returns {@code null} if there was no previously associated
|
||||||
|
* value or does nothing and returns previously associated value if there
|
||||||
|
* was one.
|
||||||
|
*
|
||||||
|
* @param cl the ClassLoader for the associated value
|
||||||
|
* @param v the value to associate
|
||||||
|
* @return previously associated value or null if there was none
|
||||||
|
*/
|
||||||
|
public V putIfAbsent(ClassLoader cl, V v) {
|
||||||
|
ConcurrentHashMap<CLV, Object> map = map(cl);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
CLV clv = (CLV) this;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
Object val = map.putIfAbsent(clv, v);
|
||||||
|
return extractValue(val);
|
||||||
|
} catch (Memoizer.RecursiveInvocationException e) {
|
||||||
|
// propagate RecursiveInvocationException for the same key that
|
||||||
|
// is just being calculated in computeIfAbsent
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// don't propagate exceptions thrown from foreign Memoizer -
|
||||||
|
// pretend that there was no entry and retry
|
||||||
|
// (foreign computeIfAbsent invocation will try to remove it anyway)
|
||||||
|
}
|
||||||
|
// TODO:
|
||||||
|
// Thread.onSpinLoop(); // when available
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the value associated with this ClassLoaderValue and given
|
||||||
|
* ClassLoader if the associated value is equal to given value {@code v} and
|
||||||
|
* returns {@code true} or does nothing and returns {@code false} if there is
|
||||||
|
* no currently associated value or it is not equal to given value {@code v}.
|
||||||
|
*
|
||||||
|
* @param cl the ClassLoader for the associated value
|
||||||
|
* @param v the value to compare with currently associated value
|
||||||
|
* @return {@code true} if the association was removed or {@code false} if not
|
||||||
|
*/
|
||||||
|
public boolean remove(ClassLoader cl, Object v) {
|
||||||
|
return AbstractClassLoaderValue.<CLV>map(cl).remove(this, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value associated with this ClassLoaderValue and given
|
||||||
|
* ClassLoader if there is one or computes the value by invoking given
|
||||||
|
* {@code mappingFunction}, associates it and returns it.
|
||||||
|
* <p>
|
||||||
|
* Computation and association of the computed value is performed atomically
|
||||||
|
* by the 1st thread that requests a particular association while holding a
|
||||||
|
* lock associated with this ClassLoaderValue and given ClassLoader.
|
||||||
|
* Nested calls from the {@code mappingFunction} to {@link #get},
|
||||||
|
* {@link #putIfAbsent} or {@link #computeIfAbsent} for the same association
|
||||||
|
* are not allowed and throw {@link IllegalStateException}. Nested call to
|
||||||
|
* {@link #remove} for the same association is allowed but will always return
|
||||||
|
* {@code false} regardless of passed-in comparison value. Nested calls for
|
||||||
|
* other association(s) are allowed, but care should be taken to avoid
|
||||||
|
* deadlocks. When two threads perform nested computations of the overlapping
|
||||||
|
* set of associations they should always request them in the same order.
|
||||||
|
*
|
||||||
|
* @param cl the ClassLoader for the associated value
|
||||||
|
* @param mappingFunction the function to compute the value
|
||||||
|
* @return the value associated with this ClassLoaderValue and given
|
||||||
|
* ClassLoader.
|
||||||
|
* @throws IllegalStateException if a direct or indirect invocation from
|
||||||
|
* within given {@code mappingFunction} that
|
||||||
|
* computes the value of a particular association
|
||||||
|
* to {@link #get}, {@link #putIfAbsent} or
|
||||||
|
* {@link #computeIfAbsent}
|
||||||
|
* for the same association is attempted.
|
||||||
|
*/
|
||||||
|
public V computeIfAbsent(ClassLoader cl,
|
||||||
|
BiFunction<
|
||||||
|
? super ClassLoader,
|
||||||
|
? super CLV,
|
||||||
|
? extends V
|
||||||
|
> mappingFunction) throws IllegalStateException {
|
||||||
|
ConcurrentHashMap<CLV, Object> map = map(cl);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
CLV clv = (CLV) this;
|
||||||
|
Memoizer<CLV, V> mv = null;
|
||||||
|
while (true) {
|
||||||
|
Object val = (mv == null) ? map.get(clv) : map.putIfAbsent(clv, mv);
|
||||||
|
if (val == null) {
|
||||||
|
if (mv == null) {
|
||||||
|
// create Memoizer lazily when 1st needed and restart loop
|
||||||
|
mv = new Memoizer<>(cl, clv, mappingFunction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// mv != null, therefore sv == null was a result of successful
|
||||||
|
// putIfAbsent
|
||||||
|
try {
|
||||||
|
// trigger Memoizer to compute the value
|
||||||
|
V v = mv.get();
|
||||||
|
// attempt to replace our Memoizer with the value
|
||||||
|
map.replace(clv, mv, v);
|
||||||
|
// return computed value
|
||||||
|
return v;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// our Memoizer has thrown, attempt to remove it
|
||||||
|
map.remove(clv, mv);
|
||||||
|
// propagate exception because it's from our Memoizer
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return extractValue(val);
|
||||||
|
} catch (Memoizer.RecursiveInvocationException e) {
|
||||||
|
// propagate recursive attempts to calculate the same
|
||||||
|
// value as being calculated at the moment
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// don't propagate exceptions thrown from foreign Memoizer -
|
||||||
|
// pretend that there was no entry and retry
|
||||||
|
// (foreign computeIfAbsent invocation will try to remove it anyway)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO:
|
||||||
|
// Thread.onSpinLoop(); // when available
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all values associated with given ClassLoader {@code cl} and
|
||||||
|
* {@link #isEqualOrDescendantOf(AbstractClassLoaderValue) this or descendants}
|
||||||
|
* of this ClassLoaderValue.
|
||||||
|
* This is not an atomic operation. Other threads may see some associations
|
||||||
|
* be already removed and others still present while this method is executing.
|
||||||
|
* <p>
|
||||||
|
* The sole intention of this method is to cleanup after a unit test that
|
||||||
|
* tests ClassLoaderValue directly. It is not intended for use in
|
||||||
|
* actual algorithms.
|
||||||
|
*
|
||||||
|
* @param cl the associated ClassLoader of the values to be removed
|
||||||
|
*/
|
||||||
|
public void removeAll(ClassLoader cl) {
|
||||||
|
ConcurrentHashMap<CLV, Object> map = map(cl);
|
||||||
|
for (Iterator<CLV> i = map.keySet().iterator(); i.hasNext(); ) {
|
||||||
|
if (i.next().isEqualOrDescendantOf(this)) {
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a ConcurrentHashMap for given ClassLoader
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <CLV extends AbstractClassLoaderValue<CLV, ?>>
|
||||||
|
ConcurrentHashMap<CLV, Object> map(ClassLoader cl) {
|
||||||
|
return (ConcurrentHashMap<CLV, Object>)
|
||||||
|
(cl == null ? BootLoader.getClassLoaderValueMap()
|
||||||
|
: JLA.createOrGetClassLoaderValueMap(cl));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return value extracted from the {@link Memoizer} if given
|
||||||
|
* {@code memoizerOrValue} parameter is a {@code Memoizer} or
|
||||||
|
* just return given parameter.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private V extractValue(Object memoizerOrValue) {
|
||||||
|
if (memoizerOrValue instanceof Memoizer) {
|
||||||
|
return ((Memoizer<?, V>) memoizerOrValue).get();
|
||||||
|
} else {
|
||||||
|
return (V) memoizerOrValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A memoized supplier that invokes given {@code mappingFunction} just once
|
||||||
|
* and remembers the result or thrown exception for subsequent calls.
|
||||||
|
* If given mappingFunction returns null, it is converted to NullPointerException,
|
||||||
|
* thrown from the Memoizer's {@link #get()} method and remembered.
|
||||||
|
* If the Memoizer is invoked recursively from the given {@code mappingFunction},
|
||||||
|
* {@link RecursiveInvocationException} is thrown, but it is not remembered.
|
||||||
|
* The in-flight call to the {@link #get()} can still complete successfully if
|
||||||
|
* such exception is handled by the mappingFunction.
|
||||||
|
*/
|
||||||
|
private static final class Memoizer<CLV extends AbstractClassLoaderValue<CLV, V>, V>
|
||||||
|
implements Supplier<V> {
|
||||||
|
|
||||||
|
private final ClassLoader cl;
|
||||||
|
private final CLV clv;
|
||||||
|
private final BiFunction<? super ClassLoader, ? super CLV, ? extends V>
|
||||||
|
mappingFunction;
|
||||||
|
|
||||||
|
private volatile V v;
|
||||||
|
private volatile Throwable t;
|
||||||
|
private boolean inCall;
|
||||||
|
|
||||||
|
Memoizer(ClassLoader cl,
|
||||||
|
CLV clv,
|
||||||
|
BiFunction<? super ClassLoader, ? super CLV, ? extends V>
|
||||||
|
mappingFunction
|
||||||
|
) {
|
||||||
|
this.cl = cl;
|
||||||
|
this.clv = clv;
|
||||||
|
this.mappingFunction = mappingFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get() throws RecursiveInvocationException {
|
||||||
|
V v = this.v;
|
||||||
|
if (v != null) return v;
|
||||||
|
Throwable t = this.t;
|
||||||
|
if (t == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
if ((v = this.v) == null && (t = this.t) == null) {
|
||||||
|
if (inCall) {
|
||||||
|
throw new RecursiveInvocationException();
|
||||||
|
}
|
||||||
|
inCall = true;
|
||||||
|
try {
|
||||||
|
this.v = v = Objects.requireNonNull(
|
||||||
|
mappingFunction.apply(cl, clv));
|
||||||
|
} catch (Throwable x) {
|
||||||
|
this.t = t = x;
|
||||||
|
} finally {
|
||||||
|
inCall = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (v != null) return v;
|
||||||
|
if (t instanceof Error) {
|
||||||
|
throw (Error) t;
|
||||||
|
} else if (t instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException) t;
|
||||||
|
} else {
|
||||||
|
throw new UndeclaredThrowableException(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class RecursiveInvocationException extends IllegalStateException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
RecursiveInvocationException() {
|
||||||
|
super("Recursive call");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sub-ClassLoaderValue is an inner class of {@link AbstractClassLoaderValue}
|
||||||
|
* and also a subclass of it. It can therefore be instantiated as an inner
|
||||||
|
* class of either an instance of root-{@link ClassLoaderValue} or another
|
||||||
|
* instance of itself. This enables composing type-safe compound keys of
|
||||||
|
* arbitrary length:
|
||||||
|
* <pre>{@code
|
||||||
|
* ClassLoaderValue<V> clv = new ClassLoaderValue<>();
|
||||||
|
* ClassLoaderValue<V>.Sub<K1>.Sub<K2>.Sub<K3> clv_k123 =
|
||||||
|
* clv.sub(k1).sub(k2).sub(k3);
|
||||||
|
* }</pre>
|
||||||
|
* From which individual components are accessible in a type-safe way:
|
||||||
|
* <pre>{@code
|
||||||
|
* K1 k1 = clv_k123.parent().parent().key();
|
||||||
|
* K2 k2 = clv_k123.parent().key();
|
||||||
|
* K3 k3 = clv_k123.key();
|
||||||
|
* }</pre>
|
||||||
|
* This allows specifying non-capturing lambdas for the mapping function of
|
||||||
|
* {@link #computeIfAbsent(ClassLoader, BiFunction)} operation that can
|
||||||
|
* access individual key components from passed-in
|
||||||
|
* sub-[sub-...]ClassLoaderValue instance in a type-safe way.
|
||||||
|
*
|
||||||
|
* @param <K> the type of {@link #key()} component contained in the
|
||||||
|
* sub-ClassLoaderValue.
|
||||||
|
*/
|
||||||
|
final class Sub<K> extends AbstractClassLoaderValue<Sub<K>, V> {
|
||||||
|
|
||||||
|
private final K key;
|
||||||
|
|
||||||
|
Sub(K key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the parent ClassLoaderValue this sub-ClassLoaderValue
|
||||||
|
* has been {@link #sub(Object) derived} from.
|
||||||
|
*/
|
||||||
|
public AbstractClassLoaderValue<CLV, V> parent() {
|
||||||
|
return AbstractClassLoaderValue.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the key component of this sub-ClassLoaderValue.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public K key() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sub-ClassLoaderValue is a descendant of given {@code clv} if it is
|
||||||
|
* either equal to it or if its {@link #parent() parent} is a
|
||||||
|
* descendant of given {@code clv}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv) {
|
||||||
|
return equals(Objects.requireNonNull(clv)) ||
|
||||||
|
parent().isEqualOrDescendantOf(clv);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof Sub)) return false;
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Sub<?> that = (Sub<?>) o;
|
||||||
|
return this.parent().equals(that.parent()) &&
|
||||||
|
Objects.equals(this.key, that.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 31 * parent().hashCode() +
|
||||||
|
Objects.hashCode(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code 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 General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.lang.reflect;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* root-ClassLoaderValue. Each instance defines a separate namespace for
|
||||||
|
* associated values.
|
||||||
|
* <p>
|
||||||
|
* ClassLoaderValue allows associating a
|
||||||
|
* {@link #computeIfAbsent(ClassLoader, BiFunction) computed} non-null value with
|
||||||
|
* a {@code (ClassLoader, keys...)} tuple. The associated value, as well as the
|
||||||
|
* keys are strongly reachable from the associated ClassLoader so care should be
|
||||||
|
* taken to use such keys and values that only reference types resolvable from
|
||||||
|
* the associated ClassLoader. Failing that, ClassLoader leaks are inevitable.
|
||||||
|
* <p>
|
||||||
|
* Example usage:
|
||||||
|
* <pre>{@code
|
||||||
|
* // create a root instance which represents a namespace and declares the type of
|
||||||
|
* // associated values (Class instances in this example)
|
||||||
|
* static final ClassLoaderValue<Class<?>> proxyClasses = new ClassLoaderValue<>();
|
||||||
|
*
|
||||||
|
* // create a compound key composed of a Module and a list of interfaces
|
||||||
|
* Module module = ...;
|
||||||
|
* List<Class<?>> interfaces = ...;
|
||||||
|
* ClassLoaderValue<Class<?>>.Sub<Module>.Sub<List<Class<?>>> key =
|
||||||
|
* proxyClasses.sub(module).sub(interfaces);
|
||||||
|
*
|
||||||
|
* // use the compound key together with ClassLoader to lazily associate
|
||||||
|
* // the value with tuple (loader, module, interfaces) and return it
|
||||||
|
* ClassLoader loader = ...;
|
||||||
|
* Class<?> proxyClass = key.computeIfAbsent(loader, (ld, ky) -> {
|
||||||
|
* List<Class<?>> intfcs = ky.key();
|
||||||
|
* Module m = ky.parent().key();
|
||||||
|
* Class<?> clazz = defineProxyClass(ld, m, intfcs);
|
||||||
|
* return clazz;
|
||||||
|
* });
|
||||||
|
* }</pre>
|
||||||
|
* <p>
|
||||||
|
* {@code classLoaderValue.<operation>(classLoader, ...)} represents an operation
|
||||||
|
* to {@link #get}, {@link #putIfAbsent}, {@link #computeIfAbsent} or {@link #remove}
|
||||||
|
* a value associated with a (classLoader, classLoaderValue) tuple. ClassLoader
|
||||||
|
* instances and root-{@link ClassLoaderValue} instances are compared using
|
||||||
|
* identity equality while {@link Sub sub}-ClassLoaderValue instances define
|
||||||
|
* {@link #equals(Object) equality} in terms of equality of its
|
||||||
|
* {@link Sub#parent() parent} ClassLoaderValue and its
|
||||||
|
* {@link #key() key} component.
|
||||||
|
*
|
||||||
|
* @param <V> the type of value(s) associated with the root-ClassLoaderValue and
|
||||||
|
* all its {@link #sub(Object) descendants}.
|
||||||
|
* @author Peter Levart
|
||||||
|
* @since 9
|
||||||
|
*/
|
||||||
|
final class ClassLoaderValue<V>
|
||||||
|
extends AbstractClassLoaderValue<ClassLoaderValue<V>, V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs new root-ClassLoaderValue representing its own namespace.
|
||||||
|
*/
|
||||||
|
public ClassLoaderValue() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the key component of this root-ClassLoaderValue (itself).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ClassLoaderValue<V> key() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* root-ClassLoaderValue can only be equal to itself and has no predecessors.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv) {
|
||||||
|
return equals(Objects.requireNonNull(clv));
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
package java.lang.reflect;
|
package java.lang.reflect;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -39,10 +38,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.WeakHashMap;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@ -283,6 +280,13 @@ public class Proxy implements java.io.Serializable {
|
|||||||
private static final Class<?>[] constructorParams =
|
private static final Class<?>[] constructorParams =
|
||||||
{ InvocationHandler.class };
|
{ InvocationHandler.class };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a cache of proxy constructors with
|
||||||
|
* {@link Constructor#setAccessible(boolean) accessible} flag already set
|
||||||
|
*/
|
||||||
|
private static final ClassLoaderValue<Constructor<?>> proxyCache =
|
||||||
|
new ClassLoaderValue<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the invocation handler for this proxy instance.
|
* the invocation handler for this proxy instance.
|
||||||
* @serial
|
* @serial
|
||||||
@ -361,14 +365,55 @@ public class Proxy implements java.io.Serializable {
|
|||||||
Class<?>... interfaces)
|
Class<?>... interfaces)
|
||||||
throws IllegalArgumentException
|
throws IllegalArgumentException
|
||||||
{
|
{
|
||||||
final List<Class<?>> intfs = List.of(interfaces); // interfaces cloned
|
Class<?> caller = System.getSecurityManager() == null
|
||||||
final SecurityManager sm = System.getSecurityManager();
|
? null
|
||||||
final Class<?> caller = Reflection.getCallerClass();
|
: Reflection.getCallerClass();
|
||||||
if (sm != null) {
|
|
||||||
checkProxyAccess(caller, loader, intfs);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ProxyBuilder(loader, intfs).build();
|
return getProxyConstructor(caller, loader, interfaces)
|
||||||
|
.getDeclaringClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@code Constructor} object of a proxy class that takes a
|
||||||
|
* single argument of type {@link InvocationHandler}, given a class loader
|
||||||
|
* and an array of interfaces. The returned constructor will have the
|
||||||
|
* {@link Constructor#setAccessible(boolean) accessible} flag already set.
|
||||||
|
*
|
||||||
|
* @param caller passed from a public-facing @CallerSensitive method if
|
||||||
|
* SecurityManager is set or {@code null} if there's no
|
||||||
|
* SecurityManager
|
||||||
|
* @param loader the class loader to define the proxy class
|
||||||
|
* @param interfaces the list of interfaces for the proxy class
|
||||||
|
* to implement
|
||||||
|
* @return a Constructor of the proxy class taking single
|
||||||
|
* {@code InvocationHandler} parameter
|
||||||
|
*/
|
||||||
|
private static Constructor<?> getProxyConstructor(Class<?> caller,
|
||||||
|
ClassLoader loader,
|
||||||
|
Class<?>... interfaces)
|
||||||
|
{
|
||||||
|
// optimization for single interface
|
||||||
|
if (interfaces.length == 1) {
|
||||||
|
Class<?> intf = interfaces[0];
|
||||||
|
if (caller != null) {
|
||||||
|
checkProxyAccess(caller, loader, intf);
|
||||||
|
}
|
||||||
|
return proxyCache.sub(intf).computeIfAbsent(
|
||||||
|
loader,
|
||||||
|
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// interfaces cloned
|
||||||
|
final Class<?>[] intfsArray = interfaces.clone();
|
||||||
|
if (caller != null) {
|
||||||
|
checkProxyAccess(caller, loader, intfsArray);
|
||||||
|
}
|
||||||
|
final List<Class<?>> intfs = Arrays.asList(intfsArray);
|
||||||
|
return proxyCache.sub(intfs).computeIfAbsent(
|
||||||
|
loader,
|
||||||
|
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -391,7 +436,7 @@ public class Proxy implements java.io.Serializable {
|
|||||||
*/
|
*/
|
||||||
private static void checkProxyAccess(Class<?> caller,
|
private static void checkProxyAccess(Class<?> caller,
|
||||||
ClassLoader loader,
|
ClassLoader loader,
|
||||||
List<Class<?>> interfaces)
|
Class<?> ... interfaces)
|
||||||
{
|
{
|
||||||
SecurityManager sm = System.getSecurityManager();
|
SecurityManager sm = System.getSecurityManager();
|
||||||
if (sm != null) {
|
if (sm != null) {
|
||||||
@ -399,147 +444,18 @@ public class Proxy implements java.io.Serializable {
|
|||||||
if (VM.isSystemDomainLoader(loader) && !VM.isSystemDomainLoader(ccl)) {
|
if (VM.isSystemDomainLoader(loader) && !VM.isSystemDomainLoader(ccl)) {
|
||||||
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
|
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
|
||||||
}
|
}
|
||||||
ReflectUtil.checkProxyPackageAccess(ccl, interfaces.toArray(EMPTY_CLASS_ARRAY));
|
ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* a key used for proxy class with 0 implemented interfaces
|
|
||||||
*/
|
|
||||||
private static final Object key0 = new Object();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Key1 and Key2 are optimized for the common use of dynamic proxies
|
|
||||||
* that implement 1 or 2 interfaces.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* a key used for proxy class with 1 implemented interface
|
|
||||||
*/
|
|
||||||
private static final class Key1 extends WeakReference<Class<?>> {
|
|
||||||
private final int hash;
|
|
||||||
|
|
||||||
Key1(Class<?> intf) {
|
|
||||||
super(intf);
|
|
||||||
this.hash = intf.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
Class<?> intf;
|
|
||||||
return this == obj ||
|
|
||||||
obj != null &&
|
|
||||||
obj.getClass() == Key1.class &&
|
|
||||||
(intf = get()) != null &&
|
|
||||||
intf == ((Key1) obj).get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* a key used for proxy class with 2 implemented interfaces
|
|
||||||
*/
|
|
||||||
private static final class Key2 extends WeakReference<Class<?>> {
|
|
||||||
private final int hash;
|
|
||||||
private final WeakReference<Class<?>> ref2;
|
|
||||||
|
|
||||||
Key2(Class<?> intf1, Class<?> intf2) {
|
|
||||||
super(intf1);
|
|
||||||
hash = 31 * intf1.hashCode() + intf2.hashCode();
|
|
||||||
ref2 = new WeakReference<>(intf2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
Class<?> intf1, intf2;
|
|
||||||
return this == obj ||
|
|
||||||
obj != null &&
|
|
||||||
obj.getClass() == Key2.class &&
|
|
||||||
(intf1 = get()) != null &&
|
|
||||||
intf1 == ((Key2) obj).get() &&
|
|
||||||
(intf2 = ref2.get()) != null &&
|
|
||||||
intf2 == ((Key2) obj).ref2.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* a key used for proxy class with any number of implemented interfaces
|
|
||||||
* (used here for 3 or more only)
|
|
||||||
*/
|
|
||||||
private static final class KeyX {
|
|
||||||
private final int hash;
|
|
||||||
private final WeakReference<Class<?>>[] refs;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
KeyX(List<Class<?>> interfaces) {
|
|
||||||
hash = Arrays.hashCode(interfaces.toArray());
|
|
||||||
refs = (WeakReference<Class<?>>[])new WeakReference<?>[interfaces.size()];
|
|
||||||
int i = 0;
|
|
||||||
for (Class<?> intf : interfaces) {
|
|
||||||
refs[i++] = new WeakReference<>(intf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return this == obj ||
|
|
||||||
obj != null &&
|
|
||||||
obj.getClass() == KeyX.class &&
|
|
||||||
equals(refs, ((KeyX) obj).refs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean equals(WeakReference<Class<?>>[] refs1,
|
|
||||||
WeakReference<Class<?>>[] refs2) {
|
|
||||||
if (refs1.length != refs2.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < refs1.length; i++) {
|
|
||||||
Class<?> intf = refs1[i].get();
|
|
||||||
if (intf == null || intf != refs2[i].get()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that maps an array of interfaces to an optimal key where
|
* Builder for a proxy class.
|
||||||
* Class objects representing interfaces are weakly referenced.
|
*
|
||||||
|
* If the module is not specified in this ProxyBuilder constructor,
|
||||||
|
* it will map from the given loader and interfaces to the module
|
||||||
|
* in which the proxy class will be defined.
|
||||||
*/
|
*/
|
||||||
private static final class KeyFactory<T>
|
private static final class ProxyBuilder {
|
||||||
implements BiFunction<T, List<Class<?>>, Object>
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public Object apply(T t, List<Class<?>> interfaces) {
|
|
||||||
switch (interfaces.size()) {
|
|
||||||
case 1: return new Key1(interfaces.get(0)); // the most frequent
|
|
||||||
case 2: return new Key2(interfaces.get(0), interfaces.get(1));
|
|
||||||
case 0: return key0;
|
|
||||||
default: return new KeyX(interfaces);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A factory function that generates, defines and returns the proxy class
|
|
||||||
* given the ClassLoader and array of interfaces.
|
|
||||||
*/
|
|
||||||
private static final class ProxyClassFactory {
|
|
||||||
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||||
|
|
||||||
// prefix for all proxy class names
|
// prefix for all proxy class names
|
||||||
@ -548,6 +464,10 @@ public class Proxy implements java.io.Serializable {
|
|||||||
// next number to use for generation of unique proxy class names
|
// next number to use for generation of unique proxy class names
|
||||||
private static final AtomicLong nextUniqueNumber = new AtomicLong();
|
private static final AtomicLong nextUniqueNumber = new AtomicLong();
|
||||||
|
|
||||||
|
// a reverse cache of defined proxy classes
|
||||||
|
private static final ClassLoaderValue<Boolean> reverseProxyCache =
|
||||||
|
new ClassLoaderValue<>();
|
||||||
|
|
||||||
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
|
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
|
||||||
String proxyPkg = null; // package to define proxy class in
|
String proxyPkg = null; // package to define proxy class in
|
||||||
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
|
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
|
||||||
@ -601,8 +521,11 @@ public class Proxy implements java.io.Serializable {
|
|||||||
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
|
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
|
||||||
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
|
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
|
||||||
try {
|
try {
|
||||||
return UNSAFE.defineClass(proxyName, proxyClassFile, 0, proxyClassFile.length,
|
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
|
||||||
loader, null);
|
0, proxyClassFile.length,
|
||||||
|
loader, null);
|
||||||
|
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
|
||||||
|
return pc;
|
||||||
} catch (ClassFormatError e) {
|
} catch (ClassFormatError e) {
|
||||||
/*
|
/*
|
||||||
* A ClassFormatError here means that (barring bugs in the
|
* A ClassFormatError here means that (barring bugs in the
|
||||||
@ -616,35 +539,14 @@ public class Proxy implements java.io.Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if the given class is a proxy class
|
* Test if given class is a class defined by
|
||||||
|
* {@link #defineProxyClass(Module, List)}
|
||||||
*/
|
*/
|
||||||
static boolean isProxyClass(Class<?> c) {
|
static boolean isProxyClass(Class<?> c) {
|
||||||
return proxyCache.containsValue(c);
|
return Objects.equals(reverseProxyCache.sub(c).get(c.getClassLoader()),
|
||||||
|
Boolean.TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the proxy class. It will return the cached proxy class
|
|
||||||
* if exists; otherwise, it will create the proxy class and store in
|
|
||||||
* the cache.
|
|
||||||
*/
|
|
||||||
static Class<?> get(Module module, List<Class<?>> interfaces) {
|
|
||||||
return proxyCache.get(module, interfaces);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a cache of proxy classes in the named and unnamed module
|
|
||||||
*/
|
|
||||||
private static final WeakCache<Module, List<Class<?>>, Class<?>> proxyCache =
|
|
||||||
new WeakCache<>(new KeyFactory<Module>(),
|
|
||||||
new BiFunction<Module, List<Class<?>>, Class<?>>() {
|
|
||||||
@Override
|
|
||||||
public Class<?> apply(Module m, List<Class<?>> interfaces) {
|
|
||||||
Objects.requireNonNull(m);
|
|
||||||
return defineProxyClass(m, interfaces);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
private static boolean isExportedType(Class<?> c) {
|
private static boolean isExportedType(Class<?> c) {
|
||||||
String pn = c.getPackageName();
|
String pn = c.getPackageName();
|
||||||
return Modifier.isPublic(c.getModifiers()) && c.getModule().isExported(pn);
|
return Modifier.isPublic(c.getModifiers()) && c.getModule().isExported(pn);
|
||||||
@ -685,25 +587,18 @@ public class Proxy implements java.io.Serializable {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
private static final boolean isDebug() {
|
private static boolean isDebug() {
|
||||||
return !DEBUG.isEmpty();
|
return !DEBUG.isEmpty();
|
||||||
}
|
}
|
||||||
private static final boolean isDebug(String flag) {
|
private static boolean isDebug(String flag) {
|
||||||
return DEBUG.equals(flag);
|
return DEBUG.equals(flag);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// ProxyBuilder instance members start here....
|
||||||
* Builder for a proxy class.
|
|
||||||
*
|
private final ClassLoader loader;
|
||||||
* If the module is not specified in this ProxyBuilder constructor,
|
private final List<Class<?>> interfaces;
|
||||||
* it will map from the given loader and interfaces to the module
|
private final Module module;
|
||||||
* in which the proxy class will be defined.
|
|
||||||
*/
|
|
||||||
private static final class ProxyBuilder {
|
|
||||||
final ClassLoader loader;
|
|
||||||
final List<Class<?>> interfaces;
|
|
||||||
final Module module;
|
|
||||||
ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
|
ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
|
||||||
if (!VM.isModuleSystemInited()) {
|
if (!VM.isModuleSystemInited()) {
|
||||||
throw new InternalError("Proxy is not supported until module system is fully initialzed");
|
throw new InternalError("Proxy is not supported until module system is fully initialzed");
|
||||||
@ -723,16 +618,34 @@ public class Proxy implements java.io.Serializable {
|
|||||||
assert getLoader(module) == loader;
|
assert getLoader(module) == loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProxyBuilder(ClassLoader loader, Class<?> intf) {
|
||||||
|
this(loader, Collections.singletonList(intf));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a proxy class. If the target module does not have any
|
* Generate a proxy class and return its proxy Constructor with
|
||||||
|
* accessible flag already set. If the target module does not have access
|
||||||
* to any interface types, IllegalAccessError will be thrown by the VM
|
* to any interface types, IllegalAccessError will be thrown by the VM
|
||||||
* at defineClass time.
|
* at defineClass time.
|
||||||
*
|
*
|
||||||
* Must call the checkProxyAccess method to perform permission checks
|
* Must call the checkProxyAccess method to perform permission checks
|
||||||
* before calling this.
|
* before calling this.
|
||||||
*/
|
*/
|
||||||
Class<?> build() {
|
Constructor<?> build() {
|
||||||
return ProxyClassFactory.get(module, interfaces);
|
Class<?> proxyClass = defineProxyClass(module, interfaces);
|
||||||
|
final Constructor<?> cons;
|
||||||
|
try {
|
||||||
|
cons = proxyClass.getConstructor(constructorParams);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new InternalError(e.toString(), e);
|
||||||
|
}
|
||||||
|
AccessController.doPrivileged(new PrivilegedAction<Void>() {
|
||||||
|
public Void run() {
|
||||||
|
cons.setAccessible(true);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return cons;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -742,9 +655,9 @@ public class Proxy implements java.io.Serializable {
|
|||||||
* @throws IllegalArgumentException if it violates the restrictions specified
|
* @throws IllegalArgumentException if it violates the restrictions specified
|
||||||
* in {@link Proxy#newProxyInstance}
|
* in {@link Proxy#newProxyInstance}
|
||||||
*/
|
*/
|
||||||
static void validateProxyInterfaces(ClassLoader loader,
|
private static void validateProxyInterfaces(ClassLoader loader,
|
||||||
List<Class<?>> interfaces,
|
List<Class<?>> interfaces,
|
||||||
Set<Class<?>> refTypes)
|
Set<Class<?>> refTypes)
|
||||||
{
|
{
|
||||||
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.size());
|
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.size());
|
||||||
for (Class<?> intf : interfaces) {
|
for (Class<?> intf : interfaces) {
|
||||||
@ -779,10 +692,11 @@ public class Proxy implements java.io.Serializable {
|
|||||||
* Returns all types referenced by all public method signatures of
|
* Returns all types referenced by all public method signatures of
|
||||||
* the proxy interfaces
|
* the proxy interfaces
|
||||||
*/
|
*/
|
||||||
static Set<Class<?>> referencedTypes(ClassLoader loader, List<Class<?>> interfaces) {
|
private static Set<Class<?>> referencedTypes(ClassLoader loader,
|
||||||
|
List<Class<?>> interfaces) {
|
||||||
return interfaces.stream()
|
return interfaces.stream()
|
||||||
.flatMap(intf -> Stream.of(intf.getMethods())
|
.flatMap(intf -> Stream.of(intf.getMethods())
|
||||||
.flatMap(m -> methodRefTypes(m))
|
.flatMap(ProxyBuilder::methodRefTypes)
|
||||||
.map(ProxyBuilder::getElementType)
|
.map(ProxyBuilder::getElementType)
|
||||||
.filter(t -> !t.isPrimitive()))
|
.filter(t -> !t.isPrimitive()))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
@ -792,11 +706,11 @@ public class Proxy implements java.io.Serializable {
|
|||||||
* Extracts all types referenced on a method signature including
|
* Extracts all types referenced on a method signature including
|
||||||
* its return type, parameter types, and exception types.
|
* its return type, parameter types, and exception types.
|
||||||
*/
|
*/
|
||||||
static Stream<Class<?>> methodRefTypes(Method m) {
|
private static Stream<Class<?>> methodRefTypes(Method m) {
|
||||||
return Stream.of(new Class<?>[] { m.getReturnType() },
|
return Stream.of(new Class<?>[] { m.getReturnType() },
|
||||||
m.getParameterTypes(),
|
m.getParameterTypes(),
|
||||||
m.getExceptionTypes())
|
m.getExceptionTypes())
|
||||||
.flatMap(a -> Stream.of(a));
|
.flatMap(Stream::of);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -813,7 +727,9 @@ public class Proxy implements java.io.Serializable {
|
|||||||
* package. Reads edge and qualified exports are added for
|
* package. Reads edge and qualified exports are added for
|
||||||
* dynamic module to access.
|
* dynamic module to access.
|
||||||
*/
|
*/
|
||||||
static Module mapToModule(ClassLoader loader, List<Class<?>> interfaces, Set<Class<?>> refTypes) {
|
private static Module mapToModule(ClassLoader loader,
|
||||||
|
List<Class<?>> interfaces,
|
||||||
|
Set<Class<?>> refTypes) {
|
||||||
Map<Class<?>, Module> modulePrivateTypes = new HashMap<>();
|
Map<Class<?>, Module> modulePrivateTypes = new HashMap<>();
|
||||||
Map<Class<?>, Module> packagePrivateTypes = new HashMap<>();
|
Map<Class<?>, Module> packagePrivateTypes = new HashMap<>();
|
||||||
for (Class<?> intf : interfaces) {
|
for (Class<?> intf : interfaces) {
|
||||||
@ -884,10 +800,9 @@ public class Proxy implements java.io.Serializable {
|
|||||||
Set<Class<?>> visited = new HashSet<>();
|
Set<Class<?>> visited = new HashSet<>();
|
||||||
while (!deque.isEmpty()) {
|
while (!deque.isEmpty()) {
|
||||||
Class<?> c = deque.poll();
|
Class<?> c = deque.poll();
|
||||||
if (visited.contains(c)) {
|
if (!visited.add(c)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
visited.add(c);
|
|
||||||
ensureAccess(target, c);
|
ensureAccess(target, c);
|
||||||
|
|
||||||
// add all superinterfaces
|
// add all superinterfaces
|
||||||
@ -906,7 +821,7 @@ public class Proxy implements java.io.Serializable {
|
|||||||
/*
|
/*
|
||||||
* Ensure the given module can access the given class.
|
* Ensure the given module can access the given class.
|
||||||
*/
|
*/
|
||||||
static void ensureAccess(Module target, Class<?> c) {
|
private static void ensureAccess(Module target, Class<?> c) {
|
||||||
Module m = c.getModule();
|
Module m = c.getModule();
|
||||||
// add read edge and qualified export for the target module to access
|
// add read edge and qualified export for the target module to access
|
||||||
if (!target.canRead(m)) {
|
if (!target.canRead(m)) {
|
||||||
@ -921,7 +836,7 @@ public class Proxy implements java.io.Serializable {
|
|||||||
/*
|
/*
|
||||||
* Ensure the given class is visible to the class loader.
|
* Ensure the given class is visible to the class loader.
|
||||||
*/
|
*/
|
||||||
static void ensureVisible(ClassLoader ld, Class<?> c) {
|
private static void ensureVisible(ClassLoader ld, Class<?> c) {
|
||||||
Class<?> type = null;
|
Class<?> type = null;
|
||||||
try {
|
try {
|
||||||
type = Class.forName(c.getName(), false, ld);
|
type = Class.forName(c.getName(), false, ld);
|
||||||
@ -933,7 +848,7 @@ public class Proxy implements java.io.Serializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Class<?> getElementType(Class<?> type) {
|
private static Class<?> getElementType(Class<?> type) {
|
||||||
Class<?> e = type;
|
Class<?> e = type;
|
||||||
while (e.isArray()) {
|
while (e.isArray()) {
|
||||||
e = e.getComponentType();
|
e = e.getComponentType();
|
||||||
@ -941,7 +856,8 @@ public class Proxy implements java.io.Serializable {
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final WeakHashMap<ClassLoader, Module> dynProxyModules = new WeakHashMap<>();
|
private static final ClassLoaderValue<Module> dynProxyModules =
|
||||||
|
new ClassLoaderValue<>();
|
||||||
private static final AtomicInteger counter = new AtomicInteger();
|
private static final AtomicInteger counter = new AtomicInteger();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -950,12 +866,12 @@ public class Proxy implements java.io.Serializable {
|
|||||||
*
|
*
|
||||||
* Each class loader will have one dynamic module.
|
* Each class loader will have one dynamic module.
|
||||||
*/
|
*/
|
||||||
static Module getDynamicModule(ClassLoader loader) {
|
private static Module getDynamicModule(ClassLoader loader) {
|
||||||
return dynProxyModules.computeIfAbsent(loader, ld -> {
|
return dynProxyModules.computeIfAbsent(loader, (ld, clv) -> {
|
||||||
// create a dynamic module and setup module access
|
// create a dynamic module and setup module access
|
||||||
String mn = "jdk.proxy" + counter.incrementAndGet();
|
String mn = "jdk.proxy" + counter.incrementAndGet();
|
||||||
String pn = PROXY_PACKAGE_PREFIX + "." + mn;
|
String pn = PROXY_PACKAGE_PREFIX + "." + mn;
|
||||||
Module m = Modules.defineModule(loader, mn, Collections.singleton(pn));
|
Module m = Modules.defineModule(ld, mn, Collections.singleton(pn));
|
||||||
Modules.addReads(m, Proxy.class.getModule());
|
Modules.addReads(m, Proxy.class.getModule());
|
||||||
// java.base to create proxy instance
|
// java.base to create proxy instance
|
||||||
Modules.addExports(m, pn, Object.class.getModule());
|
Modules.addExports(m, pn, Object.class.getModule());
|
||||||
@ -1062,40 +978,31 @@ public class Proxy implements java.io.Serializable {
|
|||||||
InvocationHandler h) {
|
InvocationHandler h) {
|
||||||
Objects.requireNonNull(h);
|
Objects.requireNonNull(h);
|
||||||
|
|
||||||
final List<Class<?>> intfs = List.of(interfaces); // interfaces cloned
|
final Class<?> caller = System.getSecurityManager() == null
|
||||||
final SecurityManager sm = System.getSecurityManager();
|
? null
|
||||||
final Class<?> caller = Reflection.getCallerClass();
|
: Reflection.getCallerClass();
|
||||||
if (sm != null) {
|
|
||||||
checkProxyAccess(caller, loader, intfs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Look up or generate the designated proxy class.
|
* Look up or generate the designated proxy class and its constructor.
|
||||||
*/
|
*/
|
||||||
Class<?> cl = new ProxyBuilder(loader, intfs).build();
|
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
|
||||||
|
|
||||||
return newProxyInstance(cl, caller, h);
|
return newProxyInstance(caller, cons, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object newProxyInstance(Class<?> proxyClass, Class<?> caller, InvocationHandler h) {
|
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
|
||||||
|
Constructor<?> cons,
|
||||||
|
InvocationHandler h) {
|
||||||
/*
|
/*
|
||||||
* Invoke its constructor with the designated invocation handler.
|
* Invoke its constructor with the designated invocation handler.
|
||||||
*/
|
*/
|
||||||
try {
|
try {
|
||||||
final SecurityManager sm = System.getSecurityManager();
|
if (caller != null) {
|
||||||
if (sm != null) {
|
checkNewProxyPermission(caller, cons.getDeclaringClass());
|
||||||
checkNewProxyPermission(caller, proxyClass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final Constructor<?> cons = proxyClass.getConstructor(constructorParams);
|
|
||||||
AccessController.doPrivileged(new PrivilegedAction<Void>() {
|
|
||||||
public Void run() {
|
|
||||||
cons.setAccessible(true);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return cons.newInstance(new Object[]{h});
|
return cons.newInstance(new Object[]{h});
|
||||||
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException e) {
|
} catch (IllegalAccessException | InstantiationException e) {
|
||||||
throw new InternalError(e.toString(), e);
|
throw new InternalError(e.toString(), e);
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
Throwable t = e.getCause();
|
Throwable t = e.getCause();
|
||||||
@ -1150,7 +1057,7 @@ public class Proxy implements java.io.Serializable {
|
|||||||
* @throws NullPointerException if {@code cl} is {@code null}
|
* @throws NullPointerException if {@code cl} is {@code null}
|
||||||
*/
|
*/
|
||||||
public static boolean isProxyClass(Class<?> cl) {
|
public static boolean isProxyClass(Class<?> cl) {
|
||||||
return Proxy.class.isAssignableFrom(cl) && ProxyClassFactory.isProxyClass(cl);
|
return Proxy.class.isAssignableFrom(cl) && ProxyBuilder.isProxyClass(cl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,381 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
|
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
||||||
*
|
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License version 2 only, as
|
|
||||||
* published by the Free Software Foundation. Oracle designates this
|
|
||||||
* particular file as subject to the "Classpath" exception as provided
|
|
||||||
* by Oracle in the LICENSE file that accompanied this code.
|
|
||||||
*
|
|
||||||
* This code 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 General Public License
|
|
||||||
* version 2 for more details (a copy is included in the LICENSE file that
|
|
||||||
* accompanied this code).
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License version
|
|
||||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
||||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*
|
|
||||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
||||||
* or visit www.oracle.com if you need additional information or have any
|
|
||||||
* questions.
|
|
||||||
*/
|
|
||||||
package java.lang.reflect;
|
|
||||||
|
|
||||||
import java.lang.ref.ReferenceQueue;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache mapping pairs of {@code (key, sub-key) -> value}. Keys and values are
|
|
||||||
* weakly but sub-keys are strongly referenced. Keys are passed directly to
|
|
||||||
* {@link #get} method which also takes a {@code parameter}. Sub-keys are
|
|
||||||
* calculated from keys and parameters using the {@code subKeyFactory} function
|
|
||||||
* passed to the constructor. Values are calculated from keys and parameters
|
|
||||||
* using the {@code valueFactory} function passed to the constructor.
|
|
||||||
* Keys can be {@code null} and are compared by identity while sub-keys returned by
|
|
||||||
* {@code subKeyFactory} or values returned by {@code valueFactory}
|
|
||||||
* can not be null. Sub-keys are compared using their {@link #equals} method.
|
|
||||||
* Entries are expunged from cache lazily on each invocation to {@link #get},
|
|
||||||
* {@link #containsValue} or {@link #size} methods when the WeakReferences to
|
|
||||||
* keys are cleared. Cleared WeakReferences to individual values don't cause
|
|
||||||
* expunging, but such entries are logically treated as non-existent and
|
|
||||||
* trigger re-evaluation of {@code valueFactory} on request for their
|
|
||||||
* key/subKey.
|
|
||||||
*
|
|
||||||
* @author Peter Levart
|
|
||||||
* @param <K> type of keys
|
|
||||||
* @param <P> type of parameters
|
|
||||||
* @param <V> type of values
|
|
||||||
*/
|
|
||||||
final class WeakCache<K, P, V> {
|
|
||||||
|
|
||||||
private final ReferenceQueue<K> refQueue
|
|
||||||
= new ReferenceQueue<>();
|
|
||||||
// the key type is Object for supporting null key
|
|
||||||
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
|
|
||||||
= new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
|
|
||||||
= new ConcurrentHashMap<>();
|
|
||||||
private final BiFunction<K, P, ?> subKeyFactory;
|
|
||||||
private final BiFunction<K, P, V> valueFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct an instance of {@code WeakCache}
|
|
||||||
*
|
|
||||||
* @param subKeyFactory a function mapping a pair of
|
|
||||||
* {@code (key, parameter) -> sub-key}
|
|
||||||
* @param valueFactory a function mapping a pair of
|
|
||||||
* {@code (key, parameter) -> value}
|
|
||||||
* @throws NullPointerException if {@code subKeyFactory} or
|
|
||||||
* {@code valueFactory} is null.
|
|
||||||
*/
|
|
||||||
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
|
|
||||||
BiFunction<K, P, V> valueFactory) {
|
|
||||||
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
|
|
||||||
this.valueFactory = Objects.requireNonNull(valueFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look-up the value through the cache. This always evaluates the
|
|
||||||
* {@code subKeyFactory} function and optionally evaluates
|
|
||||||
* {@code valueFactory} function if there is no entry in the cache for given
|
|
||||||
* pair of (key, subKey) or the entry has already been cleared.
|
|
||||||
*
|
|
||||||
* @param key possibly null key
|
|
||||||
* @param parameter parameter used together with key to create sub-key and
|
|
||||||
* value (should not be null)
|
|
||||||
* @return the cached value (never null)
|
|
||||||
* @throws NullPointerException if {@code parameter} passed in or
|
|
||||||
* {@code sub-key} calculated by
|
|
||||||
* {@code subKeyFactory} or {@code value}
|
|
||||||
* calculated by {@code valueFactory} is null.
|
|
||||||
*/
|
|
||||||
public V get(K key, P parameter) {
|
|
||||||
Objects.requireNonNull(parameter);
|
|
||||||
|
|
||||||
expungeStaleEntries();
|
|
||||||
|
|
||||||
Object cacheKey = CacheKey.valueOf(key, refQueue);
|
|
||||||
|
|
||||||
// lazily install the 2nd level valuesMap for the particular cacheKey
|
|
||||||
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
|
|
||||||
if (valuesMap == null) {
|
|
||||||
ConcurrentMap<Object, Supplier<V>> oldValuesMap
|
|
||||||
= map.putIfAbsent(cacheKey,
|
|
||||||
valuesMap = new ConcurrentHashMap<>());
|
|
||||||
if (oldValuesMap != null) {
|
|
||||||
valuesMap = oldValuesMap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create subKey and retrieve the possible Supplier<V> stored by that
|
|
||||||
// subKey from valuesMap
|
|
||||||
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
|
|
||||||
Supplier<V> supplier = valuesMap.get(subKey);
|
|
||||||
Factory factory = null;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (supplier != null) {
|
|
||||||
// supplier might be a Factory or a CacheValue<V> instance
|
|
||||||
V value = supplier.get();
|
|
||||||
if (value != null) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// else no supplier in cache
|
|
||||||
// or a supplier that returned null (could be a cleared CacheValue
|
|
||||||
// or a Factory that wasn't successful in installing the CacheValue)
|
|
||||||
|
|
||||||
// lazily construct a Factory
|
|
||||||
if (factory == null) {
|
|
||||||
factory = new Factory(key, parameter, subKey, valuesMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supplier == null) {
|
|
||||||
supplier = valuesMap.putIfAbsent(subKey, factory);
|
|
||||||
if (supplier == null) {
|
|
||||||
// successfully installed Factory
|
|
||||||
supplier = factory;
|
|
||||||
}
|
|
||||||
// else retry with winning supplier
|
|
||||||
} else {
|
|
||||||
if (valuesMap.replace(subKey, supplier, factory)) {
|
|
||||||
// successfully replaced
|
|
||||||
// cleared CacheEntry / unsuccessful Factory
|
|
||||||
// with our Factory
|
|
||||||
supplier = factory;
|
|
||||||
} else {
|
|
||||||
// retry with current supplier
|
|
||||||
supplier = valuesMap.get(subKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the specified non-null value is already present in this
|
|
||||||
* {@code WeakCache}. The check is made using identity comparison regardless
|
|
||||||
* of whether value's class overrides {@link Object#equals} or not.
|
|
||||||
*
|
|
||||||
* @param value the non-null value to check
|
|
||||||
* @return true if given {@code value} is already cached
|
|
||||||
* @throws NullPointerException if value is null
|
|
||||||
*/
|
|
||||||
public boolean containsValue(V value) {
|
|
||||||
Objects.requireNonNull(value);
|
|
||||||
|
|
||||||
expungeStaleEntries();
|
|
||||||
return reverseMap.containsKey(new LookupValue<>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current number of cached entries that
|
|
||||||
* can decrease over time when keys/values are GC-ed.
|
|
||||||
*/
|
|
||||||
public int size() {
|
|
||||||
expungeStaleEntries();
|
|
||||||
return reverseMap.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") // refQueue.poll actually returns CacheKey<K>
|
|
||||||
private void expungeStaleEntries() {
|
|
||||||
CacheKey<K> cacheKey;
|
|
||||||
while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
|
|
||||||
cacheKey.expungeFrom(map, reverseMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A factory {@link Supplier} that implements the lazy synchronized
|
|
||||||
* construction of the value and installment of it into the cache.
|
|
||||||
*/
|
|
||||||
private final class Factory implements Supplier<V> {
|
|
||||||
|
|
||||||
private final K key;
|
|
||||||
private final P parameter;
|
|
||||||
private final Object subKey;
|
|
||||||
private final ConcurrentMap<Object, Supplier<V>> valuesMap;
|
|
||||||
|
|
||||||
Factory(K key, P parameter, Object subKey,
|
|
||||||
ConcurrentMap<Object, Supplier<V>> valuesMap) {
|
|
||||||
this.key = key;
|
|
||||||
this.parameter = parameter;
|
|
||||||
this.subKey = subKey;
|
|
||||||
this.valuesMap = valuesMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized V get() { // serialize access
|
|
||||||
// re-check
|
|
||||||
Supplier<V> supplier = valuesMap.get(subKey);
|
|
||||||
if (supplier != this) {
|
|
||||||
// something changed while we were waiting:
|
|
||||||
// might be that we were replaced by a CacheValue
|
|
||||||
// or were removed because of failure ->
|
|
||||||
// return null to signal WeakCache.get() to retry
|
|
||||||
// the loop
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// else still us (supplier == this)
|
|
||||||
|
|
||||||
// create new value
|
|
||||||
V value = null;
|
|
||||||
try {
|
|
||||||
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
|
|
||||||
} finally {
|
|
||||||
if (value == null) { // remove us on failure
|
|
||||||
valuesMap.remove(subKey, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// the only path to reach here is with non-null value
|
|
||||||
assert value != null;
|
|
||||||
|
|
||||||
// wrap value with CacheValue (WeakReference)
|
|
||||||
CacheValue<V> cacheValue = new CacheValue<>(value);
|
|
||||||
|
|
||||||
// try replacing us with CacheValue (this should always succeed)
|
|
||||||
if (valuesMap.replace(subKey, this, cacheValue)) {
|
|
||||||
// put also in reverseMap
|
|
||||||
reverseMap.put(cacheValue, Boolean.TRUE);
|
|
||||||
} else {
|
|
||||||
throw new AssertionError("Should not reach here");
|
|
||||||
}
|
|
||||||
|
|
||||||
// successfully replaced us with new CacheValue -> return the value
|
|
||||||
// wrapped by it
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common type of value suppliers that are holding a referent.
|
|
||||||
* The {@link #equals} and {@link #hashCode} of implementations is defined
|
|
||||||
* to compare the referent by identity.
|
|
||||||
*/
|
|
||||||
private interface Value<V> extends Supplier<V> {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optimized {@link Value} used to look-up the value in
|
|
||||||
* {@link WeakCache#containsValue} method so that we are not
|
|
||||||
* constructing the whole {@link CacheValue} just to look-up the referent.
|
|
||||||
*/
|
|
||||||
private static final class LookupValue<V> implements Value<V> {
|
|
||||||
private final V value;
|
|
||||||
|
|
||||||
LookupValue(V value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V get() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return System.identityHashCode(value); // compare by identity
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return obj == this ||
|
|
||||||
obj instanceof Value &&
|
|
||||||
this.value == ((Value<?>) obj).get(); // compare by identity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link Value} that weakly references the referent.
|
|
||||||
*/
|
|
||||||
private static final class CacheValue<V>
|
|
||||||
extends WeakReference<V> implements Value<V>
|
|
||||||
{
|
|
||||||
private final int hash;
|
|
||||||
|
|
||||||
CacheValue(V value) {
|
|
||||||
super(value);
|
|
||||||
this.hash = System.identityHashCode(value); // compare by identity
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
V value;
|
|
||||||
return obj == this ||
|
|
||||||
obj instanceof Value &&
|
|
||||||
// cleared CacheValue is only equal to itself
|
|
||||||
(value = get()) != null &&
|
|
||||||
value == ((Value<?>) obj).get(); // compare by identity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CacheKey containing a weakly referenced {@code key}. It registers
|
|
||||||
* itself with the {@code refQueue} so that it can be used to expunge
|
|
||||||
* the entry when the {@link WeakReference} is cleared.
|
|
||||||
*/
|
|
||||||
private static final class CacheKey<K> extends WeakReference<K> {
|
|
||||||
|
|
||||||
// a replacement for null keys
|
|
||||||
private static final Object NULL_KEY = new Object();
|
|
||||||
|
|
||||||
static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) {
|
|
||||||
return key == null
|
|
||||||
// null key means we can't weakly reference it,
|
|
||||||
// so we use a NULL_KEY singleton as cache key
|
|
||||||
? NULL_KEY
|
|
||||||
// non-null key requires wrapping with a WeakReference
|
|
||||||
: new CacheKey<>(key, refQueue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final int hash;
|
|
||||||
|
|
||||||
private CacheKey(K key, ReferenceQueue<K> refQueue) {
|
|
||||||
super(key, refQueue);
|
|
||||||
this.hash = System.identityHashCode(key); // compare by identity
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
K key;
|
|
||||||
return obj == this ||
|
|
||||||
obj != null &&
|
|
||||||
obj.getClass() == this.getClass() &&
|
|
||||||
// cleared CacheKey is only equal to itself
|
|
||||||
(key = this.get()) != null &&
|
|
||||||
// compare key by identity
|
|
||||||
key == ((CacheKey<K>) obj).get(); // Cast is safe from getClass check
|
|
||||||
}
|
|
||||||
|
|
||||||
void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
|
|
||||||
ConcurrentMap<?, Boolean> reverseMap) {
|
|
||||||
// removing just by key is always safe here because after a CacheKey
|
|
||||||
// is cleared and enqueue-ed it is only equal to itself
|
|
||||||
// (see equals method)...
|
|
||||||
ConcurrentMap<?, ?> valuesMap = map.remove(this);
|
|
||||||
// remove also from reverseMap if needed
|
|
||||||
if (valuesMap != null) {
|
|
||||||
for (Object cacheValue : valuesMap.values()) {
|
|
||||||
reverseMap.remove(cacheValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -39,6 +39,7 @@ import java.security.PrivilegedAction;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.jar.JarInputStream;
|
import java.util.jar.JarInputStream;
|
||||||
import java.util.jar.Manifest;
|
import java.util.jar.Manifest;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@ -68,6 +69,10 @@ public class BootLoader {
|
|||||||
// ServiceCatalog for the boot class loader
|
// ServiceCatalog for the boot class loader
|
||||||
private static final ServicesCatalog SERVICES_CATALOG = new ServicesCatalog();
|
private static final ServicesCatalog SERVICES_CATALOG = new ServicesCatalog();
|
||||||
|
|
||||||
|
// ClassLoaderValue map for boot class loader
|
||||||
|
private static final ConcurrentHashMap<?, ?> CLASS_LOADER_VALUE_MAP =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the unnamed module for the boot loader.
|
* Returns the unnamed module for the boot loader.
|
||||||
*/
|
*/
|
||||||
@ -82,6 +87,13 @@ public class BootLoader {
|
|||||||
return SERVICES_CATALOG;
|
return SERVICES_CATALOG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ClassLoaderValue map for the boot class loader.
|
||||||
|
*/
|
||||||
|
public static ConcurrentHashMap<?, ?> getClassLoaderValueMap() {
|
||||||
|
return CLASS_LOADER_VALUE_MAP;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a module with this class loader so that its classes (and
|
* Register a module with this class loader so that its classes (and
|
||||||
* resources) become visible via this class loader.
|
* resources) become visible via this class loader.
|
||||||
|
@ -33,6 +33,7 @@ import java.lang.reflect.Module;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.AccessControlContext;
|
import java.security.AccessControlContext;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jdk.internal.module.ServicesCatalog;
|
import jdk.internal.module.ServicesCatalog;
|
||||||
@ -145,6 +146,12 @@ public interface JavaLangAccess {
|
|||||||
*/
|
*/
|
||||||
ServicesCatalog createOrGetServicesCatalog(ClassLoader cl);
|
ServicesCatalog createOrGetServicesCatalog(ClassLoader cl);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ConcurrentHashMap used as a storage for ClassLoaderValue(s)
|
||||||
|
* associated with the given class loader, creating it if it doesn't already exist.
|
||||||
|
*/
|
||||||
|
ConcurrentHashMap<?, ?> createOrGetClassLoaderValueMap(ClassLoader cl);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a class loaded by the bootstrap class loader.
|
* Returns a class loaded by the bootstrap class loader.
|
||||||
*/
|
*/
|
||||||
|
35
jdk/test/java/lang/reflect/ClassLoaderValue/Driver.java
Normal file
35
jdk/test/java/lang/reflect/ClassLoaderValue/Driver.java
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code 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 General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8152115
|
||||||
|
* @summary functional and concurrency test for ClassLoaderValue
|
||||||
|
* @build java.base/java.lang.reflect.ClassLoaderValueTest
|
||||||
|
* @run main Driver
|
||||||
|
*/
|
||||||
|
public class Driver {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
java.lang.reflect.ClassLoaderValueTest.main(args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code 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 General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.lang.reflect;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional and concurrency test for ClassLoaderValue
|
||||||
|
*
|
||||||
|
* @author Peter Levart
|
||||||
|
*/
|
||||||
|
public class ClassLoaderValueTest {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
||||||
|
ClassLoaderValue[] clvs = {new ClassLoaderValue<>(),
|
||||||
|
new ClassLoaderValue<>()};
|
||||||
|
|
||||||
|
ClassLoader[] lds = {ClassLoader.getSystemClassLoader(),
|
||||||
|
ClassLoader.getPlatformClassLoader(),
|
||||||
|
null /* bootstrap class loader */};
|
||||||
|
|
||||||
|
Integer[] keys = new Integer[32];
|
||||||
|
for (int i = 0; i < keys.length; i++) {
|
||||||
|
keys[i] = i + 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (AutoCloseable cleanup = () -> {
|
||||||
|
for (ClassLoaderValue<Integer> clv : clvs) {
|
||||||
|
for (ClassLoader ld : lds) {
|
||||||
|
clv.removeAll(ld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
// 1st just one sequential pass of single-threaded validation
|
||||||
|
// which is easier to debug if it fails...
|
||||||
|
for (ClassLoaderValue<Integer> clv : clvs) {
|
||||||
|
for (ClassLoader ld : lds) {
|
||||||
|
writeValidateOps(clv, ld, keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ClassLoaderValue<Integer> clv : clvs) {
|
||||||
|
for (ClassLoader ld : lds) {
|
||||||
|
readValidateOps(clv, ld, keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2nd the same in concurrent setting that also validates
|
||||||
|
// failure-isolation between threads and data-isolation between
|
||||||
|
// regions - (ClassLoader, ClassLoaderValue) pairs - of the storage
|
||||||
|
testConcurrentIsolation(clvs, lds, keys, TimeUnit.SECONDS.toMillis(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeValidateOps(ClassLoaderValue<Integer> clv,
|
||||||
|
ClassLoader ld,
|
||||||
|
Object[] keys) {
|
||||||
|
for (int i = 0; i < keys.length; i++) {
|
||||||
|
Object k = keys[i];
|
||||||
|
Integer v1 = i;
|
||||||
|
Integer v2 = i + 333;
|
||||||
|
Integer pv;
|
||||||
|
boolean success;
|
||||||
|
|
||||||
|
pv = clv.sub(k).putIfAbsent(ld, v1);
|
||||||
|
assertEquals(pv, null);
|
||||||
|
assertEquals(clv.sub(k).get(ld), v1);
|
||||||
|
|
||||||
|
pv = clv.sub(k).putIfAbsent(ld, v2);
|
||||||
|
assertEquals(pv, v1);
|
||||||
|
assertEquals(clv.sub(k).get(ld), v1);
|
||||||
|
|
||||||
|
success = clv.sub(k).remove(ld, v2);
|
||||||
|
assertEquals(success, false);
|
||||||
|
assertEquals(clv.sub(k).get(ld), v1);
|
||||||
|
|
||||||
|
success = clv.sub(k).remove(ld, v1);
|
||||||
|
assertEquals(success, true);
|
||||||
|
assertEquals(clv.sub(k).get(ld), null);
|
||||||
|
|
||||||
|
pv = clv.sub(k).putIfAbsent(ld, v2);
|
||||||
|
assertEquals(pv, null);
|
||||||
|
assertEquals(clv.sub(k).get(ld), v2);
|
||||||
|
|
||||||
|
pv = clv.sub(k).computeIfAbsent(ld, (_ld, _clv) -> v1);
|
||||||
|
assertEquals(pv, v2);
|
||||||
|
assertEquals(clv.sub(k).get(ld), v2);
|
||||||
|
|
||||||
|
success = clv.sub(k).remove(ld, v1);
|
||||||
|
assertEquals(success, false);
|
||||||
|
assertEquals(clv.sub(k).get(ld), v2);
|
||||||
|
|
||||||
|
success = clv.sub(k).remove(ld, v2);
|
||||||
|
assertEquals(success, true);
|
||||||
|
assertEquals(clv.sub(k).get(ld), null);
|
||||||
|
|
||||||
|
pv = clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> {
|
||||||
|
try {
|
||||||
|
// nested get for same key should throw
|
||||||
|
clv_k.get(_ld);
|
||||||
|
throw new AssertionError("Unexpected code path");
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// nested putIfAbsent for same key should throw
|
||||||
|
clv_k.putIfAbsent(_ld, v1);
|
||||||
|
throw new AssertionError("Unexpected code path");
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
// nested remove for for same key and any value (even null)
|
||||||
|
// should return false
|
||||||
|
assertEquals(clv_k.remove(_ld, null), false);
|
||||||
|
assertEquals(clv_k.remove(_ld, v1), false);
|
||||||
|
assertEquals(clv_k.remove(_ld, v2), false);
|
||||||
|
try {
|
||||||
|
// nested computeIfAbsent for same key should throw
|
||||||
|
clv_k.computeIfAbsent(_ld, (__ld, _clv_k) -> v1);
|
||||||
|
throw new AssertionError("Unexpected code path");
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
// if everything above has been handled, we should succeed...
|
||||||
|
return v2;
|
||||||
|
});
|
||||||
|
// ... and the result should be reflected in the CLV
|
||||||
|
assertEquals(pv, v2);
|
||||||
|
assertEquals(clv.sub(k).get(ld), v2);
|
||||||
|
|
||||||
|
success = clv.sub(k).remove(ld, v2);
|
||||||
|
assertEquals(success, true);
|
||||||
|
assertEquals(clv.sub(k).get(ld), null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
});
|
||||||
|
throw new AssertionError("Unexpected code path");
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
assertEquals(clv.sub(k).get(ld), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readValidateOps(ClassLoaderValue<Integer> clv,
|
||||||
|
ClassLoader ld,
|
||||||
|
Object[] keys) {
|
||||||
|
for (int i = 0; i < keys.length; i++) {
|
||||||
|
Object k = keys[i];
|
||||||
|
Integer v1 = i;
|
||||||
|
Integer v2 = i + 333;
|
||||||
|
Integer rv = clv.sub(k).get(ld);
|
||||||
|
if (!(rv == null || rv.equals(v1) || rv.equals(v2))) {
|
||||||
|
throw new AssertionError("Unexpected value: " + rv +
|
||||||
|
", expected one of: null, " + v1 + ", " + v2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testConcurrentIsolation(ClassLoaderValue<Integer>[] clvs,
|
||||||
|
ClassLoader[] lds,
|
||||||
|
Object[] keys,
|
||||||
|
long millisRuntime) {
|
||||||
|
ExecutorService exe = Executors.newCachedThreadPool();
|
||||||
|
List<Future<?>> futures = new ArrayList<>();
|
||||||
|
AtomicBoolean stop = new AtomicBoolean();
|
||||||
|
for (ClassLoaderValue<Integer> clv : clvs) {
|
||||||
|
for (ClassLoader ld : lds) {
|
||||||
|
// submit a task that exercises a mix of modifying
|
||||||
|
// and reading-validating operations in an isolated
|
||||||
|
// part of the storage. If isolation is violated,
|
||||||
|
// validation operations are expected to fail.
|
||||||
|
futures.add(exe.submit(() -> {
|
||||||
|
do {
|
||||||
|
writeValidateOps(clv, ld, keys);
|
||||||
|
} while (!stop.get());
|
||||||
|
}));
|
||||||
|
// submit a task that just reads from the same part of
|
||||||
|
// the storage as above task. It should not disturb
|
||||||
|
// above task in any way and this task should never
|
||||||
|
// exhibit any failure although above task produces
|
||||||
|
// regular failures during lazy computation
|
||||||
|
futures.add(exe.submit(() -> {
|
||||||
|
do {
|
||||||
|
readValidateOps(clv, ld, keys);
|
||||||
|
} while (!stop.get());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// wait for some time
|
||||||
|
try {
|
||||||
|
Thread.sleep(millisRuntime);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
// stop tasks
|
||||||
|
stop.set(true);
|
||||||
|
// collect results
|
||||||
|
AssertionError error = null;
|
||||||
|
for (Future<?> future : futures) {
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
if (error == null) error = new AssertionError("Failure");
|
||||||
|
error.addSuppressed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exe.shutdown();
|
||||||
|
if (error != null) throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertEquals(Object actual, Object expected) {
|
||||||
|
if (!Objects.equals(actual, expected)) {
|
||||||
|
throw new AssertionError("Expected: " + expected + ", actual: " + actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user