mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8078641: MethodHandle.asTypeCache can retain classes from unloading
Co-authored-by: Peter Levart <plevart@openjdk.org> Co-authored-by: Vladimir Ivanov <vlivanov@openjdk.org> Reviewed-by: psandoz, mchung, plevart
This commit is contained in:
parent
185557423d
commit
21012f2bbe
2 changed files with 106 additions and 21 deletions
|
@ -26,6 +26,7 @@
|
||||||
package java.lang.invoke;
|
package java.lang.invoke;
|
||||||
|
|
||||||
|
|
||||||
|
import jdk.internal.loader.ClassLoaders;
|
||||||
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
||||||
|
|
||||||
import java.lang.constant.ClassDesc;
|
import java.lang.constant.ClassDesc;
|
||||||
|
@ -33,6 +34,7 @@ import java.lang.constant.Constable;
|
||||||
import java.lang.constant.DirectMethodHandleDesc;
|
import java.lang.constant.DirectMethodHandleDesc;
|
||||||
import java.lang.constant.MethodHandleDesc;
|
import java.lang.constant.MethodHandleDesc;
|
||||||
import java.lang.constant.MethodTypeDesc;
|
import java.lang.constant.MethodTypeDesc;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -449,12 +451,9 @@ public abstract class MethodHandle implements Constable {
|
||||||
@interface PolymorphicSignature { }
|
@interface PolymorphicSignature { }
|
||||||
|
|
||||||
private final MethodType type;
|
private final MethodType type;
|
||||||
/*private*/
|
/*private*/ final LambdaForm form; // form is not private so that invokers can easily fetch it
|
||||||
final LambdaForm form;
|
private MethodHandle asTypeCache;
|
||||||
// form is not private so that invokers can easily fetch it
|
private SoftReference<MethodHandle> asTypeSoftCache;
|
||||||
/*private*/
|
|
||||||
MethodHandle asTypeCache;
|
|
||||||
// asTypeCache is not private so that invokers can easily fetch it
|
|
||||||
|
|
||||||
private byte customizationCount;
|
private byte customizationCount;
|
||||||
|
|
||||||
|
@ -855,34 +854,120 @@ public abstract class MethodHandle implements Constable {
|
||||||
* @throws WrongMethodTypeException if the conversion cannot be made
|
* @throws WrongMethodTypeException if the conversion cannot be made
|
||||||
* @see MethodHandles#explicitCastArguments
|
* @see MethodHandles#explicitCastArguments
|
||||||
*/
|
*/
|
||||||
public MethodHandle asType(MethodType newType) {
|
public final MethodHandle asType(MethodType newType) {
|
||||||
// Fast path alternative to a heavyweight {@code asType} call.
|
// Fast path alternative to a heavyweight {@code asType} call.
|
||||||
// Return 'this' if the conversion will be a no-op.
|
// Return 'this' if the conversion will be a no-op.
|
||||||
if (newType == type) {
|
if (newType == type) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
// Return 'this.asTypeCache' if the conversion is already memoized.
|
// Return 'this.asTypeCache' if the conversion is already memoized.
|
||||||
MethodHandle atc = asTypeCached(newType);
|
MethodHandle at = asTypeCached(newType);
|
||||||
if (atc != null) {
|
if (at != null) {
|
||||||
return atc;
|
return at;
|
||||||
}
|
}
|
||||||
return asTypeUncached(newType);
|
return setAsTypeCache(asTypeUncached(newType));
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodHandle asTypeCached(MethodType newType) {
|
private MethodHandle asTypeCached(MethodType newType) {
|
||||||
MethodHandle atc = asTypeCache;
|
MethodHandle atc = asTypeCache;
|
||||||
if (atc != null && newType == atc.type) {
|
if (atc != null && newType == atc.type) {
|
||||||
return atc;
|
return atc; // cache hit
|
||||||
|
}
|
||||||
|
SoftReference<MethodHandle> softCache = asTypeSoftCache;
|
||||||
|
if (softCache != null) {
|
||||||
|
atc = softCache.get();
|
||||||
|
if (atc != null && newType == atc.type) {
|
||||||
|
return atc; // soft cache hit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MethodHandle setAsTypeCache(MethodHandle at) {
|
||||||
|
// Don't introduce a strong reference in the cache if newType depends on any class loader other than
|
||||||
|
// current method handle already does to avoid class loader leaks.
|
||||||
|
if (isSafeToCache(at.type)) {
|
||||||
|
asTypeCache = at;
|
||||||
|
} else {
|
||||||
|
asTypeSoftCache = new SoftReference<>(at);
|
||||||
|
}
|
||||||
|
return at;
|
||||||
|
}
|
||||||
|
|
||||||
/** Override this to change asType behavior. */
|
/** Override this to change asType behavior. */
|
||||||
/*non-public*/
|
/*non-public*/
|
||||||
MethodHandle asTypeUncached(MethodType newType) {
|
MethodHandle asTypeUncached(MethodType newType) {
|
||||||
if (!type.isConvertibleTo(newType))
|
if (!type.isConvertibleTo(newType)) {
|
||||||
throw new WrongMethodTypeException("cannot convert "+this+" to "+newType);
|
throw new WrongMethodTypeException("cannot convert " + this + " to " + newType);
|
||||||
return asTypeCache = MethodHandleImpl.makePairwiseConvert(this, newType, true);
|
}
|
||||||
|
return MethodHandleImpl.makePairwiseConvert(this, newType, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if {@code newType} does not depend on any class loader other than current method handle already does.
|
||||||
|
* May conservatively return false in order to be efficient.
|
||||||
|
*/
|
||||||
|
private boolean isSafeToCache(MethodType newType) {
|
||||||
|
ClassLoader loader = getApproximateCommonClassLoader(type);
|
||||||
|
return keepsAlive(newType, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to find the most specific {@code ClassLoader} which keeps all the classes mentioned in {@code mt} alive.
|
||||||
|
* In the worst case, returns a {@code ClassLoader} which relates to some of the classes mentioned in {@code mt}.
|
||||||
|
*/
|
||||||
|
private static ClassLoader getApproximateCommonClassLoader(MethodType mt) {
|
||||||
|
ClassLoader loader = mt.rtype().getClassLoader();
|
||||||
|
for (Class<?> ptype : mt.ptypes()) {
|
||||||
|
ClassLoader ploader = ptype.getClassLoader();
|
||||||
|
if (isAncestorLoaderOf(loader, ploader)) {
|
||||||
|
loader = ploader; // pick more specific loader
|
||||||
|
} else {
|
||||||
|
// Either loader is a descendant of ploader or loaders are unrelated. Ignore both cases.
|
||||||
|
// When loaders are not related, just pick one and proceed. It reduces the precision of keepsAlive, but
|
||||||
|
// doesn't compromise correctness.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns true when {@code loader} keeps components of {@code mt} reachable either directly or indirectly through the loader delegation chain. */
|
||||||
|
private static boolean keepsAlive(MethodType mt, ClassLoader loader) {
|
||||||
|
for (Class<?> ptype : mt.ptypes()) {
|
||||||
|
if (!keepsAlive(ptype, loader)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keepsAlive(mt.rtype(), loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns true when {@code loader} keeps {@code cls} either directly or indirectly through the loader delegation chain. */
|
||||||
|
private static boolean keepsAlive(Class<?> cls, ClassLoader loader) {
|
||||||
|
ClassLoader defLoader = cls.getClassLoader();
|
||||||
|
if (isBuiltinLoader(defLoader)) {
|
||||||
|
return true; // built-in loaders are always reachable
|
||||||
|
}
|
||||||
|
return isAncestorLoaderOf(defLoader, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAncestorLoaderOf(ClassLoader ancestor, ClassLoader descendant) {
|
||||||
|
// Assume built-in loaders are interchangeable and all custom loaders delegate to one of them.
|
||||||
|
if (isBuiltinLoader(ancestor)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Climb up the descendant chain until a built-in loader is encountered.
|
||||||
|
for (ClassLoader loader = descendant; !isBuiltinLoader(loader); loader = loader.getParent()) {
|
||||||
|
if (loader == ancestor) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; // no direct relation between loaders is found
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBuiltinLoader(ClassLoader loader) {
|
||||||
|
return loader == null ||
|
||||||
|
loader == ClassLoaders.platformClassLoader() ||
|
||||||
|
loader == ClassLoaders.appClassLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -493,12 +493,12 @@ abstract class MethodHandleImpl {
|
||||||
if (newArity == collectArg+1 &&
|
if (newArity == collectArg+1 &&
|
||||||
type.parameterType(collectArg).isAssignableFrom(newType.parameterType(collectArg))) {
|
type.parameterType(collectArg).isAssignableFrom(newType.parameterType(collectArg))) {
|
||||||
// if arity and trailing parameter are compatible, do normal thing
|
// if arity and trailing parameter are compatible, do normal thing
|
||||||
return asTypeCache = asFixedArity().asType(newType);
|
return asFixedArity().asType(newType);
|
||||||
}
|
}
|
||||||
// check cache
|
// check cache
|
||||||
MethodHandle acc = asCollectorCache;
|
MethodHandle acc = asCollectorCache;
|
||||||
if (acc != null && acc.type().parameterCount() == newArity)
|
if (acc != null && acc.type().parameterCount() == newArity)
|
||||||
return asTypeCache = acc.asType(newType);
|
return acc.asType(newType);
|
||||||
// build and cache a collector
|
// build and cache a collector
|
||||||
int arrayLength = newArity - collectArg;
|
int arrayLength = newArity - collectArg;
|
||||||
MethodHandle collector;
|
MethodHandle collector;
|
||||||
|
@ -509,7 +509,7 @@ abstract class MethodHandleImpl {
|
||||||
throw new WrongMethodTypeException("cannot build collector", ex);
|
throw new WrongMethodTypeException("cannot build collector", ex);
|
||||||
}
|
}
|
||||||
asCollectorCache = collector;
|
asCollectorCache = collector;
|
||||||
return asTypeCache = collector.asType(newType);
|
return collector.asType(newType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -737,7 +737,7 @@ abstract class MethodHandleImpl {
|
||||||
} else {
|
} else {
|
||||||
wrapper = newTarget; // no need for a counting wrapper anymore
|
wrapper = newTarget; // no need for a counting wrapper anymore
|
||||||
}
|
}
|
||||||
return (asTypeCache = wrapper);
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean countDown() {
|
boolean countDown() {
|
||||||
|
@ -1213,7 +1213,7 @@ abstract class MethodHandleImpl {
|
||||||
public MethodHandle asTypeUncached(MethodType newType) {
|
public MethodHandle asTypeUncached(MethodType newType) {
|
||||||
// This MH is an alias for target, except for the MemberName
|
// This MH is an alias for target, except for the MemberName
|
||||||
// Drop the MemberName if there is any conversion.
|
// Drop the MemberName if there is any conversion.
|
||||||
return asTypeCache = target.asType(newType);
|
return target.asType(newType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1276,7 +1276,7 @@ abstract class MethodHandleImpl {
|
||||||
public MethodHandle asTypeUncached(MethodType newType) {
|
public MethodHandle asTypeUncached(MethodType newType) {
|
||||||
// This MH is an alias for target, except for the intrinsic name
|
// This MH is an alias for target, except for the intrinsic name
|
||||||
// Drop the name if there is any conversion.
|
// Drop the name if there is any conversion.
|
||||||
return asTypeCache = target.asType(newType);
|
return target.asType(newType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue