| /* |
| * Copyright (c) 2004, 2014, 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 sun.tools.jconsole; |
| |
| import com.sun.management.HotSpotDiagnosticMXBean; |
| import com.sun.tools.jconsole.JConsoleContext; |
| 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 MBeanServerConnection mbsc = 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; |
| |
| 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 new InternalError(e.getMessage(), 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(boolean requireSSL) { |
| setConnectionState(ConnectionState.CONNECTING); |
| try { |
| tryConnect(requireSSL); |
| setConnectionState(ConnectionState.CONNECTED); |
| } catch (Exception e) { |
| if (JConsole.isDebug()) { |
| e.printStackTrace(); |
| } |
| setConnectionState(ConnectionState.DISCONNECTED); |
| } |
| } |
| |
| private void tryConnect(boolean requireRemoteSSL) throws IOException { |
| if (jmxUrl == null && "localhost".equals(hostName) && port == 0) { |
| // Monitor self |
| this.jmxc = null; |
| this.mbsc = ManagementFactory.getPlatformMBeanServer(); |
| this.server = Snapshot.newSnapshot(mbsc); |
| } 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()); |
| } |
| } |
| Map<String, Object> env = new HashMap<String, Object>(); |
| if (requireRemoteSSL) { |
| env.put("jmx.remote.x.check.stub", "true"); |
| } |
| // 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(env); |
| } else { |
| this.jmxc = JMXConnectorFactory.connect(jmxUrl, env); |
| } |
| } else { |
| 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.mbsc = jmxc.getMBeanServerConnection(); |
| this.server = Snapshot.newSnapshot(mbsc); |
| } |
| 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 | |
| InstanceNotFoundException | |
| ReflectionException e) { |
| throw new InternalError(e.getMessage(), e); |
| } |
| |
| 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.format(Messages.CONNECTION_NAME__DISCONNECTED_, displayName); |
| } else { |
| return displayName; |
| } |
| } |
| |
| public MBeanServerConnection getMBeanServerConnection() { |
| return mbsc; |
| } |
| |
| public SnapshotMBeanServerConnection getSnapshotMBeanServerConnection() { |
| 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 {@code null}, 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<ObjectName> mbeans = server.queryNames(name, null); |
| Map<ObjectName,MBeanInfo> result = |
| new HashMap<ObjectName,MBeanInfo>(mbeans.size()); |
| Iterator<ObjectName> 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<ObjectName> mbeans = server.queryNames(poolName, null); |
| if (mbeans != null) { |
| memoryPoolProxies = new ArrayList<MemoryPoolProxy>(); |
| Iterator<ObjectName> iterator = mbeans.iterator(); |
| while (iterator.hasNext()) { |
| ObjectName objName = 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<ObjectName> mbeans = server.queryNames(gcName, null); |
| if (mbeans != null) { |
| garbageCollectorMBeans = new ArrayList<GarbageCollectorMXBean>(); |
| Iterator<ObjectName> iterator = mbeans.iterator(); |
| while (iterator.hasNext()) { |
| ObjectName on = 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>(); |
| } |
| } |
| } |