Skip to content

Commit

Permalink
Added possibility to clean nexus.
Browse files Browse the repository at this point in the history
  • Loading branch information
raphw committed Nov 13, 2016
1 parent 24f3575 commit 339d091
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 49 deletions.
Expand Up @@ -2243,8 +2243,13 @@ public String toString() {
} }


/** /**
* <p>
* An initialization strategy that adds a code block to an instrumented type's type initializer which * An initialization strategy that adds a code block to an instrumented type's type initializer which
* then calls a specific class that is responsible for the explicit initialization. * then calls a specific class that is responsible for the explicit initialization.
* </p>
* <p>
* <b>Important</b>: Using self-injection might require the explicit cleaning of class loader meta data via {@link NexusAccessor#clean()}.
* </p>
*/ */
@SuppressFBWarnings(value = "DMI_RANDOM_USED_ONLY_ONCE", justification = "Avoiding synchronization without security concerns") @SuppressFBWarnings(value = "DMI_RANDOM_USED_ONLY_ONCE", justification = "Avoiding synchronization without security concerns")
enum SelfInjection implements InitializationStrategy { enum SelfInjection implements InitializationStrategy {
Expand Down
80 changes: 54 additions & 26 deletions byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/Nexus.java
@@ -1,6 +1,7 @@
package net.bytebuddy.dynamic; package net.bytebuddy.dynamic;


import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger; import java.util.logging.Logger;
Expand All @@ -20,7 +21,7 @@
* system class loader in its hierarchy. * system class loader in its hierarchy.
* </p> * </p>
*/ */
public class Nexus { public class Nexus extends WeakReference<ClassLoader> {


/** /**
* A map of keys identifying a loaded type by its name and class loader mapping their * A map of keys identifying a loaded type by its name and class loader mapping their
Expand All @@ -34,11 +35,6 @@ public class Nexus {
*/ */
private final String name; private final String name;


/**
* A weak reference to the class loader for which a loaded type initializer is registered.
*/
private final WeakReference<ClassLoader> classLoaderReference;

/** /**
* The class loader's hash code upon registration. * The class loader's hash code upon registration.
*/ */
Expand Down Expand Up @@ -68,14 +64,9 @@ private Nexus(Class<?> type, int identification) {
* @param identification An identification for the initializer to run. * @param identification An identification for the initializer to run.
*/ */
private Nexus(String name, ClassLoader classLoader, int identification) { private Nexus(String name, ClassLoader classLoader, int identification) {
super(classLoader);
this.name = name; this.name = name;
if (classLoader == null) { classLoaderHashCode = System.identityHashCode(classLoader);
classLoaderReference = null;
classLoaderHashCode = 0;
} else {
classLoaderReference = new WeakReference<ClassLoader>(classLoader);
classLoaderHashCode = System.identityHashCode(classLoader);
}
this.identification = identification; this.identification = identification;
} }


Expand All @@ -93,7 +84,15 @@ private static String nonAnonymous(String typeName) {
} }


/** /**
* <p>
* Initializes a loaded type. This method must only be invoked via the system class loader. * Initializes a loaded type. This method must only be invoked via the system class loader.
* </p>
* <p>
* <b>Important</b>: This method must never be called directly but only by using a {@link NexusAccessor.InitializationAppender} which enforces to
* access this class for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded
* by different class loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by
* different class loaders, too. Any access of the instance is done using Java reflection instead.
* </p>
* *
* @param type The loaded type to initialize. * @param type The loaded type to initialize.
* @param identification An identification for the initializer to run. * @param identification An identification for the initializer to run.
Expand All @@ -112,11 +111,10 @@ public static void initialize(Class<?> type, int identification) throws Exceptio
* Registers a new loaded type initializer. * Registers a new loaded type initializer.
* </p> * </p>
* <p> * <p>
* Important: This method must never be called directly but only by using a * <b>Important</b>: This method must never be called directly but only by using a {@link NexusAccessor} which enforces to access this class
* {@link NexusAccessor#register(String, ClassLoader, int, net.bytebuddy.implementation.LoadedTypeInitializer)} which enforces to access this class * for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded by different class
* for the system class loader where a Java agent always registers its instances. This avoids a duplication of the class if this nexus is loaded by * loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by different class loaders,
* different class loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by different class * too. Any access of the instance is done using Java reflection instead.
* loaders, too. Any access of the instance is done using Java reflection instead.
* </p> * </p>
* *
* @param name The name of the type for the loaded type initializer. * @param name The name of the type for the loaded type initializer.
Expand All @@ -132,14 +130,44 @@ public static void register(String name, ClassLoader classLoader, int identifica
} }
} }


/**
* <p>
* Cleans any stale entries from this nexus. Entries are considered stale if their class loader was collected before a class was initialized.
* </p>
* <p>
* <b>Important</b>: This method must never be called directly but only by using a {@link NexusAccessor} which enforces to access this class
* for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded by different class
* loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by different class loaders,
* too. Any access of the instance is done using Java reflection instead.
* </p>
*/
public static void clean() {
Iterator<Nexus> iterator = TYPE_INITIALIZERS.keySet().iterator();
while (iterator.hasNext()) {
if (iterator.next().isStale()) {
iterator.remove();
}
}
}

/**
* Checks if this entry is stale, i.e. the referenced class loader was garbage collected.
*
* @return {@code true} if this entry is stale.
*/
private boolean isStale() {
return classLoaderHashCode != 0 && get() == null;
}

@Override @Override
public boolean equals(Object other) { public boolean equals(Object object) {
if (this == other) return true; if (this == object) return true;
if (other == null || getClass() != other.getClass()) return false; if (object == null || getClass() != object.getClass()) return false;
Nexus nexus = (Nexus) other; Nexus nexus = (Nexus) object;
return !(identification != nexus.identification || classLoaderHashCode != nexus.classLoaderHashCode || !name.equals(nexus.name)) return classLoaderHashCode == nexus.classLoaderHashCode
&& (classLoaderReference == nexus.classLoaderReference && identification == nexus.identification
|| classLoaderReference != null && nexus.classLoaderReference != null && classLoaderReference.get() == nexus.classLoaderReference.get()); && get() == nexus.get()
&& name.equals(nexus.name);
} }


@Override @Override
Expand All @@ -154,7 +182,7 @@ public int hashCode() {
public String toString() { public String toString() {
return "Nexus{" + return "Nexus{" +
"name='" + name + '\'' + "name='" + name + '\'' +
", classLoader=" + (classLoaderReference == null ? null : classLoaderReference.get()) + ", classLoaderReference=" + get() +
", classLoaderHashCode=" + classLoaderHashCode + ", classLoaderHashCode=" + classLoaderHashCode +
", identification=" + identification + ", identification=" + identification +
'}'; '}';
Expand Down
109 changes: 87 additions & 22 deletions byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/NexusAccessor.java
Expand Up @@ -72,7 +72,7 @@ public enum NexusAccessor implements PrivilegedAction<NexusAccessor.Dispatcher>
* Creates the singleton accessor. * Creates the singleton accessor.
*/ */
NexusAccessor() { NexusAccessor() {
this.dispatcher = AccessController.doPrivileged(this); dispatcher = AccessController.doPrivileged(this);
getSystemClassLoader = new TypeDescription.ForLoadedType(ClassLoader.class).getDeclaredMethods() getSystemClassLoader = new TypeDescription.ForLoadedType(ClassLoader.class).getDeclaredMethods()
.filter(named("getSystemClassLoader").and(takesArguments(0))).getOnly(); .filter(named("getSystemClassLoader").and(takesArguments(0))).getOnly();
loadClass = new TypeDescription.ForLoadedType(ClassLoader.class).getDeclaredMethods() loadClass = new TypeDescription.ForLoadedType(ClassLoader.class).getDeclaredMethods()
Expand All @@ -89,22 +89,40 @@ public enum NexusAccessor implements PrivilegedAction<NexusAccessor.Dispatcher>
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback") @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
public Dispatcher run() { public Dispatcher run() {
try { try {
TypeDescription nexusType = new TypeDescription.ForLoadedType(Nexus.class); Class<?> nexusType = new ClassInjector.UsingReflection(ClassLoader.getSystemClassLoader(), Nexus.class.getProtectionDomain())
return new Dispatcher.Available(new ClassInjector.UsingReflection(ClassLoader.getSystemClassLoader(), Nexus.class.getProtectionDomain()) .inject(Collections.singletonMap(new TypeDescription.ForLoadedType(Nexus.class), ClassFileLocator.ForClassLoader.read(Nexus.class).resolve()))
.inject(Collections.singletonMap(nexusType, ClassFileLocator.ForClassLoader.read(Nexus.class).resolve())) .get(new TypeDescription.ForLoadedType(Nexus.class));
.get(nexusType) return new Dispatcher.Available(nexusType.getDeclaredMethod("register", String.class, ClassLoader.class, int.class, Object.class),
.getDeclaredMethod("register", String.class, ClassLoader.class, int.class, Object.class)); nexusType.getDeclaredMethod("clean"));
} catch (Exception exception) { } catch (Exception exception) {
try { try {
return new Dispatcher.Available(ClassLoader.getSystemClassLoader() Class<?> nexusType = ClassLoader.getSystemClassLoader().loadClass(Nexus.class.getName());
.loadClass(Nexus.class.getName()) return new Dispatcher.Available(nexusType.getDeclaredMethod("register", String.class, ClassLoader.class, int.class, Object.class),
.getDeclaredMethod("register", String.class, ClassLoader.class, int.class, Object.class)); nexusType.getDeclaredMethod("clean"));
} catch (Exception ignored) { } catch (Exception ignored) {
return new Dispatcher.Unavailable(exception); return new Dispatcher.Unavailable(exception);
} }
} }
} }


/**
* Checks if this {@link NexusAccessor} is capable of registering loaded type initializers.
*
* @return {@code true} if this accessor is alive.
*/
public boolean isAlive() {
return dispatcher.isAlive();
}

/**
* Removes any stale entries that are registered in the {@link Nexus}. Entries can become stale if a class is loaded but never initialized
* prior to its garbage collection. As all class loaders within a nexus are only referenced weakly, such class loaders are always garbage
* collected. However, the initialization data stored by Byte Buddy does not become eligible which is why it needs to be cleaned explicitly.
*/
public void clean() {
dispatcher.clean();
}

/** /**
* Registers a loaded type initializer in Byte Buddy's {@link Nexus} which is injected into the system class loader. * Registers a loaded type initializer in Byte Buddy's {@link Nexus} which is injected into the system class loader.
* *
Expand Down Expand Up @@ -194,7 +212,19 @@ public String toString() {
protected interface Dispatcher { protected interface Dispatcher {


/** /**
* Registers a type initializer with the class loader's nexus. * Returns {@code true} if this dispatcher is alive.
*
* @return {@code true} if this dispatcher is alive.
*/
boolean isAlive();

/**
* Cleans any dead entries of the system class loader's {@link Nexus}.
*/
void clean();

/**
* Registers a type initializer with the system class loader's nexus.
* *
* @param name The name of a type for which a loaded type initializer is registered. * @param name The name of a type for which a loaded type initializer is registered.
* @param classLoader The class loader for which a loaded type initializer is registered. * @param classLoader The class loader for which a loaded type initializer is registered.
Expand All @@ -214,45 +244,70 @@ class Available implements Dispatcher {
private static final Object STATIC_METHOD = null; private static final Object STATIC_METHOD = null;


/** /**
* The method for registering a type initializer in the system class loader's {@link Nexus}. * The {@link Nexus#register(String, ClassLoader, int, Object)} method.
*/ */
private final Method registration; private final Method register;

/**
* The {@link Nexus#clean()} method.
*/
private final Method clean;


/** /**
* Creates a new dispatcher. * Creates a new dispatcher.
* *
* @param registration The method for registering a type initializer in the system class loader's {@link Nexus}. * @param register The {@link Nexus#register(String, ClassLoader, int, Object)} method.
* @param clean The {@link Nexus#clean()} method.
*/ */
protected Available(Method registration) { protected Available(Method register, Method clean) {
this.registration = registration; this.register = register;
this.clean = clean;
}

@Override
public boolean isAlive() {
return true;
}

@Override
public void clean() {
try {
clean.invoke(STATIC_METHOD);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot access: " + clean, exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException("Cannot invoke: " + clean, exception.getCause());
}
} }


@Override @Override
public void register(String name, ClassLoader classLoader, int identification, LoadedTypeInitializer loadedTypeInitializer) { public void register(String name, ClassLoader classLoader, int identification, LoadedTypeInitializer loadedTypeInitializer) {
try { try {
registration.invoke(STATIC_METHOD, name, classLoader, identification, loadedTypeInitializer); register.invoke(STATIC_METHOD, name, classLoader, identification, loadedTypeInitializer);
} catch (IllegalAccessException exception) { } catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot register type initializer for " + name, exception); throw new IllegalStateException("Cannot access: " + register, exception);
} catch (InvocationTargetException exception) { } catch (InvocationTargetException exception) {
throw new IllegalStateException("Cannot register type initializer for " + name, exception.getCause()); throw new IllegalStateException("Cannot invoke: " + register, exception.getCause());
} }
} }


@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass()) return this == other || !(other == null || getClass() != other.getClass())
&& registration.equals(((Available) other).registration); && register.equals(((Available) other).register)
&& clean.equals(((Available) other).clean);
} }


@Override @Override
public int hashCode() { public int hashCode() {
return registration.hashCode(); return register.hashCode() + 31 * clean.hashCode();
} }


@Override @Override
public String toString() { public String toString() {
return "NexusAccessor.Dispatcher.Available{" + return "NexusAccessor.Dispatcher.Available{" +
"registration=" + registration + "register=" + register +
", clean=" + clean +
'}'; '}';
} }
} }
Expand All @@ -276,9 +331,19 @@ protected Unavailable(Exception exception) {
this.exception = exception; this.exception = exception;
} }


@Override
public boolean isAlive() {
return false;
}

@Override
public void clean() {
/* do nothing */
}

@Override @Override
public void register(String name, ClassLoader classLoader, int identification, LoadedTypeInitializer loadedTypeInitializer) { public void register(String name, ClassLoader classLoader, int identification, LoadedTypeInitializer loadedTypeInitializer) {
throw new IllegalStateException("Could not locate registration method", exception); throw new IllegalStateException("Could not locate Nexus method", exception);
} }


@Override @Override
Expand Down
Expand Up @@ -84,8 +84,13 @@ public String toString() {
} }


/** /**
* <p>
* A type resolution strategy that applies all {@link LoadedTypeInitializer} as a part of class loading using reflection. This implies that the initializers * A type resolution strategy that applies all {@link LoadedTypeInitializer} as a part of class loading using reflection. This implies that the initializers
* are executed <b>before</b> (as a first action of) a type initializer is executed. * are executed <b>before</b> (as a first action of) a type initializer is executed.
* </p>
* <p>
* <b>Important</b>: Using self-injection might require the explicit cleaning of class loader meta data via {@link NexusAccessor#clean()}.
* </p>
*/ */
enum Active implements TypeResolutionStrategy { enum Active implements TypeResolutionStrategy {


Expand Down

0 comments on commit 339d091

Please sign in to comment.