/* * Copyright (c) 2001, 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. */ /* * Warning! Using this component need VM option -XX:-UseGCOverheadLimit * */ package nsk.share; 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 considered unloaded if its class loader is finalized. * If there no finalization of class loader detected for some timeout, * class is considered still loaded and method returns false. * *

Such finalization 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 finalization. */ private static final int WAIT_TIMEOUT = 15000; /** * Piece of time in milliseconds to wait in a loop for class loader finalization. */ private static final int WAIT_DELTA = 1000; /** * Has class loader been finalized or not. */ volatile boolean finalized = 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(this); classObjects.removeAllElements(); 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(); customClassLoader.setClassUnloader(this); } /** * 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) { finalized = 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 finalization long timeToFinish = System.currentTimeMillis() + waitTimeout; while (!finalized && 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 (finalized) { Runtime.getRuntime().gc(); return true; } // class loader has not been finalized 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. * * Note that this method can throw Failure if any exception * is thrown while eating memory. To avoid OOM while allocating * exception we preallocate it before the lunch starts. It means * that exception stack trace does not correspond to the place * where exception is thrown, but points at start of the method. * * @throws Failure if exception other than OutOfMemoryError * is thrown while eating memory */ public static void eatMemory(ExecutionController stresser) { GarbageUtils.eatMemory(stresser, 50, 1024, 2); /* * System.runFinalization() may potentially fail with OOM. This is why * System.runFinalization() is repeated several times. */ for (int i = 0; i < 10; ++i) { try { if(!stresser.continueExecution()) { return; } System.runFinalization(); break; } catch (OutOfMemoryError e) { } } } /** * Stresses memory by allocating arrays of bytes. * * Note that this method can throw Failure if any exception * is thrown while eating memory. To avoid OOM while allocating * exception we preallocate it before the lunch starts. It means * that exception stack trace does not correspond to the place * where exception is thrown, but points at start of the method. * * @throws Failure if exception other than OutOfMemoryError * is thrown while eating memory */ public static void eatMemory() { Stresser stresser = new Stresser() { @Override public boolean continueExecution() { return true; } }; GarbageUtils.eatMemory(stresser, 50, 1024, 2); /* * System.runFinalization() may potentially fail with OOM. This is why * System.runFinalization() is repeated several times. */ for (int i = 0; i < 10; ++i) { try { if(!stresser.continueExecution()) { return; } System.runFinalization(); break; } catch (OutOfMemoryError e) { } } } }