jdk/src/share/classes/sun/tools/jconsole/ProxyClient.java
changeset 2 90ce3da70b43
child 697 fb86c99b92a2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/tools/jconsole/ProxyClient.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1061 @@
+/*
+ * Copyright 2004-2007 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.tools.jconsole;
+
+import com.sun.management.HotSpotDiagnosticMXBean;
+import com.sun.tools.jconsole.JConsoleContext;
+import com.sun.tools.jconsole.JConsoleContext.ConnectionState;
+import java.awt.Component;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeEvent;
+import java.io.IOException;
+import java.lang.management.*;
+import static java.lang.management.ManagementFactory.*;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.*;
+import java.rmi.*;
+import java.rmi.registry.*;
+import java.rmi.server.*;
+import java.util.*;
+import javax.management.*;
+import javax.management.remote.*;
+import javax.management.remote.rmi.*;
+import javax.rmi.ssl.SslRMIClientSocketFactory;
+import javax.swing.event.SwingPropertyChangeSupport;
+import sun.rmi.server.UnicastRef2;
+import sun.rmi.transport.LiveRef;
+
+public class ProxyClient implements JConsoleContext {
+
+    private ConnectionState connectionState = ConnectionState.DISCONNECTED;
+
+    // The SwingPropertyChangeSupport will fire events on the EDT
+    private SwingPropertyChangeSupport propertyChangeSupport =
+                                new SwingPropertyChangeSupport(this, true);
+
+    private static Map<String, ProxyClient> cache =
+        Collections.synchronizedMap(new HashMap<String, ProxyClient>());
+
+    private volatile boolean isDead = true;
+    private String hostName = null;
+    private int port = 0;
+    private String userName = null;
+    private String password = null;
+    private boolean hasPlatformMXBeans = false;
+    private boolean hasHotSpotDiagnosticMXBean= false;
+    private boolean hasCompilationMXBean = false;
+    private boolean supportsLockUsage = false;
+
+    // REVISIT: VMPanel and other places relying using getUrl().
+
+    // set only if it's created for local monitoring
+    private LocalVirtualMachine lvm;
+
+    // set only if it's created from a given URL via the Advanced tab
+    private String advancedUrl = null;
+
+    private JMXServiceURL jmxUrl = null;
+    private SnapshotMBeanServerConnection server = null;
+    private JMXConnector jmxc = null;
+    private RMIServer stub = null;
+    private static final SslRMIClientSocketFactory sslRMIClientSocketFactory =
+            new SslRMIClientSocketFactory();
+    private String registryHostName = null;
+    private int registryPort = 0;
+    private boolean vmConnector = false;
+    private boolean sslRegistry = false;
+    private boolean sslStub = false;
+    final private String connectionName;
+    final private String displayName;
+
+    private ClassLoadingMXBean    classLoadingMBean = null;
+    private CompilationMXBean     compilationMBean = null;
+    private MemoryMXBean          memoryMBean = null;
+    private OperatingSystemMXBean operatingSystemMBean = null;
+    private RuntimeMXBean         runtimeMBean = null;
+    private ThreadMXBean          threadMBean = null;
+
+    private com.sun.management.OperatingSystemMXBean sunOperatingSystemMXBean = null;
+    private HotSpotDiagnosticMXBean                  hotspotDiagnosticMXBean = null;
+
+    private List<MemoryPoolProxy>           memoryPoolProxies = null;
+    private List<GarbageCollectorMXBean>    garbageCollectorMBeans = null;
+    private String detectDeadlocksOperation = null;
+
+    final static private String HOTSPOT_DIAGNOSTIC_MXBEAN_NAME =
+        "com.sun.management:type=HotSpotDiagnostic";
+
+    private ProxyClient(String hostName, int port,
+                        String userName, String password) throws IOException {
+        this.connectionName = getConnectionName(hostName, port, userName);
+        this.displayName = connectionName;
+        if (hostName.equals("localhost") && port == 0) {
+            // Monitor self
+            this.hostName = hostName;
+            this.port = port;
+        } else {
+            // Create an RMI connector client and connect it to
+            // the RMI connector server
+            final String urlPath = "/jndi/rmi://" + hostName + ":" + port +
+                                   "/jmxrmi";
+            JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath);
+            setParameters(url, userName, password);
+            vmConnector = true;
+            registryHostName = hostName;
+            registryPort = port;
+            checkSslConfig();
+        }
+    }
+
+    private ProxyClient(String url,
+                        String userName, String password) throws IOException {
+        this.advancedUrl = url;
+        this.connectionName = getConnectionName(url, userName);
+        this.displayName = connectionName;
+        setParameters(new JMXServiceURL(url), userName, password);
+    }
+
+    private ProxyClient(LocalVirtualMachine lvm) throws IOException {
+        this.lvm = lvm;
+        this.connectionName = getConnectionName(lvm);
+        this.displayName = "pid: " + lvm.vmid() + " " + lvm.displayName();
+    }
+
+    private void setParameters(JMXServiceURL url, String userName, String password) {
+        this.jmxUrl = url;
+        this.hostName = jmxUrl.getHost();
+        this.port = jmxUrl.getPort();
+        this.userName = userName;
+        this.password = password;
+    }
+
+    private static void checkStub(Remote stub,
+                                  Class<? extends Remote> stubClass) {
+        // Check remote stub is from the expected class.
+        //
+        if (stub.getClass() != stubClass) {
+            if (!Proxy.isProxyClass(stub.getClass())) {
+                throw new SecurityException(
+                    "Expecting a " + stubClass.getName() + " stub!");
+            } else {
+                InvocationHandler handler = Proxy.getInvocationHandler(stub);
+                if (handler.getClass() != RemoteObjectInvocationHandler.class) {
+                    throw new SecurityException(
+                        "Expecting a dynamic proxy instance with a " +
+                        RemoteObjectInvocationHandler.class.getName() +
+                        " invocation handler!");
+                } else {
+                    stub = (Remote) handler;
+                }
+            }
+        }
+        // Check RemoteRef in stub is from the expected class
+        // "sun.rmi.server.UnicastRef2".
+        //
+        RemoteRef ref = ((RemoteObject)stub).getRef();
+        if (ref.getClass() != UnicastRef2.class) {
+            throw new SecurityException(
+                "Expecting a " + UnicastRef2.class.getName() +
+                " remote reference in stub!");
+        }
+        // Check RMIClientSocketFactory in stub is from the expected class
+        // "javax.rmi.ssl.SslRMIClientSocketFactory".
+        //
+        LiveRef liveRef = ((UnicastRef2)ref).getLiveRef();
+        RMIClientSocketFactory csf = liveRef.getClientSocketFactory();
+        if (csf == null || csf.getClass() != SslRMIClientSocketFactory.class) {
+            throw new SecurityException(
+                "Expecting a " + SslRMIClientSocketFactory.class.getName() +
+                " RMI client socket factory in stub!");
+        }
+    }
+
+    private static final String rmiServerImplStubClassName =
+        "javax.management.remote.rmi.RMIServerImpl_Stub";
+    private static final Class<? extends Remote> rmiServerImplStubClass;
+
+    static {
+        // FIXME: RMIServerImpl_Stub is generated at build time
+        // after jconsole is built.  We need to investigate if
+        // the Makefile can be fixed to build jconsole in the
+        // right order.  As a workaround for now, we dynamically
+        // load RMIServerImpl_Stub class instead of statically
+        // referencing it.
+        Class<? extends Remote> serverStubClass = null;
+        try {
+            serverStubClass = Class.forName(rmiServerImplStubClassName).asSubclass(Remote.class);
+        } catch (ClassNotFoundException e) {
+            // should never reach here
+            throw (InternalError) new InternalError(e.getMessage()).initCause(e);
+        }
+        rmiServerImplStubClass = serverStubClass;
+    }
+
+    private void checkSslConfig() throws IOException {
+        // Get the reference to the RMI Registry and lookup RMIServer stub
+        //
+        Registry registry;
+        try {
+            registry =
+                LocateRegistry.getRegistry(registryHostName, registryPort,
+                                           sslRMIClientSocketFactory);
+            try {
+                stub = (RMIServer) registry.lookup("jmxrmi");
+            } catch (NotBoundException nbe) {
+                throw (IOException)
+                    new IOException(nbe.getMessage()).initCause(nbe);
+            }
+            sslRegistry = true;
+        } catch (IOException e) {
+            registry =
+                LocateRegistry.getRegistry(registryHostName, registryPort);
+            try {
+                stub = (RMIServer) registry.lookup("jmxrmi");
+            } catch (NotBoundException nbe) {
+                throw (IOException)
+                    new IOException(nbe.getMessage()).initCause(nbe);
+            }
+            sslRegistry = false;
+        }
+        // Perform the checks for secure stub
+        //
+        try {
+            checkStub(stub, rmiServerImplStubClass);
+            sslStub = true;
+        } catch (SecurityException e) {
+            sslStub = false;
+        }
+    }
+
+    /**
+     * Returns true if the underlying RMI registry is SSL-protected.
+     *
+     * @exception UnsupportedOperationException If this {@code ProxyClient}
+     * does not denote a JMX connector for a JMX VM agent.
+     */
+    public boolean isSslRmiRegistry() {
+        // Check for VM connector
+        //
+        if (!isVmConnector()) {
+            throw new UnsupportedOperationException(
+                "ProxyClient.isSslRmiRegistry() is only supported if this " +
+                "ProxyClient is a JMX connector for a JMX VM agent");
+        }
+        return sslRegistry;
+    }
+
+    /**
+     * Returns true if the retrieved RMI stub is SSL-protected.
+     *
+     * @exception UnsupportedOperationException If this {@code ProxyClient}
+     * does not denote a JMX connector for a JMX VM agent.
+     */
+    public boolean isSslRmiStub() {
+        // Check for VM connector
+        //
+        if (!isVmConnector()) {
+            throw new UnsupportedOperationException(
+                "ProxyClient.isSslRmiStub() is only supported if this " +
+                "ProxyClient is a JMX connector for a JMX VM agent");
+        }
+        return sslStub;
+    }
+
+    /**
+     * Returns true if this {@code ProxyClient} denotes
+     * a JMX connector for a JMX VM agent.
+     */
+    public boolean isVmConnector() {
+        return vmConnector;
+    }
+
+    private void setConnectionState(ConnectionState state) {
+        ConnectionState oldState = this.connectionState;
+        this.connectionState = state;
+        propertyChangeSupport.firePropertyChange(CONNECTION_STATE_PROPERTY,
+                                                 oldState, state);
+    }
+
+    public ConnectionState getConnectionState() {
+        return this.connectionState;
+    }
+
+    void flush() {
+        if (server != null) {
+            server.flush();
+        }
+    }
+
+    void connect() {
+        setConnectionState(ConnectionState.CONNECTING);
+        try {
+            tryConnect();
+            setConnectionState(ConnectionState.CONNECTED);
+        } catch (Exception e) {
+            if (JConsole.isDebug()) {
+                e.printStackTrace();
+            }
+            setConnectionState(ConnectionState.DISCONNECTED);
+        }
+    }
+
+    private void tryConnect() throws IOException {
+        if (jmxUrl == null && "localhost".equals(hostName) && port == 0) {
+            // Monitor self
+            this.jmxc = null;
+            this.server = Snapshot.newSnapshot(
+                    ManagementFactory.getPlatformMBeanServer());
+        } else {
+            // Monitor another process
+            if (lvm != null) {
+                if (!lvm.isManageable()) {
+                    lvm.startManagementAgent();
+                    if (!lvm.isManageable()) {
+                        // FIXME: what to throw
+                        throw new IOException(lvm + "not manageable");
+                    }
+                }
+                if (this.jmxUrl == null) {
+                    this.jmxUrl = new JMXServiceURL(lvm.connectorAddress());
+                }
+            }
+            // Need to pass in credentials ?
+            if (userName == null && password == null) {
+                if (isVmConnector()) {
+                    // Check for SSL config on reconnection only
+                    if (stub == null) {
+                        checkSslConfig();
+                    }
+                    this.jmxc = new RMIConnector(stub, null);
+                    jmxc.connect();
+                } else {
+                    this.jmxc = JMXConnectorFactory.connect(jmxUrl);
+                }
+            } else {
+                Map<String, String[]> env = new HashMap<String, String[]>();
+                env.put(JMXConnector.CREDENTIALS,
+                        new String[] {userName, password});
+                if (isVmConnector()) {
+                    // Check for SSL config on reconnection only
+                    if (stub == null) {
+                        checkSslConfig();
+                    }
+                    this.jmxc = new RMIConnector(stub, null);
+                    jmxc.connect(env);
+                } else {
+                    this.jmxc = JMXConnectorFactory.connect(jmxUrl, env);
+                }
+            }
+            this.server = Snapshot.newSnapshot(jmxc.getMBeanServerConnection());
+        }
+        this.isDead = false;
+
+        try {
+            ObjectName on = new ObjectName(THREAD_MXBEAN_NAME);
+            this.hasPlatformMXBeans = server.isRegistered(on);
+            this.hasHotSpotDiagnosticMXBean =
+                server.isRegistered(new ObjectName(HOTSPOT_DIAGNOSTIC_MXBEAN_NAME));
+            // check if it has 6.0 new APIs
+            if (this.hasPlatformMXBeans) {
+                MBeanOperationInfo[] mopis = server.getMBeanInfo(on).getOperations();
+                // look for findDeadlockedThreads operations;
+                for (MBeanOperationInfo op : mopis) {
+                    if (op.getName().equals("findDeadlockedThreads")) {
+                        this.supportsLockUsage = true;
+                        break;
+                    }
+                }
+
+                on = new ObjectName(COMPILATION_MXBEAN_NAME);
+                this.hasCompilationMXBean = server.isRegistered(on);
+            }
+        } catch (MalformedObjectNameException e) {
+            // should not reach here
+            throw new InternalError(e.getMessage());
+        } catch (IntrospectionException e) {
+            InternalError ie = new InternalError(e.getMessage());
+            ie.initCause(e);
+            throw ie;
+        } catch (InstanceNotFoundException e) {
+            InternalError ie = new InternalError(e.getMessage());
+            ie.initCause(e);
+            throw ie;
+        } catch (ReflectionException e) {
+            InternalError ie = new InternalError(e.getMessage());
+            ie.initCause(e);
+            throw ie;
+        }
+
+        if (hasPlatformMXBeans) {
+            // WORKAROUND for bug 5056632
+            // Check if the access role is correct by getting a RuntimeMXBean
+            getRuntimeMXBean();
+        }
+    }
+
+    /**
+     * Gets a proxy client for a given local virtual machine.
+     */
+    public static ProxyClient getProxyClient(LocalVirtualMachine lvm)
+        throws IOException {
+        final String key = getCacheKey(lvm);
+        ProxyClient proxyClient = cache.get(key);
+        if (proxyClient == null) {
+            proxyClient = new ProxyClient(lvm);
+            cache.put(key, proxyClient);
+        }
+        return proxyClient;
+    }
+
+    public static String getConnectionName(LocalVirtualMachine lvm) {
+        return Integer.toString(lvm.vmid());
+    }
+
+    private static String getCacheKey(LocalVirtualMachine lvm) {
+        return Integer.toString(lvm.vmid());
+    }
+
+    /**
+     * Gets a proxy client for a given JMXServiceURL.
+     */
+    public static ProxyClient getProxyClient(String url,
+                                             String userName, String password)
+        throws IOException {
+        final String key = getCacheKey(url, userName, password);
+        ProxyClient proxyClient = cache.get(key);
+        if (proxyClient == null) {
+            proxyClient = new ProxyClient(url, userName, password);
+            cache.put(key, proxyClient);
+        }
+        return proxyClient;
+    }
+
+    public static String getConnectionName(String url,
+                                           String userName) {
+        if (userName != null && userName.length() > 0) {
+            return userName + "@" + url;
+        } else {
+            return url;
+        }
+    }
+
+    private static String getCacheKey(String url,
+                                      String userName, String password) {
+        return (url == null ? "" : url) + ":" +
+               (userName == null ? "" : userName) + ":" +
+               (password == null ? "" : password);
+    }
+
+    /**
+     * Gets a proxy client for a given "hostname:port".
+     */
+    public static ProxyClient getProxyClient(String hostName, int port,
+                                             String userName, String password)
+        throws IOException {
+        final String key = getCacheKey(hostName, port, userName, password);
+        ProxyClient proxyClient = cache.get(key);
+        if (proxyClient == null) {
+            proxyClient = new ProxyClient(hostName, port, userName, password);
+            cache.put(key, proxyClient);
+        }
+        return proxyClient;
+    }
+
+    public static String getConnectionName(String hostName, int port,
+                                           String userName) {
+        String name = hostName + ":" + port;
+        if (userName != null && userName.length() > 0) {
+            return userName + "@" + name;
+        } else {
+            return name;
+        }
+    }
+
+    private static String getCacheKey(String hostName, int port,
+                                      String userName, String password) {
+        return (hostName == null ? "" : hostName) + ":" +
+               port + ":" +
+               (userName == null ? "" : userName) + ":" +
+               (password == null ? "" : password);
+    }
+
+    public String connectionName() {
+        return connectionName;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String toString() {
+        if (!isConnected()) {
+            return Resources.getText("ConnectionName (disconnected)", displayName);
+        } else {
+            return displayName;
+        }
+    }
+
+    public MBeanServerConnection getMBeanServerConnection() {
+        return server;
+    }
+
+    public String getUrl() {
+        return advancedUrl;
+    }
+
+    public String getHostName() {
+        return hostName;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public int getVmid() {
+        return (lvm != null) ? lvm.vmid() : 0;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void disconnect() {
+        // Reset remote stub
+        stub = null;
+        // Close MBeanServer connection
+        if (jmxc != null) {
+            try {
+                jmxc.close();
+            } catch (IOException e) {
+                // Ignore ???
+            }
+        }
+        // Reset platform MBean references
+        classLoadingMBean = null;
+        compilationMBean = null;
+        memoryMBean = null;
+        operatingSystemMBean = null;
+        runtimeMBean = null;
+        threadMBean = null;
+        sunOperatingSystemMXBean = null;
+        garbageCollectorMBeans = null;
+        // Set connection state to DISCONNECTED
+        if (!isDead) {
+            isDead = true;
+            setConnectionState(ConnectionState.DISCONNECTED);
+        }
+    }
+
+    /**
+     * Returns the list of domains in which any MBean is
+     * currently registered.
+     */
+    public String[] getDomains() throws IOException {
+        return server.getDomains();
+    }
+
+    /**
+     * Returns a map of MBeans with ObjectName as the key and MBeanInfo value
+     * of a given domain.  If domain is <tt>null</tt>, all MBeans
+     * are returned.  If no MBean found, an empty map is returned.
+     *
+     */
+    public Map<ObjectName, MBeanInfo> getMBeans(String domain)
+        throws IOException {
+
+        ObjectName name = null;
+        if (domain != null) {
+            try {
+                name = new ObjectName(domain + ":*");
+            } catch (MalformedObjectNameException e) {
+                // should not reach here
+                assert(false);
+            }
+        }
+        Set mbeans = server.queryNames(name, null);
+        Map<ObjectName,MBeanInfo> result =
+            new HashMap<ObjectName,MBeanInfo>(mbeans.size());
+        Iterator iterator = mbeans.iterator();
+        while (iterator.hasNext()) {
+            Object object = iterator.next();
+            if (object instanceof ObjectName) {
+                ObjectName o = (ObjectName)object;
+                try {
+                    MBeanInfo info = server.getMBeanInfo(o);
+                    result.put(o, info);
+                } catch (IntrospectionException e) {
+                    // TODO: should log the error
+                } catch (InstanceNotFoundException e) {
+                    // TODO: should log the error
+                } catch (ReflectionException e) {
+                    // TODO: should log the error
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns a list of attributes of a named MBean.
+     *
+     */
+    public AttributeList getAttributes(ObjectName name, String[] attributes)
+        throws IOException {
+        AttributeList list = null;
+        try {
+            list = server.getAttributes(name, attributes);
+        } catch (InstanceNotFoundException e) {
+            // TODO: A MBean may have been unregistered.
+            // need to set up listener to listen for MBeanServerNotification.
+        } catch (ReflectionException e) {
+            // TODO: should log the error
+        }
+        return list;
+    }
+
+    /**
+     * Set the value of a specific attribute of a named MBean.
+     */
+    public void setAttribute(ObjectName name, Attribute attribute)
+        throws InvalidAttributeValueException,
+               MBeanException,
+               IOException {
+        try {
+            server.setAttribute(name, attribute);
+        } catch (InstanceNotFoundException e) {
+            // TODO: A MBean may have been unregistered.
+        } catch (AttributeNotFoundException e) {
+            assert(false);
+        } catch (ReflectionException e) {
+            // TODO: should log the error
+        }
+    }
+
+    /**
+     * Invokes an operation of a named MBean.
+     *
+     * @throws MBeanException Wraps an exception thrown by
+     *      the MBean's invoked method.
+     */
+    public Object invoke(ObjectName name, String operationName,
+                         Object[] params, String[] signature)
+        throws IOException, MBeanException {
+        Object result = null;
+        try {
+            result = server.invoke(name, operationName, params, signature);
+        } catch (InstanceNotFoundException e) {
+            // TODO: A MBean may have been unregistered.
+        } catch (ReflectionException e) {
+            // TODO: should log the error
+        }
+        return result;
+    }
+
+    public synchronized ClassLoadingMXBean getClassLoadingMXBean() throws IOException {
+        if (hasPlatformMXBeans && classLoadingMBean == null) {
+            classLoadingMBean =
+                newPlatformMXBeanProxy(server, CLASS_LOADING_MXBEAN_NAME,
+                                       ClassLoadingMXBean.class);
+        }
+        return classLoadingMBean;
+    }
+
+    public synchronized CompilationMXBean getCompilationMXBean() throws IOException {
+        if (hasCompilationMXBean && compilationMBean == null) {
+            compilationMBean =
+                newPlatformMXBeanProxy(server, COMPILATION_MXBEAN_NAME,
+                                       CompilationMXBean.class);
+        }
+        return compilationMBean;
+    }
+
+    public Collection<MemoryPoolProxy> getMemoryPoolProxies()
+        throws IOException {
+
+        // TODO: How to deal with changes to the list??
+        if (memoryPoolProxies == null) {
+            ObjectName poolName = null;
+            try {
+                poolName = new ObjectName(MEMORY_POOL_MXBEAN_DOMAIN_TYPE + ",*");
+            } catch (MalformedObjectNameException e) {
+                // should not reach here
+                assert(false);
+            }
+            Set mbeans = server.queryNames(poolName, null);
+            if (mbeans != null) {
+                memoryPoolProxies = new ArrayList<MemoryPoolProxy>();
+                Iterator iterator = mbeans.iterator();
+                while (iterator.hasNext()) {
+                    ObjectName objName = (ObjectName) iterator.next();
+                    MemoryPoolProxy p = new MemoryPoolProxy(this, objName);
+                    memoryPoolProxies.add(p);
+                }
+            }
+        }
+        return memoryPoolProxies;
+    }
+
+    public synchronized Collection<GarbageCollectorMXBean> getGarbageCollectorMXBeans()
+        throws IOException {
+
+        // TODO: How to deal with changes to the list??
+        if (garbageCollectorMBeans == null) {
+            ObjectName gcName = null;
+            try {
+                gcName = new ObjectName(GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*");
+            } catch (MalformedObjectNameException e) {
+                // should not reach here
+                assert(false);
+            }
+            Set mbeans = server.queryNames(gcName, null);
+            if (mbeans != null) {
+                garbageCollectorMBeans = new ArrayList<GarbageCollectorMXBean>();
+                Iterator iterator = mbeans.iterator();
+                while (iterator.hasNext()) {
+                    ObjectName on = (ObjectName) iterator.next();
+                    String name = GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE +
+                        ",name=" + on.getKeyProperty("name");
+
+                    GarbageCollectorMXBean mBean =
+                        newPlatformMXBeanProxy(server, name,
+                                               GarbageCollectorMXBean.class);
+                        garbageCollectorMBeans.add(mBean);
+                }
+            }
+        }
+        return garbageCollectorMBeans;
+    }
+
+    public synchronized MemoryMXBean getMemoryMXBean() throws IOException {
+        if (hasPlatformMXBeans && memoryMBean == null) {
+            memoryMBean =
+                newPlatformMXBeanProxy(server, MEMORY_MXBEAN_NAME,
+                                       MemoryMXBean.class);
+        }
+        return memoryMBean;
+    }
+
+    public synchronized RuntimeMXBean getRuntimeMXBean() throws IOException {
+        if (hasPlatformMXBeans && runtimeMBean == null) {
+            runtimeMBean =
+                newPlatformMXBeanProxy(server, RUNTIME_MXBEAN_NAME,
+                                       RuntimeMXBean.class);
+        }
+        return runtimeMBean;
+    }
+
+
+    public synchronized ThreadMXBean getThreadMXBean() throws IOException {
+        if (hasPlatformMXBeans && threadMBean == null) {
+            threadMBean =
+                newPlatformMXBeanProxy(server, THREAD_MXBEAN_NAME,
+                                       ThreadMXBean.class);
+        }
+        return threadMBean;
+    }
+
+    public synchronized OperatingSystemMXBean getOperatingSystemMXBean() throws IOException {
+        if (hasPlatformMXBeans && operatingSystemMBean == null) {
+            operatingSystemMBean =
+                newPlatformMXBeanProxy(server, OPERATING_SYSTEM_MXBEAN_NAME,
+                                       OperatingSystemMXBean.class);
+        }
+        return operatingSystemMBean;
+    }
+
+    public synchronized com.sun.management.OperatingSystemMXBean
+        getSunOperatingSystemMXBean() throws IOException {
+
+        try {
+            ObjectName on = new ObjectName(OPERATING_SYSTEM_MXBEAN_NAME);
+            if (sunOperatingSystemMXBean == null) {
+                if (server.isInstanceOf(on,
+                        "com.sun.management.OperatingSystemMXBean")) {
+                    sunOperatingSystemMXBean =
+                        newPlatformMXBeanProxy(server,
+                            OPERATING_SYSTEM_MXBEAN_NAME,
+                            com.sun.management.OperatingSystemMXBean.class);
+                }
+            }
+        } catch (InstanceNotFoundException e) {
+             return null;
+        } catch (MalformedObjectNameException e) {
+             return null; // should never reach here
+        }
+        return sunOperatingSystemMXBean;
+    }
+
+    public synchronized HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOException {
+        if (hasHotSpotDiagnosticMXBean && hotspotDiagnosticMXBean == null) {
+            hotspotDiagnosticMXBean =
+                newPlatformMXBeanProxy(server, HOTSPOT_DIAGNOSTIC_MXBEAN_NAME,
+                                       HotSpotDiagnosticMXBean.class);
+        }
+        return hotspotDiagnosticMXBean;
+    }
+
+    public <T> T getMXBean(ObjectName objName, Class<T> interfaceClass)
+        throws IOException {
+        return newPlatformMXBeanProxy(server,
+                                      objName.toString(),
+                                      interfaceClass);
+
+    }
+
+    // Return thread IDs of deadlocked threads or null if any.
+    // It finds deadlocks involving only monitors if it's a Tiger VM.
+    // Otherwise, it finds deadlocks involving both monitors and
+    // the concurrent locks.
+    public long[] findDeadlockedThreads() throws IOException {
+        ThreadMXBean tm = getThreadMXBean();
+        if (supportsLockUsage && tm.isSynchronizerUsageSupported()) {
+            return tm.findDeadlockedThreads();
+        } else {
+            return tm.findMonitorDeadlockedThreads();
+        }
+    }
+
+    public synchronized void markAsDead() {
+        disconnect();
+    }
+
+    public boolean isDead() {
+        return isDead;
+    }
+
+    boolean isConnected() {
+        return !isDead();
+    }
+
+    boolean hasPlatformMXBeans() {
+        return this.hasPlatformMXBeans;
+    }
+
+    boolean hasHotSpotDiagnosticMXBean() {
+        return this.hasHotSpotDiagnosticMXBean;
+    }
+
+    boolean isLockUsageSupported() {
+        return supportsLockUsage;
+    }
+
+    public boolean isRegistered(ObjectName name) throws IOException {
+        return server.isRegistered(name);
+    }
+
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+        propertyChangeSupport.addPropertyChangeListener(listener);
+    }
+
+    public void addWeakPropertyChangeListener(PropertyChangeListener listener) {
+        if (!(listener instanceof WeakPCL)) {
+            listener = new WeakPCL(listener);
+        }
+        propertyChangeSupport.addPropertyChangeListener(listener);
+    }
+
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+        if (!(listener instanceof WeakPCL)) {
+            // Search for the WeakPCL holding this listener (if any)
+            for (PropertyChangeListener pcl : propertyChangeSupport.getPropertyChangeListeners()) {
+                if (pcl instanceof WeakPCL && ((WeakPCL)pcl).get() == listener) {
+                    listener = pcl;
+                    break;
+                }
+            }
+        }
+        propertyChangeSupport.removePropertyChangeListener(listener);
+    }
+
+    /**
+     * The PropertyChangeListener is handled via a WeakReference
+     * so as not to pin down the listener.
+     */
+    private class WeakPCL extends WeakReference<PropertyChangeListener>
+                          implements PropertyChangeListener {
+        WeakPCL(PropertyChangeListener referent) {
+            super(referent);
+        }
+
+        public void propertyChange(PropertyChangeEvent pce) {
+            PropertyChangeListener pcl = get();
+
+            if (pcl == null) {
+                // The referent listener was GC'ed, we're no longer
+                // interested in PropertyChanges, remove the listener.
+                dispose();
+            } else {
+                pcl.propertyChange(pce);
+            }
+        }
+
+        private void dispose() {
+            removePropertyChangeListener(this);
+        }
+    }
+
+    //
+    // Snapshot MBeanServerConnection:
+    //
+    // This is an object that wraps an existing MBeanServerConnection and adds
+    // caching to it, as follows:
+    //
+    // - The first time an attribute is called in a given MBean, the result is
+    //   cached. Every subsequent time getAttribute is called for that attribute
+    //   the cached result is returned.
+    //
+    // - Before every call to VMPanel.update() or when the Refresh button in the
+    //   Attributes table is pressed down the attributes cache is flushed. Then
+    //   any subsequent call to getAttribute will retrieve all the values for
+    //   the attributes that are known to the cache.
+    //
+    // - The attributes cache uses a learning approach and only the attributes
+    //   that are in the cache will be retrieved between two subsequent updates.
+    //
+
+    public interface SnapshotMBeanServerConnection
+            extends MBeanServerConnection {
+        /**
+         * Flush all cached values of attributes.
+         */
+        public void flush();
+    }
+
+    public static class Snapshot {
+        private Snapshot() {
+        }
+        public static SnapshotMBeanServerConnection
+                newSnapshot(MBeanServerConnection mbsc) {
+            final InvocationHandler ih = new SnapshotInvocationHandler(mbsc);
+            return (SnapshotMBeanServerConnection) Proxy.newProxyInstance(
+                    Snapshot.class.getClassLoader(),
+                    new Class[] {SnapshotMBeanServerConnection.class},
+                    ih);
+        }
+    }
+
+    static class SnapshotInvocationHandler implements InvocationHandler {
+
+        private final MBeanServerConnection conn;
+        private Map<ObjectName, NameValueMap> cachedValues = newMap();
+        private Map<ObjectName, Set<String>> cachedNames = newMap();
+
+        @SuppressWarnings("serial")
+        private static final class NameValueMap
+                extends HashMap<String, Object> {}
+
+        SnapshotInvocationHandler(MBeanServerConnection conn) {
+            this.conn = conn;
+        }
+
+        synchronized void flush() {
+            cachedValues = newMap();
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args)
+                throws Throwable {
+            final String methodName = method.getName();
+            if (methodName.equals("getAttribute")) {
+                return getAttribute((ObjectName) args[0], (String) args[1]);
+            } else if (methodName.equals("getAttributes")) {
+                return getAttributes((ObjectName) args[0], (String[]) args[1]);
+            } else if (methodName.equals("flush")) {
+                flush();
+                return null;
+            } else {
+                try {
+                    return method.invoke(conn, args);
+                } catch (InvocationTargetException e) {
+                    throw e.getCause();
+                }
+            }
+        }
+
+        private Object getAttribute(ObjectName objName, String attrName)
+                throws MBeanException, InstanceNotFoundException,
+                AttributeNotFoundException, ReflectionException, IOException {
+            final NameValueMap values = getCachedAttributes(
+                    objName, Collections.singleton(attrName));
+            Object value = values.get(attrName);
+            if (value != null || values.containsKey(attrName)) {
+                return value;
+            }
+            // Not in cache, presumably because it was omitted from the
+            // getAttributes result because of an exception.  Following
+            // call will probably provoke the same exception.
+            return conn.getAttribute(objName, attrName);
+        }
+
+        private AttributeList getAttributes(
+                ObjectName objName, String[] attrNames) throws
+                InstanceNotFoundException, ReflectionException, IOException {
+            final NameValueMap values = getCachedAttributes(
+                    objName,
+                    new TreeSet<String>(Arrays.asList(attrNames)));
+            final AttributeList list = new AttributeList();
+            for (String attrName : attrNames) {
+                final Object value = values.get(attrName);
+                if (value != null || values.containsKey(attrName)) {
+                    list.add(new Attribute(attrName, value));
+                }
+            }
+            return list;
+        }
+
+        private synchronized NameValueMap getCachedAttributes(
+                ObjectName objName, Set<String> attrNames) throws
+                InstanceNotFoundException, ReflectionException, IOException {
+            NameValueMap values = cachedValues.get(objName);
+            if (values != null && values.keySet().containsAll(attrNames)) {
+                return values;
+            }
+            attrNames = new TreeSet<String>(attrNames);
+            Set<String> oldNames = cachedNames.get(objName);
+            if (oldNames != null) {
+                attrNames.addAll(oldNames);
+            }
+            values = new NameValueMap();
+            final AttributeList attrs = conn.getAttributes(
+                    objName,
+                    attrNames.toArray(new String[attrNames.size()]));
+            for (Attribute attr : attrs.asList()) {
+                values.put(attr.getName(), attr.getValue());
+            }
+            cachedValues.put(objName, values);
+            cachedNames.put(objName, attrNames);
+            return values;
+        }
+
+        // See http://www.artima.com/weblogs/viewpost.jsp?thread=79394
+        private static <K, V> Map<K, V> newMap() {
+            return new HashMap<K, V>();
+        }
+    }
+}