Skip to content

Instantly share code, notes, and snippets.

@simonis
Last active November 7, 2024 17:03
Show Gist options
  • Save simonis/f9adae5ab077301dd2d96ac3d885e949 to your computer and use it in GitHub Desktop.
Save simonis/f9adae5ab077301dd2d96ac3d885e949 to your computer and use it in GitHub Desktop.
The HotSpot internal `sun.management` MBeans

Since JDK 5 the Oracle/OpenJDK support an internal sun.management.HotspotInternal MBean which exports the five HotspotClassLoading, HotspotCompilation, HotspotMemory, HotspotRuntime and HotspotThreading MBeans from the sun.management package. See ManagementFactoryHelper::registerInternalMBeans() for how and where these MBeans are registered. Notice that these MBeans are not installed by default in the platform MBeanServer, this has to be done manually with (see InternalMBeans.java in this Gist for a full example):

    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    mbs.createMBean("sun.management.HotspotInternal", null);

These MBeans mostly source their data from HotSpot's perfromace counters (see sun.management.VMManagementImpl::getInternalCounters() and jdk.internal.perf.Perf for how it is implemented). Notice that writing the hsperf counters might have perfromance impacts in some rare situations (you can read my investigations "Performance implications of -XX:+/-PerfDisableSharedMem" for more details).

JDK modularization

Since the modularization of the JDK in JDK 9, the sun.management package is not exported any more from the java.management module where it lives in. However it was still possible to use it (albeit at the cost of a " illegal reflective access" warning). Since the implementation of strong encapsualtion in JDK 17, the usage of the sun.management MBeans require the usage of the --add-exports java.management/sun.management=ALL-UNNAMED command line paramter.

JConsole

Using the sun.management MBeans in JConsole requires the usage of the J-Djconsole.showUnsupported=true command line paramter (and --add-exports java.management/sun.management=ALL-UNNAMED in the monitored target VM). You can then choose HotSpot MBeans from the Connection menue to create the internal HotSpot MBeans in the target VM.

core-libs-dev discussion about "why sun.management MBeans are not exported"

Following are the contents of an email I wrote to the core-libs-dev mailing list

I recently looked for an easy way to get the CPU time spent by JIT-compiler and GC threads in Java (e.g exported by IBM J9's JvmCpuMonitorMXBean [0]). An easy way to achieve this is in OpenJDK is by using the "sun.management.HotspotInternal" MBean which exports the "sun.management:type=HotspotThreading" MBean with the attributes InternalThreadCount/InternalThreadCpuTimes (among other useful HotSpot internal counters and metrics).

Up until JDK 16/17 the usage of the "sun.management.HotspotInternal" MBean was straightforward, although it resulted in an illegal reflective access warning since JDK 9. Since JDK 17 its usage requires the " --add-exports java.management/sun.management=ALL-UNNAMED" for the monitored JVM.

I wonder why "sun.management" was encapsulated in the first place? I understand that it is not an "officially supported" API, but I find it still quite useful. If we really don't want to export it, I wonder why we are maintaining it at all (we even have JTreg tests for it under "test/jdk/sun/management"), because in the current configuration it isn't particularly useful.

Notice that jconsole supports the "sun.management.HotspotInternal" MBean if started with "J-Djconsole.showUnsupported=true" and the "java.management" module kind of tries to support jconsole with the following exports:

exports sun.management to
    jdk.jconsole,
    jdk.management,
    jdk.management.agent;

However, that doesn't help even if jconsole monitors itself (for the general case, where jconsole monitors a different JVM process it can't work anyway). With JDK 16 and "--illegal-access=debug" we can see why:

WARNING: Illegal reflective access by sun.reflect.misc.Trampoline to
method sun.management.HotspotThreadMBean.getInternalThreadCount()
at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
at java.base/sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:260)
at java.management/com.sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.java:112)
at java.management/com.sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.java:46)
at java.management/com.sun.jmx.mbeanserver.MBeanIntrospector.invokeM(MBeanIntrospector.java:237)
at java.management/com.sun.jmx.mbeanserver.PerInterface.getAttribute(PerInterface.java:83)
at java.management/com.sun.jmx.mbeanserver.MBeanSupport.getAttribute(MBeanSupport.java:206)
at java.management/com.sun.jmx.mbeanserver.MBeanSupport.getAttributes(MBeanSupport.java:213)
at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.getAttributes(DefaultMBeanServerInterceptor.java:701)
at java.management/com.sun.jmx.mbeanserver.JmxMBeanServer.getAttributes(JmxMBeanServer.java:705)

Notice that sun.reflect.misc.Trampoline is loaded by a custom class loader (java.base/sun.reflect.misc.MethodUtil) and not considered part of the base module.

So to cut a long story short, I see several options:

  1. Publicly export sun.management and restore the JDK 8 (or pre JDK 16) behavior. This would certainly require some polishing (e.g. some of the corresponding JVM functionality has already been removed [1]) but I think it could still be quite useful.
  2. Port the useful functionality from the "sun.management" MBeans to corresponding "com.sun.management" MBeans and remove the "sun.management" MBeans.
  3. Remove the "sun.management" MBeans without substitution.

What do you think?

Thank you and best regards, Volker

[0] https://www.ibm.com/docs/en/sdk-java-technology/8?topic=interfaces-language-management

[1] https://bugs.openjdk.org/browse/JDK-8134607

You can read the whole thread but the overall feedback was that Oracle more or less favours option three from above, i.e. rather completely remove the internal mbeans than making them publicly available.

I think that's sad because these internal MBeans together with the hsperf counters offer a very low-overhead way of monitoring JVM internals which are not available from other sources.

Various other links and comments

import java.lang.management.ManagementFactory;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import com.sun.management.OperatingSystemMXBean;
public class InternalMBeans {
public static void main(String args[]) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.createMBean("sun.management.HotspotInternal", null);
for (ObjectName on : mbs.queryNames(null, null)) {
System.out.print(on + " | ");
MBeanInfo mbi = mbs.getMBeanInfo(on);
System.out.println(String.format("%s (%s) %s", mbi.getClassName(), mbi.getDescription(), mbi.getDescriptor()));
System.out.println("-----------------------------------------");
for (MBeanAttributeInfo ai : mbi.getAttributes()) {
System.out.println(String.format(" %s (%s) type=%s", ai.getName(), ai.getDescription(), ai.getType()));
}
System.out.println("=========================================");
}
ObjectName hotspotThreading = new ObjectName("sun.management:type=HotspotThreading");
System.out.println("== InternalThreadCount ==");
System.out.println(mbs.getAttribute(hotspotThreading, "InternalThreadCount"));
System.out.println("== InternalThreadCpuTimes ==");
System.out.println(mbs.getAttribute(hotspotThreading, "InternalThreadCpuTimes"));
System.out.println("== InternalThreadingCounters ==");
System.out.println(mbs.getAttribute(hotspotThreading, "InternalThreadingCounters"));
ObjectName hotspotCompilation = new ObjectName("sun.management:type=HotspotCompilation");
System.out.println("== CompilerThreadCount ==");
System.out.println(mbs.getAttribute(hotspotCompilation, "CompilerThreadCount"));
System.out.println("== TotalCompileCount ==");
System.out.println(mbs.getAttribute(hotspotCompilation, "TotalCompileCount"));
OperatingSystemMXBean osb = (OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean();
System.out.println(String.format("\nProcess CPU Time = %dms", osb.getProcessCpuTime() / 1_000_000));
System.console().readLine();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment