--- old/src/java.base/share/classes/java/nio/Bits.java 2020-03-13 22:36:05.168997822 +0800 +++ new/src/java.base/share/classes/java/nio/Bits.java 2020-03-13 22:36:04.979003395 +0800 @@ -26,10 +26,10 @@ package java.nio; import jdk.internal.access.JavaLangRefAccess; -import jdk.internal.access.JavaNioAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; +import jdk.internal.misc.VM.BufferPool; import java.util.concurrent.atomic.AtomicLong; @@ -210,7 +210,7 @@ assert cnt >= 0 && reservedMem >= 0 && totalCap >= 0; } - static final JavaNioAccess.BufferPool BUFFER_POOL = new JavaNioAccess.BufferPool() { + static final BufferPool BUFFER_POOL = new BufferPool() { @Override public String getName() { return "direct"; --- old/src/java.base/share/classes/java/nio/Buffer.java 2020-03-13 22:36:06.280965207 +0800 +++ new/src/java.base/share/classes/java/nio/Buffer.java 2020-03-13 22:36:06.080971073 +0800 @@ -30,6 +30,7 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.access.foreign.MemorySegmentProxy; import jdk.internal.misc.Unsafe; +import jdk.internal.misc.VM.BufferPool; import jdk.internal.vm.annotation.ForceInline; import java.util.Spliterator; @@ -758,7 +759,7 @@ SharedSecrets.setJavaNioAccess( new JavaNioAccess() { @Override - public JavaNioAccess.BufferPool getDirectBufferPool() { + public BufferPool getDirectBufferPool() { return Bits.BUFFER_POOL; } --- old/src/java.base/share/classes/jdk/internal/access/JavaNioAccess.java 2020-03-13 22:36:07.371933208 +0800 +++ new/src/java.base/share/classes/jdk/internal/access/JavaNioAccess.java 2020-03-13 22:36:07.173939015 +0800 @@ -26,20 +26,16 @@ package jdk.internal.access; import jdk.internal.access.foreign.MemorySegmentProxy; +import jdk.internal.misc.VM.BufferPool; import java.nio.Buffer; import java.nio.ByteBuffer; public interface JavaNioAccess { + /** - * Provides access to information on buffer usage. + * Used by {@code jdk.internal.misc.VM}. */ - interface BufferPool { - String getName(); - long getCount(); - long getTotalCapacity(); - long getMemoryUsed(); - } BufferPool getDirectBufferPool(); /** --- old/src/java.base/share/classes/jdk/internal/misc/VM.java 2020-03-13 22:36:08.453901473 +0800 +++ new/src/java.base/share/classes/jdk/internal/misc/VM.java 2020-03-13 22:36:08.254907310 +0800 @@ -27,10 +27,16 @@ import static java.lang.Thread.State.*; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Properties; +import jdk.internal.access.SharedSecrets; + +import sun.nio.ch.FileChannelImpl; + public class VM { // the init level when the VM is fully initialized @@ -414,4 +420,34 @@ * object class in the archived graph. */ public static native void initializeFromArchive(Class c); + + /** + * Provides access to information on buffer usage. + */ + public interface BufferPool { + String getName(); + long getCount(); + long getTotalCapacity(); + long getMemoryUsed(); + } + + private static class BufferPoolsHolder { + static final List BUFFER_POOLS; + + static { + ArrayList bufferPools = new ArrayList<>(3); + bufferPools.add(SharedSecrets.getJavaNioAccess().getDirectBufferPool()); + bufferPools.add(FileChannelImpl.getMappedBufferPool()); + bufferPools.add(FileChannelImpl.getSyncMappedBufferPool()); + + BUFFER_POOLS = Collections.unmodifiableList(bufferPools); + } + } + + /** + * @return the list of buffer pools. + */ + public static List getBufferPools() { + return BufferPoolsHolder.BUFFER_POOLS; + } } --- old/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java 2020-03-13 22:36:09.585868271 +0800 +++ new/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java 2020-03-13 22:36:09.395873844 +0800 @@ -45,11 +45,11 @@ import java.util.Objects; import jdk.internal.access.JavaIOFileDescriptorAccess; -import jdk.internal.access.JavaNioAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.ExtendedMapMode; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; +import jdk.internal.misc.VM.BufferPool; import jdk.internal.ref.Cleaner; import jdk.internal.ref.CleanerFactory; @@ -1160,8 +1160,8 @@ * Invoked by sun.management.ManagementFactoryHelper to create the management * interface for mapped buffers. */ - public static JavaNioAccess.BufferPool getMappedBufferPool() { - return new JavaNioAccess.BufferPool() { + public static BufferPool getMappedBufferPool() { + return new BufferPool() { @Override public String getName() { return "mapped"; @@ -1185,8 +1185,8 @@ * Invoked by sun.management.ManagementFactoryHelper to create the management * interface for sync mapped buffers. */ - public static JavaNioAccess.BufferPool getSyncMappedBufferPool() { - return new JavaNioAccess.BufferPool() { + public static BufferPool getSyncMappedBufferPool() { + return new BufferPool() { @Override public String getName() { return "mapped - 'non-volatile memory'"; --- old/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java 2020-03-13 22:36:10.724834864 +0800 +++ new/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java 2020-03-13 22:36:10.528840613 +0800 @@ -39,8 +39,9 @@ import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; -import jdk.internal.access.JavaNioAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.VM; +import jdk.internal.misc.VM.BufferPool; import java.util.ArrayList; import java.util.List; @@ -340,13 +341,8 @@ private static List bufferPools = null; public static synchronized List getBufferPoolMXBeans() { if (bufferPools == null) { - bufferPools = new ArrayList<>(2); - bufferPools.add(createBufferPoolMXBean(SharedSecrets.getJavaNioAccess() - .getDirectBufferPool())); - bufferPools.add(createBufferPoolMXBean(sun.nio.ch.FileChannelImpl - .getMappedBufferPool())); - bufferPools.add(createBufferPoolMXBean(sun.nio.ch.FileChannelImpl - .getSyncMappedBufferPool())); + bufferPools = new ArrayList<>(3); + VM.getBufferPools().forEach(pool -> bufferPools.add(createBufferPoolMXBean(pool))); } return bufferPools; } @@ -357,7 +353,7 @@ * Creates management interface for the given buffer pool. */ private static BufferPoolMXBean - createBufferPoolMXBean(final JavaNioAccess.BufferPool pool) + createBufferPoolMXBean(final BufferPool pool) { return new BufferPoolMXBean() { private volatile ObjectName objname; // created lazily --- old/src/jdk.jfr/share/classes/jdk/jfr/internal/instrument/JDKEvents.java 2020-03-13 22:36:11.813802924 +0800 +++ new/src/jdk.jfr/share/classes/jdk/jfr/internal/instrument/JDKEvents.java 2020-03-13 22:36:11.623808497 +0800 @@ -28,9 +28,13 @@ import java.util.ArrayList; import java.util.List; +import jdk.internal.misc.VM; +import jdk.internal.misc.VM.BufferPool; + import jdk.jfr.Event; import jdk.jfr.events.ActiveRecordingEvent; import jdk.jfr.events.ActiveSettingEvent; +import jdk.jfr.events.DirectMemoryStatisticsEvent; import jdk.jfr.events.ErrorThrownEvent; import jdk.jfr.events.ExceptionStatisticsEvent; import jdk.jfr.events.ExceptionThrownEvent; @@ -73,7 +77,8 @@ jdk.internal.event.SecurityPropertyModificationEvent.class, jdk.internal.event.TLSHandshakeEvent.class, jdk.internal.event.X509CertificateEvent.class, - jdk.internal.event.X509ValidationEvent.class + jdk.internal.event.X509ValidationEvent.class, + DirectMemoryStatisticsEvent.class }; // This is a list of the classes with instrumentation code that should be applied. @@ -90,6 +95,7 @@ private static final Class[] targetClasses = new Class[instrumentationClasses.length]; private static final JVM jvm = JVM.getJVM(); private static final Runnable emitExceptionStatistics = JDKEvents::emitExceptionStatistics; + private static final Runnable emitDirectMemoryStatistics = JDKEvents::emitDirectMemoryStatistics; private static boolean initializationTriggered; @SuppressWarnings("unchecked") @@ -104,6 +110,7 @@ } initializationTriggered = true; RequestEngine.addTrustedJDKHook(ExceptionStatisticsEvent.class, emitExceptionStatistics); + RequestEngine.addTrustedJDKHook(DirectMemoryStatisticsEvent.class, emitDirectMemoryStatistics); } } catch (Exception e) { Logger.log(LogTag.JFR_SYSTEM, LogLevel.WARN, "Could not initialize JDK events. " + e.getMessage()); @@ -160,5 +167,19 @@ public static void remove() { RequestEngine.removeHook(JDKEvents::emitExceptionStatistics); + RequestEngine.removeHook(emitDirectMemoryStatistics); + } + + private static final BufferPool DIRECT_BUFFER_POOL = VM.getBufferPools().stream() + .filter(p -> "direct".equals(p.getName())) + .findFirst().get(); + + private static void emitDirectMemoryStatistics() { + DirectMemoryStatisticsEvent e = new DirectMemoryStatisticsEvent(); + e.count = DIRECT_BUFFER_POOL.getCount(); + e.totalCapacity = DIRECT_BUFFER_POOL.getTotalCapacity(); + e.memoryUsed = DIRECT_BUFFER_POOL.getMemoryUsed(); + e.maxCapacity = VM.maxDirectMemory(); + e.commit(); } } --- old/src/jdk.jfr/share/conf/jfr/default.jfc 2020-03-13 22:36:12.895771189 +0800 +++ new/src/jdk.jfr/share/conf/jfr/default.jfc 2020-03-13 22:36:12.704776791 +0800 @@ -731,6 +731,10 @@ true + + true + 5 s + --- old/src/jdk.jfr/share/conf/jfr/profile.jfc 2020-03-13 22:36:13.982739307 +0800 +++ new/src/jdk.jfr/share/conf/jfr/profile.jfc 2020-03-13 22:36:13.785745085 +0800 @@ -731,6 +731,10 @@ true + + true + 5 s + --- old/test/lib/jdk/test/lib/jfr/EventNames.java 2020-03-13 22:36:15.113706135 +0800 +++ new/test/lib/jdk/test/lib/jfr/EventNames.java 2020-03-13 22:36:14.925711649 +0800 @@ -187,6 +187,7 @@ public final static String X509Certificate = PREFIX + "X509Certificate"; public final static String X509Validation = PREFIX + "X509Validation"; public final static String SecurityProperty = PREFIX + "SecurityPropertyModification"; + public final static String DirectMemoryStatistics = PREFIX + "DirectMemoryStatistics"; // Flight Recorder public final static String DumpReason = PREFIX + "DumpReason"; --- /dev/null 2018-06-17 23:18:20.806999507 +0800 +++ new/src/jdk.jfr/share/classes/jdk/jfr/events/DirectMemoryStatisticsEvent.java 2020-03-13 22:36:16.019679561 +0800 @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.jfr.events; + +import jdk.jfr.*; +import jdk.jfr.internal.Type; + +@Name(Type.EVENT_NAME_PREFIX + "DirectMemoryStatistics") +@Label("Direct Memory Statistics") +@Category({ "Java Application", "Statistics" }) +@Description("Statistics of direct memory") +public final class DirectMemoryStatisticsEvent extends AbstractJDKEvent { + + @Label("Count") + public long count; + + @Label("Total Capacity") + @DataAmount + public long totalCapacity; + + @Label("Memory Used") + @DataAmount + public long memoryUsed; + + @Label("Maximum Capacity") + @Description("Maximum direct memory capacity the process can use") + @DataAmount + public long maxCapacity; +} --- /dev/null 2018-06-17 23:18:20.806999507 +0800 +++ new/test/jdk/jdk/jfr/event/runtime/TestDirectMemoryStatisticsEvent.java 2020-03-13 22:36:17.221644307 +0800 @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.jfr.event.runtime; + +import java.nio.ByteBuffer; +import java.util.List; + +import jdk.internal.misc.VM; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @key jfr + * @requires vm.hasJFR + * @library /test/lib + * @modules java.base/jdk.internal.misc + * @run main/othervm -XX:MaxDirectMemorySize=128m jdk.jfr.event.runtime.TestDirectMemoryStatisticsEvent + * @run main/othervm jdk.jfr.event.runtime.TestDirectMemoryStatisticsEvent + */ +public class TestDirectMemoryStatisticsEvent { + + private static final String EVENT_PATH = EventNames.DirectMemoryStatistics; + + public static void main(String[] args) throws Throwable { + try (Recording recording = new Recording()) { + recording.enable(EVENT_PATH); + recording.start(); + int rounds = 16; + int size = 1 * 1024 * 1024; // 1M + for (int i = 0; i < rounds; i++) { + ByteBuffer.allocateDirect(size); + } + recording.stop(); + + List events = Events.fromRecording(recording); + Events.hasEvents(events); + + long count = 0; + long totalCapacity = 0; + long memoryUsed = 0; + + for (RecordedEvent event : events) { + System.out.println(event); + Asserts.assertTrue(Events.isEventType(event, EVENT_PATH), "Wrong event type"); + count = Math.max(count, Events.assertField(event, "count").getValue()); + totalCapacity = Math.max(totalCapacity, Events.assertField(event, "totalCapacity").getValue()); + memoryUsed = Math.max(memoryUsed, Events.assertField(event, "memoryUsed").getValue()); + Asserts.assertEquals(VM.maxDirectMemory(), Events.assertField(event, "maxCapacity").getValue()); + } + + Asserts.assertGreaterThanOrEqual(count, (long)rounds, "Too few count in statistics event"); + Asserts.assertGreaterThanOrEqual(totalCapacity, (long)(rounds * size), "Too few totalCapacity in statistics event"); + Asserts.assertGreaterThanOrEqual(memoryUsed, totalCapacity, "Too few memoryUsed in statistics event"); + } + } +}