/*
* 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 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 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 VectorgetLoadedClass(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.
*
* 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) {
}
}
}
}