blob: f570d8b4189afb1eb02580718feed3cc1ceeba24 [file] [log] [blame]
/*
* Copyright (c) 2014, 2018, 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 gc.g1.unloading.loading;
import gc.g1.unloading.ExecutionTask;
import gc.g1.unloading.bytecode.*;
//import gc.g1.unloading.check.*;
import gc.g1.unloading.check.Assertion;
import gc.g1.unloading.check.ClassAssertion;
import gc.g1.unloading.check.PhantomizedAssertion;
import gc.g1.unloading.check.FinalizedAssertion;
import gc.g1.unloading.check.PhantomizationServiceThread;
import gc.g1.unloading.check.cleanup.UnusedThreadKiller;
import gc.g1.unloading.classloaders.DoItYourselfClassLoader;
import gc.g1.unloading.classloaders.FinalizableClassloader;
import gc.g1.unloading.classloaders.JNIClassloader;
import gc.g1.unloading.classloaders.ReflectionClassloader;
import gc.g1.unloading.configuration.ClassloadingMethod;
import gc.g1.unloading.configuration.KeepRefMode;
import gc.g1.unloading.configuration.TestConfiguration;
import gc.g1.unloading.keepref.*;
import nsk.share.test.ExecutionController;
import sun.hotspot.WhiteBox;
import jdk.internal.misc.Unsafe;
import java.lang.ref.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Random;
/**
* This helper performs dirty job: loads classes, instantiate objects, performs redefinition etc...
*/
public class ClassLoadingHelper {
private static final int NATIVE_VERBOSITY = 2;
private static final Object[] NO_CP_PATCHES = new Object[0];
private static BytecodeFactory bf;
private ExecutionController executionController;
private PhantomizationServiceThread phantomizationServiceThread;
private Random random;
private TestConfiguration configuration;
/**
* Constructor that creates instance of helper. All arguments are self-explaining.
* @param executionController
* @param randomSeed
* @param testConfiguration
*/
public ClassLoadingHelper(ExecutionController executionController,
long randomSeed, TestConfiguration testConfiguration) {
random = new Random(randomSeed);
this.executionController = executionController;
this.configuration = testConfiguration;
phantomizationServiceThread = new PhantomizationServiceThread(executionController);
Thread thread = new Thread(phantomizationServiceThread);
thread.setDaemon(true);
thread.start();
if (configuration.isInMemoryCompilation() && !configuration.isHumongousClass() && !(configuration.getKeepRefMode() == KeepRefMode.THREAD_ITSELF)) {
bf = new BytecodeGeneratorFactory(random.nextLong());
} else {
if (configuration.isHumongousClass()) {
bf = new BytecodeMutatorFactory(HumongousTemplateClass.class.getName());
} else if (configuration.getKeepRefMode() == KeepRefMode.THREAD_ITSELF) {
bf = new BytecodeMutatorFactory(ThreadTemplateClass.class.getName());
} else {
bf = new BytecodeMutatorFactory();
}
}
}
/**
* Load class that's supposed to live. Method returns collection of assertions to check it will live.
* @param className_
* @return
*/
public Collection<Assertion> loadClassThatGonnaLive(String className_) {
Bytecode kit = bf.createBytecode(className_);
String className = kit.getClassName();
byte[] bytecode = kit.getBytecode();
Class<?> clazz = loadClass(className, bytecode);
Object object = instantiateObject(clazz);
Object referenceToKeep = configuration.getWhatToKeep().decideUponRefToKeep(clazz, clazz.getClassLoader(), object);
redefineIfNeeded(bytecode, clazz);
warmUpClassIfNeeded(object);
Assertion assertion;
// The JVM prepends the host class's package to the anonymous class name.
if (configuration.getClassloadingMethod() != ClassloadingMethod.ANONYMOUS_CLASSLOADER) {
assertion = new ClassAssertion(className, true);
} else {
assertion = new ClassAssertion("gc/g1/unloading/loading/" + className, true);
}
switch (configuration.getKeepRefMode()) {
case STRONG_REFERENCE:
assertion.keepLink(referenceToKeep);
break;
case SOFT_REFERENCE:
assertion.keepLink(new SoftReference<Object>(referenceToKeep));
break;
case STATIC_FIELD:
RefHolder holder1 = new InStaticFieldHolder();
assertion.keepLink(holder1.hold(referenceToKeep));
break;
case STACK_LOCAL:
RefHolder holder2 = new InStackLocalHolder(); // UnusedThreadKiller
assertion.keepLink(holder2.hold(referenceToKeep));
break;
case THREAD_FIELD:
RefHolder holder3 = new InThreadFieldHolder(); // UnusedThreadKiller
assertion.keepLink(holder3.hold(referenceToKeep));
break;
case THREAD_ITSELF:
Thread objectThread = (Thread) object;
objectThread.setDaemon(true);
objectThread.start();
assertion.keepLink(new UnusedThreadKiller(objectThread.getId())); // UnusedThreadKiller
break;
case STATIC_FIELD_OF_ROOT_CLASS:
RefHolder holder4 = new NullClassloaderHolder();
Object keep = holder4.hold(referenceToKeep);
if (keep != null) {
assertion.keepLink(keep);
}
break;
case JNI_GLOBAL_REF:
JNIGlobalRefHolder holder5 = new JNIGlobalRefHolder();
assertion.keepLink(holder5.hold(referenceToKeep));
break;
case JNI_LOCAL_REF:
JNILocalRefHolder holder6 = new JNILocalRefHolder();
assertion.keepLink(holder6.hold(referenceToKeep));
break;
}
Collection<Assertion> returnValue = new LinkedList<>();
returnValue.add(assertion);
return returnValue;
}
/**
* Load class that's supposed to be unloaded. Method returns collection of assertions to check it will be unloaded.
* @param className_
* @return
*/
public Collection<Assertion> loadClassThatGonnaDie(String className_) {
Collection<Assertion> returnValue = new LinkedList<>();
Bytecode kit = bf.createBytecode(className_);
String className = kit.getClassName();
byte[] bytecode = kit.getBytecode();
Class<?> clazz = loadClass(className, bytecode);
FinalizableClassloader cl = null;
if (clazz.getClassLoader() instanceof FinalizableClassloader) {
cl = (FinalizableClassloader) clazz.getClassLoader();
}
Object object = instantiateObject(clazz);
Object referenceToKeep = configuration.getWhatToKeep().decideUponRefToKeep(clazz, clazz.getClassLoader(), object);
redefineIfNeeded(bytecode, clazz);
warmUpClassIfNeeded(object);
Assertion assertion;
// The JVM prepends the host class's package to the anonymous class name.
if (configuration.getClassloadingMethod() != ClassloadingMethod.ANONYMOUS_CLASSLOADER) {
assertion = new ClassAssertion(className, false);
} else {
assertion = new ClassAssertion("gc/g1/unloading/loading/" + className, false);
}
switch (configuration.getReleaseRefMode()) {
case WEAK:
assertion.keepLink(new WeakReference<Object>(referenceToKeep));
break;
case PHANTOM:
final ReferenceQueue queue = new ReferenceQueue<Object>();
assertion.keepLink(new PhantomReference<Object>(referenceToKeep, queue));
new Thread(new ReferenceCleaningThread(executionController, queue)).start();
break;
}
returnValue.add(assertion);
if (cl != null) {
// Check that classloader will be finalized
FinalizedAssertion finalizedAssertion = new FinalizedAssertion();
cl.setFinalizedAssertion(finalizedAssertion);
returnValue.add(finalizedAssertion);
// Check that classloader will be phantomized
PhantomizedAssertion phantomizedAssertion = new PhantomizedAssertion();
PhantomReference phantomReference = new PhantomReference<Object>(cl, phantomizationServiceThread.getQueue());
phantomizationServiceThread.add(phantomReference, phantomizedAssertion);
returnValue.add(phantomizedAssertion);
}
return returnValue;
}
private void redefineIfNeeded(byte[] bytecode, Class<?> clazz) {
if (configuration.isRedefineClasses()) {
BytecodePatcher.patch(bytecode);
makeRedefinition(NATIVE_VERBOSITY, clazz, bytecode);
// This will call class's method
instantiateObject(clazz);
}
}
private Class<?> loadClass(String className, byte[] bytecode) {
try {
switch (configuration.getClassloadingMethod()) {
case PLAIN:
DoItYourselfClassLoader loader1 = new DoItYourselfClassLoader();
return loader1.defineClass(className, bytecode);
case REFLECTION:
return Class.forName(className, true, new ReflectionClassloader(bytecode, className));
case JNI:
return JNIClassloader.loadThroughJNI(className, bytecode);
case ANONYMOUS_CLASSLOADER:
return getUnsafe().defineAnonymousClass(ClassLoadingHelper.class, bytecode, NO_CP_PATCHES);
}
return null;
} catch (ClassNotFoundException e) {
throw new RuntimeException("Test bug!", e);
}
}
private Object instantiateObject(Class<?> clazz) {
try {
Object object = clazz.newInstance();
// Call method just for fun
for (Method m : clazz.getMethods()) {
if (m.getName().equals("main")) {
m.invoke(object);
}
}
return object;
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException("Test bug!", e);
}
}
private void warmUpClassIfNeeded(Object object) {
if (configuration.getCompilationLevel() < 1 || configuration.getCompilationNumber() == 0) {
return;
}
Method m = null;
for (Method method : object.getClass().getMethods()) {
if (method.getName().equalsIgnoreCase("methodForCompilation")) {
m = method;
}
}
WhiteBox wb = WhiteBox.getWhiteBox();
if (!wb.isMethodCompilable(m)) {
throw new RuntimeException("Test bug! Method occured to be not compilable. Requires investigation.");
}
for (int i = configuration.getCompilationNumber(); i >= 0 && executionController.continueExecution(); i--) {
if (!wb.isMethodCompilable(m, configuration.getCompilationLevel())) {
continue;
}
wb.enqueueMethodForCompilation(m, configuration.getCompilationLevel());
while (!wb.isMethodCompiled(m) && executionController.continueExecution()) {
sleep(50);
try {
m.invoke(object, new Object());
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException("Something went wrong while compilation", e);
}
}
if (i > 0) {
wb.deoptimizeMethod(m);
}
}
}
native static int makeRedefinition0(int verbose, Class<?> redefClass, byte[] classBytes);
private static void makeRedefinition(int verbose, Class<?> redefClass, byte[] classBytes) {
new LibLoader().hashCode();
if (makeRedefinition0(verbose, redefClass, classBytes) != 0) {
throw new RuntimeException("Test bug: native method \"makeRedefinition\" return nonzero");
}
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException("Got InterruptedException while sleeping.", e);
}
}
private static Unsafe getUnsafe() {
return Unsafe.getUnsafe();
}
}
class ReferenceCleaningThread extends ExecutionTask {
private ReferenceQueue<?> queue;
public ReferenceCleaningThread(ExecutionController executionController, ReferenceQueue<?> queue) {
super(executionController);
this.queue = queue;
}
@Override
protected void task() throws Exception {
Reference<?> ref = queue.remove(100);
if (ref != null) {
ref.clear();
return;
}
}
}