6682020: (bf) Support monitoring of direct and mapped buffer usage
authoralanb
Tue, 26 Aug 2008 10:21:22 +0100
changeset 1143 645d4b930f93
parent 1142 e01e390f6551
child 1144 7a9035ddf8e2
6682020: (bf) Support monitoring of direct and mapped buffer usage Reviewed-by: mchung, iris
jdk/make/java/java/FILES_java.gmk
jdk/make/java/nio/FILES_java.gmk
jdk/src/share/classes/java/lang/management/PlatformComponent.java
jdk/src/share/classes/java/nio/Bits.java
jdk/src/share/classes/java/nio/BufferPoolMXBean.java
jdk/src/share/classes/java/nio/Direct-X-Buffer.java
jdk/src/share/classes/sun/misc/JavaNioAccess.java
jdk/src/share/classes/sun/misc/SharedSecrets.java
jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java
jdk/test/java/nio/BufferPoolMXBean/Basic.java
--- a/jdk/make/java/java/FILES_java.gmk	Tue Aug 26 09:23:12 2008 +0100
+++ b/jdk/make/java/java/FILES_java.gmk	Tue Aug 26 10:21:22 2008 +0100
@@ -449,6 +449,7 @@
     sun/misc/JavaLangAccess.java \
     sun/misc/JavaIOAccess.java \
     sun/misc/JavaIODeleteOnExitAccess.java \
-    sun/misc/JavaIOFileDescriptorAccess.java
+    sun/misc/JavaIOFileDescriptorAccess.java \
+    sun/misc/JavaNioAccess.java
 
 FILES_java = $(JAVA_JAVA_java)
--- a/jdk/make/java/nio/FILES_java.gmk	Tue Aug 26 09:23:12 2008 +0100
+++ b/jdk/make/java/nio/FILES_java.gmk	Tue Aug 26 10:21:22 2008 +0100
@@ -26,6 +26,7 @@
 FILES_src = \
 	java/nio/Bits.java \
 	java/nio/Buffer.java \
+	java/nio/BufferPoolMXBean.java \
 	java/nio/ByteOrder.java \
 	java/nio/MappedByteBuffer.java \
 	java/nio/StringCharBuffer.java \
--- a/jdk/src/share/classes/java/lang/management/PlatformComponent.java	Tue Aug 26 09:23:12 2008 +0100
+++ b/jdk/src/share/classes/java/lang/management/PlatformComponent.java	Tue Aug 26 10:21:22 2008 +0100
@@ -32,6 +32,7 @@
 import java.util.Set;
 import java.util.logging.LoggingMXBean;
 import java.util.logging.LogManager;
+import java.nio.BufferPoolMXBean;
 import javax.management.MBeanServerConnection;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
@@ -188,6 +189,23 @@
             }
         }),
 
+
+    /**
+     * Buffer pools.
+     */
+    BUFFER_POOL(
+        "java.nio.BufferPoolMXBean",
+        "java.nio", "BufferPool", keyProperties("name"),
+        new MXBeanFetcher<BufferPoolMXBean>() {
+            public List<BufferPoolMXBean> getMXBeans() {
+                List<BufferPoolMXBean> pools = new ArrayList<BufferPoolMXBean>(2);
+                pools.add( sun.misc.SharedSecrets.getJavaNioAccess().getDirectBufferPoolMXBean() );
+                pools.add( sun.nio.ch.FileChannelImpl.getMappedBufferPoolMXBean() );
+                return pools;
+            }
+        }),
+
+
     // Sun Platform Extension
 
     /**
--- a/jdk/src/share/classes/java/nio/Bits.java	Tue Aug 26 09:23:12 2008 +0100
+++ b/jdk/src/share/classes/java/nio/Bits.java	Tue Aug 26 10:21:22 2008 +0100
@@ -29,6 +29,8 @@
 import java.security.PrivilegedAction;
 import sun.misc.Unsafe;
 import sun.misc.VM;
+import javax.management.ObjectName;
+import javax.management.MalformedObjectNameException;
 
 /**
  * Access to bits, native and otherwise.
@@ -625,13 +627,15 @@
     // direct buffer memory.  This value may be changed during VM
     // initialization if it is launched with "-XX:MaxDirectMemorySize=<size>".
     private static volatile long maxMemory = VM.maxDirectMemory();
-    private static volatile long reservedMemory = 0;
+    private static volatile long reservedMemory;
+    private static volatile long usedMemory;
+    private static volatile long count;
     private static boolean memoryLimitSet = false;
 
     // These methods should be called whenever direct memory is allocated or
     // freed.  They allow the user to control the amount of direct memory
     // which a process may access.  All sizes are specified in bytes.
-    static void reserveMemory(long size) {
+    static void reserveMemory(long size, int cap) {
 
         synchronized (Bits.class) {
             if (!memoryLimitSet && VM.isBooted()) {
@@ -640,6 +644,8 @@
             }
             if (size <= maxMemory - reservedMemory) {
                 reservedMemory += size;
+                usedMemory += cap;
+                count++;
                 return;
             }
         }
@@ -655,17 +661,71 @@
             if (reservedMemory + size > maxMemory)
                 throw new OutOfMemoryError("Direct buffer memory");
             reservedMemory += size;
+            usedMemory += cap;
+            count++;
         }
 
     }
 
-    static synchronized void unreserveMemory(long size) {
+    static synchronized void unreserveMemory(long size, int cap) {
         if (reservedMemory > 0) {
             reservedMemory -= size;
+            usedMemory -= cap;
+            count--;
             assert (reservedMemory > -1);
         }
     }
 
+    // -- Management interface for monitoring of direct buffer usage --
+
+    static {
+        // setup access to this package in SharedSecrets
+        sun.misc.SharedSecrets.setJavaNioAccess(
+            new sun.misc.JavaNioAccess() {
+                @Override
+                public BufferPoolMXBean getDirectBufferPoolMXBean() {
+                    return LazyInitialization.directBufferPoolMXBean;
+                }
+            }
+        );
+    }
+
+    // Lazy initialization of management interface
+    private static class LazyInitialization {
+        static final BufferPoolMXBean directBufferPoolMXBean = directBufferPoolMXBean();
+
+        private static BufferPoolMXBean directBufferPoolMXBean() {
+            final String pool = "direct";
+            final ObjectName obj;
+            try {
+                obj = new ObjectName("java.nio:type=BufferPool,name=" + pool);
+            } catch (MalformedObjectNameException x) {
+                throw new AssertionError(x);
+            }
+            return new BufferPoolMXBean() {
+                @Override
+                public ObjectName getObjectName() {
+                    return obj;
+                }
+                @Override
+                public String getName() {
+                    return pool;
+                }
+                @Override
+                public long getCount() {
+                    return Bits.count;
+                }
+                @Override
+                public long getTotalCapacity() {
+                    return Bits.usedMemory;
+                }
+                @Override
+                public long getMemoryUsed() {
+                    return Bits.reservedMemory;
+                }
+            };
+        }
+    }
 
     // -- Bulk get/put acceleration --
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/java/nio/BufferPoolMXBean.java	Tue Aug 26 10:21:22 2008 +0100
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2007-2008 Sun Microsystems, Inc.  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.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package java.nio;
+
+import java.lang.management.PlatformManagedObject;
+
+/**
+ * The management interface for a buffer pool.
+ *
+ * <p> A class implementing this interface is an <a href=
+ * "java.lang.management.ManagementFactory.html#MXBean">MXBean</a>. A Java
+ * virtual machine has one or more implementations of this interface. The {@link
+ * java.lang.management.ManagementFactory#getPlatformMXBeans getPlatformMXBeans}
+ * method can be used to obtain the list of {@code BufferPoolMXBean} objects
+ * representing the management interfaces for pools of buffers as follows:
+ * <pre>
+ *     List&lt;BufferPoolMXBean&gt; pools = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class);
+ * </pre>
+ *
+ * <p> The management interfaces are also registered with the platform {@link
+ * javax.management.MBeanServer MBeanServer}. The {@link
+ * javax.management.ObjectName ObjectName} that uniquely identifies the
+ * management interface within the {@code MBeanServer} takes the form:
+ * <blockquote>
+ *    <tt>java.nio:type=BufferPool</tt><tt>,name=</tt><i>pool name</i>
+ * </blockquote>
+ * where <em>pool name</em> is the {@link #getName name} of the buffer pool.
+ *
+ * @since   1.7
+ */
+
+public interface BufferPoolMXBean extends PlatformManagedObject {
+
+    /**
+     * Returns the name representing this buffer pool.
+     *
+     * @return  The name of this buffer pool.
+     */
+    String getName();
+
+    /**
+     * Returns an estimate of the number of buffers in the pool.
+     *
+     * @return  An estimate of the number of buffers in this pool
+     */
+    long getCount();
+
+    /**
+     * Returns an estimate of the total capacity of the buffers in this pool.
+     * A buffer's capacity is the number of elements it contains and the value
+     * returned by this method is an estimate of the total capacity of buffers
+     * in the pool in bytes.
+     *
+     * @return  An estimate of the total capacity of the buffers in this pool
+     *          in bytes
+     */
+    long getTotalCapacity();
+
+    /**
+     * Returns an estimate of the memory that the Java virtual machine is using
+     * for this buffer pool. The value returned by this method may differ
+     * from the estimate of the total {@link #getTotalCapacity capacity} of
+     * the buffers in this pool. This difference is explained by alignment,
+     * memory allocator, and other implementation specific reasons.
+     *
+     * @return  An estimate of the memory that the Java virtual machine is using
+     *          for this buffer pool in bytes, or {@code -1L} if an estimate of
+     *          the memory usage is not available
+     */
+    long getMemoryUsed();
+}
--- a/jdk/src/share/classes/java/nio/Direct-X-Buffer.java	Tue Aug 26 09:23:12 2008 +0100
+++ b/jdk/src/share/classes/java/nio/Direct-X-Buffer.java	Tue Aug 26 10:21:22 2008 +0100
@@ -71,11 +71,13 @@
         private static Unsafe unsafe = Unsafe.getUnsafe();
 
         private long address;
+        private long size;
         private int capacity;
 
-        private Deallocator(long address, int capacity) {
+        private Deallocator(long address, long size, int capacity) {
             assert (address != 0);
             this.address = address;
+            this.size = size;
             this.capacity = capacity;
         }
 
@@ -86,7 +88,7 @@
             }
             unsafe.freeMemory(address);
             address = 0;
-            Bits.unreserveMemory(capacity);
+            Bits.unreserveMemory(size, capacity);
         }
 
     }
@@ -110,23 +112,25 @@
     Direct$Type$Buffer$RW$(int cap) {                   // package-private
 #if[rw]
         super(-1, 0, cap, cap, false);
-        Bits.reserveMemory(cap);
         int ps = Bits.pageSize();
+        int size = cap + ps;
+        Bits.reserveMemory(size, cap);
+
         long base = 0;
         try {
-            base = unsafe.allocateMemory(cap + ps);
+            base = unsafe.allocateMemory(size);
         } catch (OutOfMemoryError x) {
-            Bits.unreserveMemory(cap);
+            Bits.unreserveMemory(size, cap);
             throw x;
         }
-        unsafe.setMemory(base, cap + ps, (byte) 0);
+        unsafe.setMemory(base, size, (byte) 0);
         if (base % ps != 0) {
             // Round up to page boundary
             address = base + ps - (base & (ps - 1));
         } else {
             address = base;
         }
-        cleaner = Cleaner.create(this, new Deallocator(base, cap));
+        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
 #else[rw]
         super(cap);
 #end[rw]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/misc/JavaNioAccess.java	Tue Aug 26 10:21:22 2008 +0100
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2007-2008 Sun Microsystems, Inc.  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.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.misc;
+
+import java.nio.BufferPoolMXBean;
+
+public interface JavaNioAccess {
+    BufferPoolMXBean getDirectBufferPoolMXBean();
+}
--- a/jdk/src/share/classes/sun/misc/SharedSecrets.java	Tue Aug 26 09:23:12 2008 +0100
+++ b/jdk/src/share/classes/sun/misc/SharedSecrets.java	Tue Aug 26 10:21:22 2008 +0100
@@ -46,6 +46,7 @@
     private static JavaIOAccess javaIOAccess;
     private static JavaIODeleteOnExitAccess javaIODeleteOnExitAccess;
     private static JavaNetAccess javaNetAccess;
+    private static JavaNioAccess javaNioAccess;
     private static JavaIOFileDescriptorAccess javaIOFileDescriptorAccess;
 
     public static JavaUtilJarAccess javaUtilJarAccess() {
@@ -77,6 +78,20 @@
         return javaNetAccess;
     }
 
+    public static void setJavaNioAccess(JavaNioAccess jna) {
+        javaNioAccess = jna;
+    }
+
+    public static JavaNioAccess getJavaNioAccess() {
+        if (javaNioAccess == null) {
+            // Ensure java.nio.ByteOrder is initialized; we know that
+            // this class initializes java.nio.Bits that provides the
+            // shared secret.
+            unsafe.ensureClassInitialized(java.nio.ByteOrder.class);
+        }
+        return javaNioAccess;
+    }
+
     public static void setJavaIOAccess(JavaIOAccess jia) {
         javaIOAccess = jia;
     }
--- a/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java	Tue Aug 26 09:23:12 2008 +0100
+++ b/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java	Tue Aug 26 10:21:22 2008 +0100
@@ -32,6 +32,7 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.MappedByteBuffer;
+import java.nio.BufferPoolMXBean;
 import java.nio.channels.*;
 import java.nio.channels.spi.*;
 import java.util.ArrayList;
@@ -43,10 +44,12 @@
 import java.lang.reflect.Field;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import javax.management.ObjectName;
+import javax.management.MalformedObjectNameException;
+
 import sun.misc.Cleaner;
 import sun.security.action.GetPropertyAction;
 
-
 public class FileChannelImpl
     extends FileChannel
 {
@@ -681,14 +684,26 @@
     private static class Unmapper
         implements Runnable
     {
+        // keep track of mapped buffer usage
+        static volatile int count;
+        static volatile long totalSize;
+        static volatile long totalCapacity;
 
         private long address;
         private long size;
+        private int cap;
 
-        private Unmapper(long address, long size) {
+        private Unmapper(long address, long size, int cap) {
             assert (address != 0);
             this.address = address;
             this.size = size;
+            this.cap = cap;
+
+            synchronized (Unmapper.class) {
+                count++;
+                totalSize += size;
+                totalCapacity += cap;
+            }
         }
 
         public void run() {
@@ -696,8 +711,13 @@
                 return;
             unmap0(address, size);
             address = 0;
+
+            synchronized (Unmapper.class) {
+                count--;
+                totalSize -= size;
+                totalCapacity -= cap;
+            }
         }
-
     }
 
     private static void unmap(MappedByteBuffer bb) {
@@ -786,7 +806,7 @@
             assert (IOStatus.checkAll(addr));
             assert (addr % allocationGranularity == 0);
             int isize = (int)size;
-            Unmapper um = new Unmapper(addr, size + pagePosition);
+            Unmapper um = new Unmapper(addr, size + pagePosition, isize);
             if ((!writable) || (imode == MAP_RO))
                 return Util.newMappedByteBufferR(isize, addr + pagePosition, um);
             else
@@ -797,6 +817,49 @@
         }
     }
 
+    /**
+     * Returns the management interface for mapped buffers
+     */
+    public static BufferPoolMXBean getMappedBufferPoolMXBean() {
+        return LazyInitialization.mappedBufferPoolMXBean;
+    }
+
+    // Lazy initialization of management interface
+    private static class LazyInitialization {
+        static final BufferPoolMXBean mappedBufferPoolMXBean = mappedBufferPoolMXBean();
+
+        private static BufferPoolMXBean mappedBufferPoolMXBean() {
+            final String pool = "mapped";
+            final ObjectName obj;
+            try {
+                obj = new ObjectName("java.nio:type=BufferPool,name=" + pool);
+            } catch (MalformedObjectNameException x) {
+                throw new AssertionError(x);
+            }
+            return new BufferPoolMXBean() {
+                @Override
+                public ObjectName getObjectName() {
+                    return obj;
+                }
+                @Override
+                public String getName() {
+                    return pool;
+                }
+                @Override
+                public long getCount() {
+                    return Unmapper.count;
+                }
+                @Override
+                public long getTotalCapacity() {
+                    return Unmapper.totalCapacity;
+                }
+                @Override
+                public long getMemoryUsed() {
+                    return Unmapper.totalSize;
+                }
+            };
+        }
+    }
 
     // -- Locks --
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/nio/BufferPoolMXBean/Basic.java	Tue Aug 26 10:21:22 2008 +0100
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2007-2008 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/* @test
+ * @bug 6606598
+ * @summary Unit test for java.nio.BufferPoolMXBean
+ */
+
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.BufferPoolMXBean;
+import java.nio.channels.FileChannel;
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.lang.management.ManagementFactory;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import java.util.*;
+
+public class Basic {
+
+    // static fields to ensure buffers aren't GC'ed
+    static List<ByteBuffer> buffers;
+    static MappedByteBuffer mbb;
+
+    // check counters
+    static void check(List<BufferPoolMXBean> pools,
+                      int minBufferCount,
+                      long minTotalCapacity)
+    {
+        int bufferCount = 0;
+        long totalCap = 0;
+        long totalMem = 0;
+        for (BufferPoolMXBean pool: pools) {
+            bufferCount += pool.getCount();
+            totalCap += pool.getTotalCapacity();
+            totalMem += pool.getMemoryUsed();
+        }
+        if (bufferCount < minBufferCount)
+            throw new RuntimeException("Count less than expected");
+        if (totalMem < minTotalCapacity)
+            throw new RuntimeException("Memory usage less than expected");
+        if (totalCap < minTotalCapacity)
+            throw new RuntimeException("Total capacity less than expected");
+    }
+
+    public static void main(String[] args) throws Exception {
+        Random rand = new Random();
+
+        // allocate a few direct buffers
+        int bufferCount = 5 + rand.nextInt(20);
+        buffers = new ArrayList<ByteBuffer>(bufferCount);
+        long totalCapacity = 0L;
+        for (int i=0; i<bufferCount; i++) {
+            int cap = 1024 + rand.nextInt(4096);
+            buffers.add( ByteBuffer.allocateDirect(cap) );
+            totalCapacity += cap;
+        }
+
+        // map a file
+        File f = File.createTempFile("blah", null);
+        f.deleteOnExit();
+        RandomAccessFile raf = new RandomAccessFile(f, "rw");
+        FileChannel fc = raf.getChannel();
+        mbb = fc.map(FileChannel.MapMode.READ_WRITE, 10, 100);
+        bufferCount++;
+        totalCapacity += mbb.capacity();
+
+        // direct
+        List<BufferPoolMXBean> pools =
+            ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class);
+        check(pools, bufferCount, totalCapacity);
+
+        // using MBeanServer
+        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+        Set<ObjectName> mbeans = server.queryNames(
+            new ObjectName("java.nio:type=BufferPool,*"), null);
+        pools = new ArrayList<BufferPoolMXBean>();
+        for (ObjectName name: mbeans) {
+            BufferPoolMXBean pool = ManagementFactory
+                .newPlatformMXBeanProxy(server, name.toString(), BufferPoolMXBean.class);
+            pools.add(pool);
+        }
+        check(pools, bufferCount, totalCapacity);
+    }
+}