/* * Copyright (c) 2001, 2023, 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. */ /* * Warning! Using this component need VM option -XX:-UseGCOverheadLimit * */ package nsk.share; import java.lang.ref.Cleaner; import java.util.*; import nsk.share.gc.gp.*; import nsk.share.test.ExecutionController; import nsk.share.test.Stresser; /** * The ClassUnloader class allows to force VM to unload class(es) * using memory stressing technique. * *

The method unloadClass() is provided which eats memory * to enforce GC to cleanup the heap. So, if all references to a class * and its loader are canceled, this may result in unloading the class. * *

ClassUnloader mainly intends to unload a class which was loaded * with especial ClassUnloader.loadClass() method. * A class is eligible for unloading if its class loader has been reclaimed. * A Cleaner is used to inform the main test code when the class loader * becomes unreachable and is reclaimed. * If, after setting the class loader to null, no notification that it has become * reclaimed is received within the timeout interval, then the class is considered * to still be loaded and unloadClass() returns false. * *

Such reclaiming control applies only to a class loaded by * ClassUnloader's loadClass() method. Otherwise, if there * was no such class loaded, unloadClass() doesn't wait * for a timeout and always returns false. * *

By default internal class loader of CustomClassLoader class * is used for loading classes. This class loader can load class from .class file * located in the specified directory. * Application may define its own class loader, which may load classes using * any other technique. Such class loader should be derived from base * CustomClassLoader class, and set by setClassLoader() * method. * * @see #setClassLoader(CustomClassLoader) * @see #loadClass(String) * @see #loadClass(String, String) * @see #unloadClass() */ public class ClassUnloader { /** * Class name of default class loader. */ public static final String INTERNAL_CLASS_LOADER_NAME = "nsk.share.CustomClassLoader"; /** * Whole amount of time in milliseconds to wait for class loader to be reclaimed. */ private static final int WAIT_TIMEOUT = 15000; /** * Sleep time in milliseconds for the loop waiting for the class loader to be reclaimed. */ private static final int WAIT_DELTA = 1000; /** * Has class loader been reclaimed or not. */ volatile boolean is_reclaimed = false; /** * Current class loader used for loading classes. */ private CustomClassLoader customClassLoader = null; /** * List of classes loaded with current class loader. */ private Vector> classObjects = new Vector>(); /** * Class object of the first class been loaded with current class loader. * To get the rest loaded classes use getLoadedClass(int). * The call getLoadedClass() is effectively equivalent to the call * getLoadedClass(0) * * @return class object of the first loaded class. * * @see #getLoadedClass(int) */ public Class getLoadedClass() { return classObjects.get(0); } /** * Returns class objects at the specified index in the list of classes loaded * with current class loader. * * @return class objects at the specified index. */ public Class getLoadedClass(int index) { return classObjects.get(index); } /** * Creates new instance of CustomClassLoader class as the current * class loader and clears the list of loaded classes. * * @return created instance of CustomClassLoader class. * * @see #getClassLoader() * @see #setClassLoader(CustomClassLoader) */ public CustomClassLoader createClassLoader() { customClassLoader = new CustomClassLoader(); classObjects.removeAllElements(); // Register a Cleaner to inform us when the class loader has been reclaimed. Cleaner.create().register(customClassLoader, () -> { is_reclaimed = true; } ); return customClassLoader; } /** * Sets new current class loader and clears the list of loaded classes. * * @see #getClassLoader() * @see #createClassLoader() */ public void setClassLoader(CustomClassLoader customClassLoader) { this.customClassLoader = customClassLoader; classObjects.removeAllElements(); // Register a Cleaner to inform us when the class loader has been reclaimed. Cleaner.create().register(customClassLoader, () -> { is_reclaimed = true; } ); } /** * Returns current class loader or null if not yet created or set. * * @return class loader object or null. * * @see #createClassLoader() * @see #setClassLoader(CustomClassLoader) */ public CustomClassLoader getClassLoader() { return customClassLoader; } /** * Loads class for specified class name using current class loader. * *

Current class loader should be set and capable to load class using only * given class name. No other information such a location of .class files * is passed to class loader. * * @param className name of class to load * * @throws ClassNotFoundException if no bytecode found for specified class name * @throws Failure if current class loader is not specified; * or if class was actually loaded with different class loader * * @see #loadClass(String, String) */ public void loadClass(String className) throws ClassNotFoundException { if (customClassLoader == null) { throw new Failure("No current class loader defined"); } Class cls = Class.forName(className, true, customClassLoader); // ensure that class was loaded by current class loader if (cls.getClassLoader() != customClassLoader) { throw new Failure("Class was loaded by unexpected class loader: " + cls.getClassLoader()); } classObjects.add(cls); } /** * Loads class from .class file located into specified directory using * current class loader. * *

If there is no current class loader, then default class loader * is created using createClassLoader(). Parameter classDir * is passed to class loader using CustomClassLoader.setClassPath() * method before loading class. * * @param className name of class to load * @param classDir path to .class file location * * @throws ClassNotFoundException if no .class file found * for specified class name * @throws Failure if class was actually loaded with different class loader * * @see #loadClass(String) * @see CustomClassLoader#setClassPath(String) */ public void loadClass(String className, String classDir) throws ClassNotFoundException { if (customClassLoader == null) { createClassLoader(); } customClassLoader.setClassPath(classDir); loadClass(className); } /** * Forces GC to unload previously loaded classes by cleaning all references * to class loader with its loaded classes and eating memory. * * @return true if classes unloading has been detected or false otherwise * * @throws Failure if exception other than OutOfMemoryError * is thrown while eating memory * * @see #eatMemory() */ public boolean unloadClass(ExecutionController stresser) { is_reclaimed = false; // free references to class and class loader to be able for collecting by GC long waitTimeout = (customClassLoader == null) ? 0 : WAIT_TIMEOUT; classObjects.removeAllElements(); customClassLoader = null; // force class unloading by eating memory pool eatMemory(stresser); // give GC chance to run and wait for receiving reclaim notification long timeToFinish = System.currentTimeMillis() + waitTimeout; while (!is_reclaimed && System.currentTimeMillis() < timeToFinish) { if (!stresser.continueExecution()) { return false; } try { // suspend thread for a while Thread.sleep(WAIT_DELTA); } catch (InterruptedException e) { throw new Failure("Unexpected InterruptedException while class unloading: " + e); } } // force GC to unload marked class loader and its classes if (is_reclaimed) { Runtime.getRuntime().gc(); return true; } // class loader has not been reclaimed return false; } public boolean unloadClass() { Stresser stresser = new Stresser() { @Override public boolean continueExecution() { return true; } }; return unloadClass(stresser); } // Stresses memory by allocating arrays of bytes. public static void eatMemory(ExecutionController stresser) { GarbageUtils.eatMemory(stresser, 50, 1024, 2); } // Stresses memory by allocating arrays of bytes. public static void eatMemory() { Stresser stresser = new Stresser() { @Override public boolean continueExecution() { return true; } }; eatMemory(stresser); } }