/* * Copyright (c) 2014, 2020, 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 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(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(random.nextLong()); 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 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 loadClassThatGonnaDie(String className_) { Collection 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(referenceToKeep)); break; case PHANTOM: final ReferenceQueue queue = new ReferenceQueue(); assertion.keepLink(new PhantomReference(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(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; } } }