JDK-8207829 : FlightRecorderMXBeanImpl is leaking the first classloader which calls it
  • Type: Bug
  • Component: hotspot
  • Sub-Component: jfr
  • Affected Version: 9,10,11,12
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2018-07-16
  • Updated: 2020-04-27
  • Resolved: 2018-12-07
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 12 Other
11.0.3-oracleFixed 12 b24Fixed openjdk8u262Fixed
Related Reports
Relates :  
Description
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



Comments
Replacing jdk8u-fix-request with link to JDK-8239140
17-02-2020

RFC: https://mail.openjdk.java.net/pipermail/jdk8u-dev/2020-January/011063.html
30-01-2020

Fix Request Backporting this patch makes JFR more reliable. Patch applies cleanly on 11u, but deceptively so: it removes one import that is still needed in 11u (this one: http://hg.openjdk.java.net/jdk/jdk/rev/325c95779368#l2.15), reinstating that import makes JDK buildable. Non-patched 11u fails the new regression test, patched 11u passes it. Patched 11u also passes entire jdk_jfr suite.
18-02-2019

ManagementSupport::getEventTypes() was created so JMX clients would be able to browse/inspect JFR capabilities without actually loading JFR by calling FlightRecorder::getFlightRecorder The AcessControlContext should however not be needed for events defined in the JDK. I thought null was used for ACC for JDK events, but that doesn't seem to be the case.I will look into it.
19-07-2018

This is regression started from 9 ea b122 onwards. 8u172 - Pass 9 ea b121 - Pass 9 ea b122 - Fail <-- Regression introduced here 9 GA - Fail 10 GA - Fail 11 ea b22 - Fail 12 ea b02 - Fail
19-07-2018