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.
|
||||
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
|
||||
|
@ -49,6 +49,7 @@ import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.nio.channels.Channel;
|
||||
import java.nio.channels.spi.SelectorProvider;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import java.util.Objects;
|
||||
@ -2026,6 +2027,9 @@ public final class System {
|
||||
public ServicesCatalog createOrGetServicesCatalog(ClassLoader cl) {
|
||||
return cl.createOrGetServicesCatalog();
|
||||
}
|
||||
public ConcurrentHashMap<?, ?> createOrGetClassLoaderValueMap(ClassLoader cl) {
|
||||
return cl.createOrGetClassLoaderValueMap();
|
||||
}
|
||||
public Class<?> findBootstrapClassOrNull(ClassLoader cl, String 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;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.Arrays;
|
||||
@ -39,10 +38,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@ -283,6 +280,13 @@ public class Proxy implements java.io.Serializable {
|
||||
private static final Class<?>[] constructorParams =
|
||||
{ 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.
|
||||
* @serial
|
||||
@ -361,14 +365,55 @@ public class Proxy implements java.io.Serializable {
|
||||
Class<?>... interfaces)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
final List<Class<?>> intfs = List.of(interfaces); // interfaces cloned
|
||||
final SecurityManager sm = System.getSecurityManager();
|
||||
final Class<?> caller = Reflection.getCallerClass();
|
||||
if (sm != null) {
|
||||
checkProxyAccess(caller, loader, intfs);
|
||||
}
|
||||
Class<?> caller = System.getSecurityManager() == null
|
||||
? null
|
||||
: Reflection.getCallerClass();
|
||||
|
||||
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,
|
||||
ClassLoader loader,
|
||||
List<Class<?>> interfaces)
|
||||
Class<?> ... interfaces)
|
||||
{
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
@ -399,147 +444,18 @@ public class Proxy implements java.io.Serializable {
|
||||
if (VM.isSystemDomainLoader(loader) && !VM.isSystemDomainLoader(ccl)) {
|
||||
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
|
||||
}
|
||||
ReflectUtil.checkProxyPackageAccess(ccl, interfaces.toArray(EMPTY_CLASS_ARRAY));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that maps an array of interfaces to an optimal key where
|
||||
* Class objects representing interfaces are weakly referenced.
|
||||
* Builder for a proxy class.
|
||||
*
|
||||
* 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>
|
||||
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 class ProxyBuilder {
|
||||
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||
|
||||
// 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
|
||||
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) {
|
||||
String proxyPkg = null; // package to define proxy class in
|
||||
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
|
||||
@ -601,8 +521,11 @@ public class Proxy implements java.io.Serializable {
|
||||
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
|
||||
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
|
||||
try {
|
||||
return UNSAFE.defineClass(proxyName, proxyClassFile, 0, proxyClassFile.length,
|
||||
loader, null);
|
||||
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
|
||||
0, proxyClassFile.length,
|
||||
loader, null);
|
||||
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
|
||||
return pc;
|
||||
} catch (ClassFormatError e) {
|
||||
/*
|
||||
* 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) {
|
||||
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) {
|
||||
String pn = c.getPackageName();
|
||||
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();
|
||||
}
|
||||
private static final boolean isDebug(String flag) {
|
||||
private static boolean isDebug(String flag) {
|
||||
return DEBUG.equals(flag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for a proxy class.
|
||||
*
|
||||
* 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 ProxyBuilder {
|
||||
final ClassLoader loader;
|
||||
final List<Class<?>> interfaces;
|
||||
final Module module;
|
||||
// ProxyBuilder instance members start here....
|
||||
|
||||
private final ClassLoader loader;
|
||||
private final List<Class<?>> interfaces;
|
||||
private final Module module;
|
||||
ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
|
||||
if (!VM.isModuleSystemInited()) {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
* at defineClass time.
|
||||
*
|
||||
* Must call the checkProxyAccess method to perform permission checks
|
||||
* before calling this.
|
||||
*/
|
||||
Class<?> build() {
|
||||
return ProxyClassFactory.get(module, interfaces);
|
||||
Constructor<?> build() {
|
||||
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
|
||||
* in {@link Proxy#newProxyInstance}
|
||||
*/
|
||||
static void validateProxyInterfaces(ClassLoader loader,
|
||||
List<Class<?>> interfaces,
|
||||
Set<Class<?>> refTypes)
|
||||
private static void validateProxyInterfaces(ClassLoader loader,
|
||||
List<Class<?>> interfaces,
|
||||
Set<Class<?>> refTypes)
|
||||
{
|
||||
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.size());
|
||||
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
|
||||
* 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()
|
||||
.flatMap(intf -> Stream.of(intf.getMethods())
|
||||
.flatMap(m -> methodRefTypes(m))
|
||||
.flatMap(ProxyBuilder::methodRefTypes)
|
||||
.map(ProxyBuilder::getElementType)
|
||||
.filter(t -> !t.isPrimitive()))
|
||||
.collect(Collectors.toSet());
|
||||
@ -792,11 +706,11 @@ public class Proxy implements java.io.Serializable {
|
||||
* Extracts all types referenced on a method signature including
|
||||
* 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() },
|
||||
m.getParameterTypes(),
|
||||
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
|
||||
* 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> packagePrivateTypes = new HashMap<>();
|
||||
for (Class<?> intf : interfaces) {
|
||||
@ -884,10 +800,9 @@ public class Proxy implements java.io.Serializable {
|
||||
Set<Class<?>> visited = new HashSet<>();
|
||||
while (!deque.isEmpty()) {
|
||||
Class<?> c = deque.poll();
|
||||
if (visited.contains(c)) {
|
||||
if (!visited.add(c)) {
|
||||
continue;
|
||||
}
|
||||
visited.add(c);
|
||||
ensureAccess(target, c);
|
||||
|
||||
// add all superinterfaces
|
||||
@ -906,7 +821,7 @@ public class Proxy implements java.io.Serializable {
|
||||
/*
|
||||
* 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();
|
||||
// add read edge and qualified export for the target module to access
|
||||
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.
|
||||
*/
|
||||
static void ensureVisible(ClassLoader ld, Class<?> c) {
|
||||
private static void ensureVisible(ClassLoader ld, Class<?> c) {
|
||||
Class<?> type = null;
|
||||
try {
|
||||
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;
|
||||
while (e.isArray()) {
|
||||
e = e.getComponentType();
|
||||
@ -941,7 +856,8 @@ public class Proxy implements java.io.Serializable {
|
||||
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();
|
||||
|
||||
/*
|
||||
@ -950,12 +866,12 @@ public class Proxy implements java.io.Serializable {
|
||||
*
|
||||
* Each class loader will have one dynamic module.
|
||||
*/
|
||||
static Module getDynamicModule(ClassLoader loader) {
|
||||
return dynProxyModules.computeIfAbsent(loader, ld -> {
|
||||
private static Module getDynamicModule(ClassLoader loader) {
|
||||
return dynProxyModules.computeIfAbsent(loader, (ld, clv) -> {
|
||||
// create a dynamic module and setup module access
|
||||
String mn = "jdk.proxy" + counter.incrementAndGet();
|
||||
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());
|
||||
// java.base to create proxy instance
|
||||
Modules.addExports(m, pn, Object.class.getModule());
|
||||
@ -1062,40 +978,31 @@ public class Proxy implements java.io.Serializable {
|
||||
InvocationHandler h) {
|
||||
Objects.requireNonNull(h);
|
||||
|
||||
final List<Class<?>> intfs = List.of(interfaces); // interfaces cloned
|
||||
final SecurityManager sm = System.getSecurityManager();
|
||||
final Class<?> caller = Reflection.getCallerClass();
|
||||
if (sm != null) {
|
||||
checkProxyAccess(caller, loader, intfs);
|
||||
}
|
||||
final Class<?> caller = System.getSecurityManager() == null
|
||||
? null
|
||||
: Reflection.getCallerClass();
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
try {
|
||||
final SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
checkNewProxyPermission(caller, proxyClass);
|
||||
if (caller != null) {
|
||||
checkNewProxyPermission(caller, cons.getDeclaringClass());
|
||||
}
|
||||
|
||||
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});
|
||||
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException e) {
|
||||
} catch (IllegalAccessException | InstantiationException e) {
|
||||
throw new InternalError(e.toString(), e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable t = e.getCause();
|
||||
@ -1150,7 +1057,7 @@ public class Proxy implements java.io.Serializable {
|
||||
* @throws NullPointerException if {@code cl} is {@code null}
|
||||
*/
|
||||
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.Enumeration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Stream;
|
||||
@ -68,6 +69,10 @@ public class BootLoader {
|
||||
// ServiceCatalog for the boot class loader
|
||||
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.
|
||||
*/
|
||||
@ -82,6 +87,13 @@ public class BootLoader {
|
||||
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
|
||||
* resources) become visible via this class loader.
|
||||
|
@ -33,6 +33,7 @@ import java.lang.reflect.Module;
|
||||
import java.net.URL;
|
||||
import java.security.AccessControlContext;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.module.ServicesCatalog;
|
||||
@ -145,6 +146,12 @@ public interface JavaLangAccess {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
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