jdk-24/test/hotspot/jtreg/vmTestbase/nsk/share/ClassUnloader.java
2023-03-17 13:21:47 +00:00

311 lines
11 KiB
Java

/*
* 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 <code>ClassUnloader</code> class allows to force VM to unload class(es)
* using memory stressing technique.
*
* <p>The method <code>unloadClass()</code> 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.
*
* <p>ClassUnloader mainly intends to unload a class which was loaded
* with especial <code>ClassUnloader.loadClass()</code> 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 <code>unloadClass()</code> returns <i>false</i>.
*
* <p>Such reclaiming control applies only to a class loaded by
* ClassUnloader's <code>loadClass()</code> method. Otherwise, if there
* was no such class loaded, <code>unloadClass()</code> doesn't wait
* for a timeout and always returns <i>false</i>.
*
* <p>By default internal class loader of <code>CustomClassLoader</code> 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
* <code>CustomClassLoader</code> class, and set by <code>setClassLoader()</code>
* 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<Class<?>> classObjects = new Vector<Class<?>>();
/**
* Class object of the first class been loaded with current class loader.
* To get the rest loaded classes use <code>getLoadedClass(int)</code>.
* The call <code>getLoadedClass()</code> is effectively equivalent to the call
* <code>getLoadedClass(0)</code>
*
* @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 <code>CustomClassLoader</code> class as the current
* class loader and clears the list of loaded classes.
*
* @return created instance of <code>CustomClassLoader</code> 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 <i>null</i> 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.
*
* <p>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.
*
* <p>If there is no current class loader, then default class loader
* is created using <code>createClassLoader()</code>. Parameter <i>classDir</i>
* is passed to class loader using <code>CustomClassLoader.setClassPath()</code>
* 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 <i>true</i> if classes unloading has been detected
or <i>false</i> 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);
}
}