8c238eddce
Reviewed-by: sspitsyn, dfuchs
337 lines
14 KiB
Java
337 lines
14 KiB
Java
/*
|
|
* Copyright (c) 2005, 2024, 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.
|
|
*/
|
|
|
|
/*
|
|
* @test
|
|
* @bug 8058865
|
|
* @summary Checks correct collection of MXBean's class after unregistration
|
|
* @requires vm.opt.final.ClassUnloading
|
|
* @author Olivier Lagneau
|
|
*
|
|
* @library /lib/testlibrary
|
|
*
|
|
* @run main/othervm/timeout=300 MXBeanLoadingTest1
|
|
*/
|
|
|
|
import java.lang.ref.WeakReference;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.util.Arrays;
|
|
import java.util.Map;
|
|
import javax.management.Attribute;
|
|
import javax.management.JMX;
|
|
import javax.management.MBeanAttributeInfo;
|
|
import javax.management.MBeanInfo;
|
|
import javax.management.MBeanOperationInfo;
|
|
import javax.management.MBeanServer;
|
|
import javax.management.MBeanServerFactory;
|
|
import javax.management.MXBean;
|
|
import javax.management.ObjectInstance;
|
|
import javax.management.ObjectName;
|
|
import javax.management.loading.PrivateClassLoader;
|
|
import javax.management.openmbean.CompositeData;
|
|
import javax.management.openmbean.CompositeDataSupport;
|
|
import javax.management.openmbean.CompositeType;
|
|
import javax.management.openmbean.OpenType;
|
|
import javax.management.openmbean.SimpleType;
|
|
|
|
public class MXBeanLoadingTest1 {
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
MXBeanLoadingTest1 test = new MXBeanLoadingTest1();
|
|
test.run((Map<String, Object>)null);
|
|
}
|
|
|
|
public void run(Map<String, Object> args) {
|
|
|
|
System.out.println("MXBeanLoadingTest1::run: Start") ;
|
|
|
|
try {
|
|
System.out.println("We ensure no reference is retained on MXBean class"
|
|
+ " after it is unregistered. We take time to perform"
|
|
+ " some little extra check of Descriptors, MBean*Info.");
|
|
|
|
ClassLoader myClassLoader = MXBeanLoadingTest1.class.getClassLoader();
|
|
if(myClassLoader == null)
|
|
throw new RuntimeException("Test Failed : Null Classloader for test");
|
|
URL url = myClassLoader.getResource(
|
|
MXBeanLoadingTest1.class.getCanonicalName()
|
|
.replace(".", "/") + ".class");
|
|
String clsLoadPath = url.toURI().toString().
|
|
replaceAll(MXBeanLoadingTest1.class.getSimpleName()
|
|
+ ".class", "");
|
|
|
|
URL[] urls = new URL[]{new URL(clsLoadPath)};
|
|
Loader loader = new Loader(urls);
|
|
Class<?> shadowClass = loader.loadClass(TestMXBean.class.getName());
|
|
|
|
if (shadowClass == TestMXBean.class) {
|
|
String message = "(ERROR) Loader got original TestMXBean, not shadow";
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
shadowClass = null;
|
|
|
|
MBeanServer mbs = MBeanServerFactory.createMBeanServer();
|
|
ObjectName loaderName = new ObjectName("x:type=myloader");
|
|
mbs.registerMBean(loader, loaderName);
|
|
|
|
ObjectName testName = new ObjectName("x:type=test");
|
|
mbs.createMBean(Test.class.getName(), testName, loaderName);
|
|
|
|
// That test fails because the MXBean instance is accessed via
|
|
// a delegate OpenMBean which has
|
|
ClassLoader testLoader = mbs.getClassLoaderFor(testName);
|
|
|
|
if (testLoader != loader) {
|
|
System.out.println("Loader " + loader);
|
|
String message = "(ERROR) MXBean's class loader is not Loader: "
|
|
+ testLoader;
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
testLoader = null;
|
|
|
|
// Cycle get/set/get of the attribute of type Luis.
|
|
// We check the set is effective.
|
|
CompositeData cd_B = (CompositeData)mbs.getAttribute(testName, "B");
|
|
CompositeType compType_B = cd_B.getCompositeType();
|
|
|
|
CompositeDataSupport cds_B =
|
|
new CompositeDataSupport(compType_B,
|
|
new String[]{"something"},
|
|
new Object[]{Integer.valueOf(13)});
|
|
Attribute myAtt = new Attribute("B", cds_B);
|
|
mbs.setAttribute(testName, myAtt);
|
|
|
|
CompositeData cd_B2 = (CompositeData)mbs.getAttribute(testName, "B");
|
|
|
|
if ( ((Integer)cd_B2.get("something")).intValue() != 13 ) {
|
|
String message = "(ERROR) The setAttribute of att B did not work;"
|
|
+ " expect Luis.something = 13 but got "
|
|
+ cd_B2.get("something");
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
MBeanInfo info = mbs.getMBeanInfo(testName);
|
|
String mxbeanField =
|
|
(String)info.getDescriptor().getFieldValue(JMX.MXBEAN_FIELD);
|
|
|
|
if ( mxbeanField == null || ! mxbeanField.equals("true")) {
|
|
String message = "(ERROR) Improper mxbean field value "
|
|
+ mxbeanField;
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
// Check the 2 attributes.
|
|
MBeanAttributeInfo[] attrs = info.getAttributes();
|
|
|
|
if ( attrs.length == 2 ) {
|
|
for (MBeanAttributeInfo mbai : attrs) {
|
|
String originalTypeFieldValue =
|
|
(String)mbai.getDescriptor().getFieldValue(JMX.ORIGINAL_TYPE_FIELD);
|
|
OpenType<?> openTypeFieldValue =
|
|
(OpenType<?>)mbai.getDescriptor().getFieldValue(JMX.OPEN_TYPE_FIELD);
|
|
|
|
if ( mbai.getName().equals("A") ) {
|
|
if ( !mbai.isReadable() || !mbai.isWritable()
|
|
|| mbai.isIs()
|
|
|| !mbai.getType().equals("int") ) {
|
|
String message = "(ERROR) Unexpected MBeanAttributeInfo for A "
|
|
+ mbai;
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
if ( ! originalTypeFieldValue.equals("int") ) {
|
|
String message = "(ERROR) Unexpected originalType in Descriptor for A "
|
|
+ originalTypeFieldValue;
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
if ( ! openTypeFieldValue.equals(SimpleType.INTEGER) ) {
|
|
String message = "(ERROR) Unexpected openType in Descriptor for A "
|
|
+ originalTypeFieldValue;
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
} else if ( mbai.getName().equals("B") ) {
|
|
if ( !mbai.isReadable() || !mbai.isWritable()
|
|
|| mbai.isIs()
|
|
|| !mbai.getType().equals("javax.management.openmbean.CompositeData") ) {
|
|
String message = "(ERROR) Unexpected MBeanAttributeInfo for B "
|
|
+ mbai;
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
if ( ! originalTypeFieldValue.equals(Luis.class.getName()) ) {
|
|
String message = "(ERROR) Unexpected originalType in Descriptor for B "
|
|
+ originalTypeFieldValue;
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
if ( ! openTypeFieldValue.equals(compType_B) ) {
|
|
String message = "(ERROR) Unexpected openType in Descriptor for B "
|
|
+ compType_B;
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
} else {
|
|
String message = "(ERROR) Unknown attribute name";
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
}
|
|
} else {
|
|
String message = "(ERROR) Unexpected MBeanAttributeInfo array"
|
|
+ Arrays.deepToString(attrs);
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
// Check the MXBean operation.
|
|
MBeanOperationInfo[] ops = info.getOperations();
|
|
// The impact is ACTION_INFO as for a standard MBean it is UNKNOWN,
|
|
// logged 6320104.
|
|
if (ops.length != 1 || !ops[0].getName().equals("bogus")
|
|
|| ops[0].getSignature().length > 0
|
|
|| !ops[0].getReturnType().equals("void")) {
|
|
String message = "(ERROR) Unexpected MBeanOperationInfo array "
|
|
+ Arrays.deepToString(ops);
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
String originalTypeFieldValue =
|
|
(String)ops[0].getDescriptor().getFieldValue(JMX.ORIGINAL_TYPE_FIELD);
|
|
OpenType<?> openTypeFieldValue =
|
|
(OpenType<?>)ops[0].getDescriptor().getFieldValue(JMX.OPEN_TYPE_FIELD);
|
|
|
|
if ( ! originalTypeFieldValue.equals("void") ) {
|
|
String message = "(ERROR) Unexpected originalType in Descriptor for bogus "
|
|
+ originalTypeFieldValue;
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
if ( ! openTypeFieldValue.equals(SimpleType.VOID) ) {
|
|
String message = "(ERROR) Unexpected openType in Descriptor for bogus "
|
|
+ originalTypeFieldValue;
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
// Check there is 2 constructors.
|
|
if (info.getConstructors().length != 2) {
|
|
String message = "(ERROR) Wrong number of constructors " +
|
|
"in introspected bean: " +
|
|
Arrays.asList(info.getConstructors());
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
// Check MXBean class name.
|
|
if (!info.getClassName().endsWith("Test")) {
|
|
String message = "(ERROR) Wrong info class name: " +
|
|
info.getClassName();
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
WeakReference<Loader> loaderRef = new WeakReference<>(loader);
|
|
mbs.unregisterMBean(testName);
|
|
mbs.unregisterMBean(loaderName);
|
|
loader = null;
|
|
|
|
System.out.println("MXBean registered and unregistered, waiting for " +
|
|
"garbage collector to collect class loader");
|
|
|
|
for (int i = 0; i < 10000 && loaderRef.get() != null; i++) {
|
|
System.gc();
|
|
Thread.sleep(1);
|
|
}
|
|
|
|
if (loaderRef.get() == null)
|
|
System.out.println("(OK) class loader was GC'd");
|
|
else {
|
|
String message = "(ERROR) Class loader was not GC'd";
|
|
System.out.println(message);
|
|
throw new RuntimeException(message);
|
|
}
|
|
} catch(Exception e) {
|
|
Utils.printThrowable(e, true) ;
|
|
throw new RuntimeException(e);
|
|
}
|
|
|
|
System.out.println("MXBeanLoadingTest1::run: Done without any error") ;
|
|
}
|
|
|
|
public static interface LoaderMBean {
|
|
}
|
|
|
|
public static class Loader extends URLClassLoader implements LoaderMBean, PrivateClassLoader {
|
|
public Loader(URL[] urls) {
|
|
super(urls, null);
|
|
}
|
|
}
|
|
|
|
// I agree the use of the MXBean annotation and the MXBean suffix for the
|
|
// interface name are redundant but however harmless.
|
|
//
|
|
@MXBean(true)
|
|
public static interface TestMXBean {
|
|
public void bogus();
|
|
public int getA();
|
|
public void setA(int a);
|
|
public Luis getB();
|
|
public void setB(Luis mi);
|
|
}
|
|
|
|
|
|
public static class Test implements TestMXBean {
|
|
private Luis luis = new Luis() ;
|
|
public Test() {}
|
|
public Test(int x) {}
|
|
|
|
public void bogus() {}
|
|
public int getA() {return 0;}
|
|
public void setA(int a) {}
|
|
public Luis getB() {return this.luis;}
|
|
public void setB(Luis luis) {this.luis = luis;}
|
|
}
|
|
|
|
|
|
public static class Luis {
|
|
private int something = 0;
|
|
public Luis() {}
|
|
public int getSomething() {return something;}
|
|
public void setSomething(int v) {something = v;}
|
|
public void doNothing() {}
|
|
}
|
|
}
|