A DESCRIPTION OF THE PROBLEM :
Iterating over all the mbeans in java is causing to the leak the caller classloader.
This used to work under Java8, but since Java9 is leaking (tested 9,10,11ea)
When using tools such as Eclipse Memory Analyzer I see the reason is FlightRecorderMXBeanImpl mbean.
This happens because it is calling ManagementSupport::getEventTypes()
which calls JDKEvents::initialize()
which calls FlightRecorder::addPeriodicEvent()
which causes a leak since the AccessControlContext is take a reference to the caller classloader.
The only way to mitigate is to make stuff like tomcat or classloader-leak-prevention to preload the mbean of flightrecorder to make sure it won't leak.
this happens whether flightrecorder is turned on or off.
REGRESSION : Last worked in version 8u172
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
see the example test case,
basically if query all the mbeans attributes, or just the flightrecorder one, it will leak the caller classloader
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
not leaking the classloader which just wanted to query all the mbean values.
ACTUAL -
leaking the classloader.
---------- BEGIN SOURCE ----------
import javax.management.*;
import java.lang.management.ManagementFactory;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.net.URLClassLoader;
public class TestLeak {
public static void main(String[] args) throws Exception {
// uncomment this line to mitigate:
//preinitializeFlightRecorderMXBeanImpl();
WeakReference<ClassLoader> loader = getLoader();
while (loader.get() != null) {
System.gc();
Thread.sleep(1000);
System.out.println("Not freed :(");
}
System.out.println("No leak!");
}
private static void preinitializeFlightRecorderMXBeanImpl() {
try {
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName beanName = ObjectName.getInstance("jdk.management.jfr:type=FlightRecorder");
MBeanInfo mBeanInfo = platformMBeanServer.getMBeanInfo(beanName);
for (MBeanAttributeInfo att : mBeanInfo.getAttributes()) {
platformMBeanServer.getAttribute(beanName, att.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void test() {
try {
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
for (ObjectName objectName : platformMBeanServer.queryNames(null, null)) {
final MBeanInfo mBeanInfo = platformMBeanServer.getMBeanInfo(objectName);
for (MBeanAttributeInfo att : mBeanInfo.getAttributes()) {
try {
final Object attValue = platformMBeanServer.getAttribute(objectName, att.getName());
} catch (RuntimeMBeanException e) {
// ignore
} catch (Exception e) {
e.printStackTrace();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static WeakReference<ClassLoader> getLoader() throws Exception {
URL url = TestLeak.class.getProtectionDomain().getCodeSource().getLocation();
URLClassLoader loader = new URLClassLoader(new URL[] {url}, null);
Class<?> workerClass = Class.forName("TestLeak", true, loader);
workerClass.getDeclaredMethod("test").invoke(null);
loader.close();
return new WeakReference<>(loader);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
do the function of preinitializeFlightRecorderMXBeanImpl() in your code before everything to workaround this leak
FREQUENCY : always