1 /* 2 * Copyright (c) 2004, 2007, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.tools.jconsole; 27 28 import com.sun.management.HotSpotDiagnosticMXBean; 29 import com.sun.tools.jconsole.JConsoleContext; 30 import com.sun.tools.jconsole.JConsoleContext.ConnectionState; 31 import java.awt.Component; 32 import java.beans.PropertyChangeListener; 33 import java.beans.PropertyChangeEvent; 34 import java.io.IOException; 35 import java.lang.management.*; 36 import static java.lang.management.ManagementFactory.*; 37 import java.lang.ref.WeakReference; 38 import java.lang.reflect.*; 39 import java.rmi.*; 40 import java.rmi.registry.*; 41 import java.rmi.server.*; 42 import java.util.*; 43 import javax.management.*; 44 import javax.management.remote.*; 45 import javax.management.remote.rmi.*; 46 import javax.rmi.ssl.SslRMIClientSocketFactory; 47 import javax.swing.event.SwingPropertyChangeSupport; 48 import sun.rmi.server.UnicastRef2; 49 import sun.rmi.transport.LiveRef; 50 51 public class ProxyClient implements JConsoleContext { 52 53 private ConnectionState connectionState = ConnectionState.DISCONNECTED; 54 55 // The SwingPropertyChangeSupport will fire events on the EDT 56 private SwingPropertyChangeSupport propertyChangeSupport = 57 new SwingPropertyChangeSupport(this, true); 58 59 private static Map<String, ProxyClient> cache = 60 Collections.synchronizedMap(new HashMap<String, ProxyClient>()); 61 62 private volatile boolean isDead = true; 63 private String hostName = null; 64 private int port = 0; 65 private String userName = null; 66 private String password = null; 67 private boolean hasPlatformMXBeans = false; 68 private boolean hasHotSpotDiagnosticMXBean= false; 69 private boolean hasCompilationMXBean = false; 70 private boolean supportsLockUsage = false; 71 72 // REVISIT: VMPanel and other places relying using getUrl(). 73 74 // set only if it's created for local monitoring 75 private LocalVirtualMachine lvm; 76 77 // set only if it's created from a given URL via the Advanced tab 78 private String advancedUrl = null; 79 80 private JMXServiceURL jmxUrl = null; 81 private SnapshotMBeanServerConnection server = null; 82 private JMXConnector jmxc = null; 83 private RMIServer stub = null; 84 private static final SslRMIClientSocketFactory sslRMIClientSocketFactory = 85 new SslRMIClientSocketFactory(); 86 private String registryHostName = null; 87 private int registryPort = 0; 88 private boolean vmConnector = false; 89 private boolean sslRegistry = false; 90 private boolean sslStub = false; 91 final private String connectionName; 92 final private String displayName; 93 94 private ClassLoadingMXBean classLoadingMBean = null; 95 private CompilationMXBean compilationMBean = null; 96 private MemoryMXBean memoryMBean = null; 97 private OperatingSystemMXBean operatingSystemMBean = null; 98 private RuntimeMXBean runtimeMBean = null; 99 private ThreadMXBean threadMBean = null; 100 101 private com.sun.management.OperatingSystemMXBean sunOperatingSystemMXBean = null; 102 private HotSpotDiagnosticMXBean hotspotDiagnosticMXBean = null; 103 104 private List<MemoryPoolProxy> memoryPoolProxies = null; 105 private List<GarbageCollectorMXBean> garbageCollectorMBeans = null; 106 private String detectDeadlocksOperation = null; 107 108 final static private String HOTSPOT_DIAGNOSTIC_MXBEAN_NAME = 109 "com.sun.management:type=HotSpotDiagnostic"; 110 111 private ProxyClient(String hostName, int port, 112 String userName, String password) throws IOException { 113 this.connectionName = getConnectionName(hostName, port, userName); 114 this.displayName = connectionName; 115 if (hostName.equals("localhost") && port == 0) { 116 // Monitor self 117 this.hostName = hostName; 118 this.port = port; 119 } else { 120 // Create an RMI connector client and connect it to 121 // the RMI connector server 122 final String urlPath = "/jndi/rmi://" + hostName + ":" + port + 123 "/jmxrmi"; 124 JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath); 125 setParameters(url, userName, password); 126 vmConnector = true; 127 registryHostName = hostName; 128 registryPort = port; 129 checkSslConfig(); 130 } 131 } 132 133 private ProxyClient(String url, 134 String userName, String password) throws IOException { 135 this.advancedUrl = url; 136 this.connectionName = getConnectionName(url, userName); 137 this.displayName = connectionName; 138 setParameters(new JMXServiceURL(url), userName, password); 139 } 140 141 private ProxyClient(LocalVirtualMachine lvm) throws IOException { 142 this.lvm = lvm; 143 this.connectionName = getConnectionName(lvm); 144 this.displayName = "pid: " + lvm.vmid() + " " + lvm.displayName(); 145 } 146 147 private void setParameters(JMXServiceURL url, String userName, String password) { 148 this.jmxUrl = url; 149 this.hostName = jmxUrl.getHost(); 150 this.port = jmxUrl.getPort(); 151 this.userName = userName; 152 this.password = password; 153 } 154 155 private static void checkStub(Remote stub, 156 Class<? extends Remote> stubClass) { 157 // Check remote stub is from the expected class. 158 // 159 if (stub.getClass() != stubClass) { 160 if (!Proxy.isProxyClass(stub.getClass())) { 161 throw new SecurityException( 162 "Expecting a " + stubClass.getName() + " stub!"); 163 } else { 164 InvocationHandler handler = Proxy.getInvocationHandler(stub); 165 if (handler.getClass() != RemoteObjectInvocationHandler.class) { 166 throw new SecurityException( 167 "Expecting a dynamic proxy instance with a " + 168 RemoteObjectInvocationHandler.class.getName() + 169 " invocation handler!"); 170 } else { 171 stub = (Remote) handler; 172 } 173 } 174 } 175 // Check RemoteRef in stub is from the expected class 176 // "sun.rmi.server.UnicastRef2". 177 // 178 RemoteRef ref = ((RemoteObject)stub).getRef(); 179 if (ref.getClass() != UnicastRef2.class) { 180 throw new SecurityException( 181 "Expecting a " + UnicastRef2.class.getName() + 182 " remote reference in stub!"); 183 } 184 // Check RMIClientSocketFactory in stub is from the expected class 185 // "javax.rmi.ssl.SslRMIClientSocketFactory". 186 // 187 LiveRef liveRef = ((UnicastRef2)ref).getLiveRef(); 188 RMIClientSocketFactory csf = liveRef.getClientSocketFactory(); 189 if (csf == null || csf.getClass() != SslRMIClientSocketFactory.class) { 190 throw new SecurityException( 191 "Expecting a " + SslRMIClientSocketFactory.class.getName() + 192 " RMI client socket factory in stub!"); 193 } 194 } 195 196 private static final String rmiServerImplStubClassName = 197 "javax.management.remote.rmi.RMIServerImpl_Stub"; 198 private static final Class<? extends Remote> rmiServerImplStubClass; 199 200 static { 201 // FIXME: RMIServerImpl_Stub is generated at build time 202 // after jconsole is built. We need to investigate if 203 // the Makefile can be fixed to build jconsole in the 204 // right order. As a workaround for now, we dynamically 205 // load RMIServerImpl_Stub class instead of statically 206 // referencing it. 207 Class<? extends Remote> serverStubClass = null; 208 try { 209 serverStubClass = Class.forName(rmiServerImplStubClassName).asSubclass(Remote.class); 210 } catch (ClassNotFoundException e) { 211 // should never reach here 212 throw (InternalError) new InternalError(e.getMessage()).initCause(e); 213 } 214 rmiServerImplStubClass = serverStubClass; 215 } 216 217 private void checkSslConfig() throws IOException { 218 // Get the reference to the RMI Registry and lookup RMIServer stub 219 // 220 Registry registry; 221 try { 222 registry = 223 LocateRegistry.getRegistry(registryHostName, registryPort, 224 sslRMIClientSocketFactory); 225 try { 226 stub = (RMIServer) registry.lookup("jmxrmi"); 227 } catch (NotBoundException nbe) { 228 throw (IOException) 229 new IOException(nbe.getMessage()).initCause(nbe); 230 } 231 sslRegistry = true; 232 } catch (IOException e) { 233 registry = 234 LocateRegistry.getRegistry(registryHostName, registryPort); 235 try { 236 stub = (RMIServer) registry.lookup("jmxrmi"); 237 } catch (NotBoundException nbe) { 238 throw (IOException) 239 new IOException(nbe.getMessage()).initCause(nbe); 240 } 241 sslRegistry = false; 242 } 243 // Perform the checks for secure stub 244 // 245 try { 246 checkStub(stub, rmiServerImplStubClass); 247 sslStub = true; 248 } catch (SecurityException e) { 249 sslStub = false; 250 } 251 } 252 253 /** 254 * Returns true if the underlying RMI registry is SSL-protected. 255 * 256 * @exception UnsupportedOperationException If this {@code ProxyClient} 257 * does not denote a JMX connector for a JMX VM agent. 258 */ 259 public boolean isSslRmiRegistry() { 260 // Check for VM connector 261 // 262 if (!isVmConnector()) { 263 throw new UnsupportedOperationException( 264 "ProxyClient.isSslRmiRegistry() is only supported if this " + 265 "ProxyClient is a JMX connector for a JMX VM agent"); 266 } 267 return sslRegistry; 268 } 269 270 /** 271 * Returns true if the retrieved RMI stub is SSL-protected. 272 * 273 * @exception UnsupportedOperationException If this {@code ProxyClient} 274 * does not denote a JMX connector for a JMX VM agent. 275 */ 276 public boolean isSslRmiStub() { 277 // Check for VM connector 278 // 279 if (!isVmConnector()) { 280 throw new UnsupportedOperationException( 281 "ProxyClient.isSslRmiStub() is only supported if this " + 282 "ProxyClient is a JMX connector for a JMX VM agent"); 283 } 284 return sslStub; 285 } 286 287 /** 288 * Returns true if this {@code ProxyClient} denotes 289 * a JMX connector for a JMX VM agent. 290 */ 291 public boolean isVmConnector() { 292 return vmConnector; 293 } 294 295 private void setConnectionState(ConnectionState state) { 296 ConnectionState oldState = this.connectionState; 297 this.connectionState = state; 298 propertyChangeSupport.firePropertyChange(CONNECTION_STATE_PROPERTY, 299 oldState, state); 300 } 301 302 public ConnectionState getConnectionState() { 303 return this.connectionState; 304 } 305 306 void flush() { 307 if (server != null) { 308 server.flush(); 309 } 310 } 311 312 void connect(boolean requireSSL) { 313 setConnectionState(ConnectionState.CONNECTING); 314 Exception exception = null; 315 try { 316 tryConnect(requireSSL); 317 } catch (IOException ex) { 318 if (JConsole.isDebug()) { 319 ex.printStackTrace(); 320 } 321 exception = ex; 322 } catch (SecurityException ex) { 323 if (JConsole.isDebug()) { 324 ex.printStackTrace(); 325 } 326 exception = ex; 327 } 328 if (exception != null) { 329 // TODO: Include exception message with reason 330 setConnectionState(ConnectionState.DISCONNECTED); 331 } else { 332 setConnectionState(ConnectionState.CONNECTED); 333 } 334 } 335 336 private void tryConnect(boolean requireRemoteSSL) throws IOException { 337 if (jmxUrl == null && "localhost".equals(hostName) && port == 0) { 338 // Monitor self 339 this.jmxc = null; 340 this.server = Snapshot.newSnapshot( 341 ManagementFactory.getPlatformMBeanServer()); 342 } else { 343 // Monitor another process 344 if (lvm != null) { 345 if (!lvm.isManageable()) { 346 lvm.startManagementAgent(); 347 if (!lvm.isManageable()) { 348 // FIXME: what to throw 349 throw new IOException(lvm + "not manageable"); 350 } 351 } 352 if (this.jmxUrl == null) { 353 this.jmxUrl = new JMXServiceURL(lvm.connectorAddress()); 354 } 355 } 356 Map<String, Object> env = new HashMap<String, Object>(); 357 if (requireRemoteSSL) { 358 env.put("jmx.remote.x.check.stub", "true"); 359 } 360 // Need to pass in credentials ? 361 if (userName == null && password == null) { 362 if (isVmConnector()) { 363 // Check for SSL config on reconnection only 364 if (stub == null) { 365 checkSslConfig(); 366 } 367 this.jmxc = new RMIConnector(stub, null); 368 jmxc.connect(env); 369 } else { 370 this.jmxc = JMXConnectorFactory.connect(jmxUrl, env); 371 } 372 } else { 373 env.put(JMXConnector.CREDENTIALS, 374 new String[] {userName, password}); 375 if (isVmConnector()) { 376 // Check for SSL config on reconnection only 377 if (stub == null) { 378 checkSslConfig(); 379 } 380 this.jmxc = new RMIConnector(stub, null); 381 jmxc.connect(env); 382 } else { 383 this.jmxc = JMXConnectorFactory.connect(jmxUrl, env); 384 } 385 } 386 this.server = Snapshot.newSnapshot(jmxc.getMBeanServerConnection()); 387 } 388 this.isDead = false; 389 390 try { 391 ObjectName on = new ObjectName(THREAD_MXBEAN_NAME); 392 this.hasPlatformMXBeans = server.isRegistered(on); 393 this.hasHotSpotDiagnosticMXBean = 394 server.isRegistered(new ObjectName(HOTSPOT_DIAGNOSTIC_MXBEAN_NAME)); 395 // check if it has 6.0 new APIs 396 if (this.hasPlatformMXBeans) { 397 MBeanOperationInfo[] mopis = server.getMBeanInfo(on).getOperations(); 398 // look for findDeadlockedThreads operations; 399 for (MBeanOperationInfo op : mopis) { 400 if (op.getName().equals("findDeadlockedThreads")) { 401 this.supportsLockUsage = true; 402 break; 403 } 404 } 405 406 on = new ObjectName(COMPILATION_MXBEAN_NAME); 407 this.hasCompilationMXBean = server.isRegistered(on); 408 } 409 } catch (MalformedObjectNameException e) { 410 // should not reach here 411 throw new InternalError(e.getMessage()); 412 } catch (IntrospectionException e) { 413 InternalError ie = new InternalError(e.getMessage()); 414 ie.initCause(e); 415 throw ie; 416 } catch (InstanceNotFoundException e) { 417 InternalError ie = new InternalError(e.getMessage()); 418 ie.initCause(e); 419 throw ie; 420 } catch (ReflectionException e) { 421 InternalError ie = new InternalError(e.getMessage()); 422 ie.initCause(e); 423 throw ie; 424 } 425 426 if (hasPlatformMXBeans) { 427 // WORKAROUND for bug 5056632 428 // Check if the access role is correct by getting a RuntimeMXBean 429 getRuntimeMXBean(); 430 } 431 } 432 433 /** 434 * Gets a proxy client for a given local virtual machine. 435 */ 436 public static ProxyClient getProxyClient(LocalVirtualMachine lvm) 437 throws IOException { 438 final String key = getCacheKey(lvm); 439 ProxyClient proxyClient = cache.get(key); 440 if (proxyClient == null) { 441 proxyClient = new ProxyClient(lvm); 442 cache.put(key, proxyClient); 443 } 444 return proxyClient; 445 } 446 447 public static String getConnectionName(LocalVirtualMachine lvm) { 448 return Integer.toString(lvm.vmid()); 449 } 450 451 private static String getCacheKey(LocalVirtualMachine lvm) { 452 return Integer.toString(lvm.vmid()); 453 } 454 455 /** 456 * Gets a proxy client for a given JMXServiceURL. 457 */ 458 public static ProxyClient getProxyClient(String url, 459 String userName, String password) 460 throws IOException { 461 final String key = getCacheKey(url, userName, password); 462 ProxyClient proxyClient = cache.get(key); 463 if (proxyClient == null) { 464 proxyClient = new ProxyClient(url, userName, password); 465 cache.put(key, proxyClient); 466 } 467 return proxyClient; 468 } 469 470 public static String getConnectionName(String url, 471 String userName) { 472 if (userName != null && userName.length() > 0) { 473 return userName + "@" + url; 474 } else { 475 return url; 476 } 477 } 478 479 private static String getCacheKey(String url, 480 String userName, String password) { 481 return (url == null ? "" : url) + ":" + 482 (userName == null ? "" : userName) + ":" + 483 (password == null ? "" : password); 484 } 485 486 /** 487 * Gets a proxy client for a given "hostname:port". 488 */ 489 public static ProxyClient getProxyClient(String hostName, int port, 490 String userName, String password) 491 throws IOException { 492 final String key = getCacheKey(hostName, port, userName, password); 493 ProxyClient proxyClient = cache.get(key); 494 if (proxyClient == null) { 495 proxyClient = new ProxyClient(hostName, port, userName, password); 496 cache.put(key, proxyClient); 497 } 498 return proxyClient; 499 } 500 501 public static String getConnectionName(String hostName, int port, 502 String userName) { 503 String name = hostName + ":" + port; 504 if (userName != null && userName.length() > 0) { 505 return userName + "@" + name; 506 } else { 507 return name; 508 } 509 } 510 511 private static String getCacheKey(String hostName, int port, 512 String userName, String password) { 513 return (hostName == null ? "" : hostName) + ":" + 514 port + ":" + 515 (userName == null ? "" : userName) + ":" + 516 (password == null ? "" : password); 517 } 518 519 public String connectionName() { 520 return connectionName; 521 } 522 523 public String getDisplayName() { 524 return displayName; 525 } 526 527 public String toString() { 528 if (!isConnected()) { 529 return Resources.getText("ConnectionName (disconnected)", displayName); 530 } else { 531 return displayName; 532 } 533 } 534 535 public MBeanServerConnection getMBeanServerConnection() { 536 return server; 537 } 538 539 public String getUrl() { 540 return advancedUrl; 541 } 542 543 public String getHostName() { 544 return hostName; 545 } 546 547 public int getPort() { 548 return port; 549 } 550 551 public int getVmid() { 552 return (lvm != null) ? lvm.vmid() : 0; 553 } 554 555 public String getUserName() { 556 return userName; 557 } 558 559 public String getPassword() { 560 return password; 561 } 562 563 public void disconnect() { 564 // Reset remote stub 565 stub = null; 566 // Close MBeanServer connection 567 if (jmxc != null) { 568 try { 569 jmxc.close(); 570 } catch (IOException e) { 571 // Ignore ??? 572 } 573 } 574 // Reset platform MBean references 575 classLoadingMBean = null; 576 compilationMBean = null; 577 memoryMBean = null; 578 operatingSystemMBean = null; 579 runtimeMBean = null; 580 threadMBean = null; 581 sunOperatingSystemMXBean = null; 582 garbageCollectorMBeans = null; 583 // Set connection state to DISCONNECTED 584 if (!isDead) { 585 isDead = true; 586 setConnectionState(ConnectionState.DISCONNECTED); 587 } 588 } 589 590 /** 591 * Returns the list of domains in which any MBean is 592 * currently registered. 593 */ 594 public String[] getDomains() throws IOException { 595 return server.getDomains(); 596 } 597 598 /** 599 * Returns a map of MBeans with ObjectName as the key and MBeanInfo value 600 * of a given domain. If domain is <tt>null</tt>, all MBeans 601 * are returned. If no MBean found, an empty map is returned. 602 * 603 */ 604 public Map<ObjectName, MBeanInfo> getMBeans(String domain) 605 throws IOException { 606 607 ObjectName name = null; 608 if (domain != null) { 609 try { 610 name = new ObjectName(domain + ":*"); 611 } catch (MalformedObjectNameException e) { 612 // should not reach here 613 assert(false); 614 } 615 } 616 Set mbeans = server.queryNames(name, null); 617 Map<ObjectName,MBeanInfo> result = 618 new HashMap<ObjectName,MBeanInfo>(mbeans.size()); 619 Iterator iterator = mbeans.iterator(); 620 while (iterator.hasNext()) { 621 Object object = iterator.next(); 622 if (object instanceof ObjectName) { 623 ObjectName o = (ObjectName)object; 624 try { 625 MBeanInfo info = server.getMBeanInfo(o); 626 result.put(o, info); 627 } catch (IntrospectionException e) { 628 // TODO: should log the error 629 } catch (InstanceNotFoundException e) { 630 // TODO: should log the error 631 } catch (ReflectionException e) { 632 // TODO: should log the error 633 } 634 } 635 } 636 return result; 637 } 638 639 /** 640 * Returns a list of attributes of a named MBean. 641 * 642 */ 643 public AttributeList getAttributes(ObjectName name, String[] attributes) 644 throws IOException { 645 AttributeList list = null; 646 try { 647 list = server.getAttributes(name, attributes); 648 } catch (InstanceNotFoundException e) { 649 // TODO: A MBean may have been unregistered. 650 // need to set up listener to listen for MBeanServerNotification. 651 } catch (ReflectionException e) { 652 // TODO: should log the error 653 } 654 return list; 655 } 656 657 /** 658 * Set the value of a specific attribute of a named MBean. 659 */ 660 public void setAttribute(ObjectName name, Attribute attribute) 661 throws InvalidAttributeValueException, 662 MBeanException, 663 IOException { 664 try { 665 server.setAttribute(name, attribute); 666 } catch (InstanceNotFoundException e) { 667 // TODO: A MBean may have been unregistered. 668 } catch (AttributeNotFoundException e) { 669 assert(false); 670 } catch (ReflectionException e) { 671 // TODO: should log the error 672 } 673 } 674 675 /** 676 * Invokes an operation of a named MBean. 677 * 678 * @throws MBeanException Wraps an exception thrown by 679 * the MBean's invoked method. 680 */ 681 public Object invoke(ObjectName name, String operationName, 682 Object[] params, String[] signature) 683 throws IOException, MBeanException { 684 Object result = null; 685 try { 686 result = server.invoke(name, operationName, params, signature); 687 } catch (InstanceNotFoundException e) { 688 // TODO: A MBean may have been unregistered. 689 } catch (ReflectionException e) { 690 // TODO: should log the error 691 } 692 return result; 693 } 694 695 public synchronized ClassLoadingMXBean getClassLoadingMXBean() throws IOException { 696 if (hasPlatformMXBeans && classLoadingMBean == null) { 697 classLoadingMBean = 698 newPlatformMXBeanProxy(server, CLASS_LOADING_MXBEAN_NAME, 699 ClassLoadingMXBean.class); 700 } 701 return classLoadingMBean; 702 } 703 704 public synchronized CompilationMXBean getCompilationMXBean() throws IOException { 705 if (hasCompilationMXBean && compilationMBean == null) { 706 compilationMBean = 707 newPlatformMXBeanProxy(server, COMPILATION_MXBEAN_NAME, 708 CompilationMXBean.class); 709 } 710 return compilationMBean; 711 } 712 713 public Collection<MemoryPoolProxy> getMemoryPoolProxies() 714 throws IOException { 715 716 // TODO: How to deal with changes to the list?? 717 if (memoryPoolProxies == null) { 718 ObjectName poolName = null; 719 try { 720 poolName = new ObjectName(MEMORY_POOL_MXBEAN_DOMAIN_TYPE + ",*"); 721 } catch (MalformedObjectNameException e) { 722 // should not reach here 723 assert(false); 724 } 725 Set mbeans = server.queryNames(poolName, null); 726 if (mbeans != null) { 727 memoryPoolProxies = new ArrayList<MemoryPoolProxy>(); 728 Iterator iterator = mbeans.iterator(); 729 while (iterator.hasNext()) { 730 ObjectName objName = (ObjectName) iterator.next(); 731 MemoryPoolProxy p = new MemoryPoolProxy(this, objName); 732 memoryPoolProxies.add(p); 733 } 734 } 735 } 736 return memoryPoolProxies; 737 } 738 739 public synchronized Collection<GarbageCollectorMXBean> getGarbageCollectorMXBeans() 740 throws IOException { 741 742 // TODO: How to deal with changes to the list?? 743 if (garbageCollectorMBeans == null) { 744 ObjectName gcName = null; 745 try { 746 gcName = new ObjectName(GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*"); 747 } catch (MalformedObjectNameException e) { 748 // should not reach here 749 assert(false); 750 } 751 Set mbeans = server.queryNames(gcName, null); 752 if (mbeans != null) { 753 garbageCollectorMBeans = new ArrayList<GarbageCollectorMXBean>(); 754 Iterator iterator = mbeans.iterator(); 755 while (iterator.hasNext()) { 756 ObjectName on = (ObjectName) iterator.next(); 757 String name = GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + 758 ",name=" + on.getKeyProperty("name"); 759 760 GarbageCollectorMXBean mBean = 761 newPlatformMXBeanProxy(server, name, 762 GarbageCollectorMXBean.class); 763 garbageCollectorMBeans.add(mBean); 764 } 765 } 766 } 767 return garbageCollectorMBeans; 768 } 769 770 public synchronized MemoryMXBean getMemoryMXBean() throws IOException { 771 if (hasPlatformMXBeans && memoryMBean == null) { 772 memoryMBean = 773 newPlatformMXBeanProxy(server, MEMORY_MXBEAN_NAME, 774 MemoryMXBean.class); 775 } 776 return memoryMBean; 777 } 778 779 public synchronized RuntimeMXBean getRuntimeMXBean() throws IOException { 780 if (hasPlatformMXBeans && runtimeMBean == null) { 781 runtimeMBean = 782 newPlatformMXBeanProxy(server, RUNTIME_MXBEAN_NAME, 783 RuntimeMXBean.class); 784 } 785 return runtimeMBean; 786 } 787 788 789 public synchronized ThreadMXBean getThreadMXBean() throws IOException { 790 if (hasPlatformMXBeans && threadMBean == null) { 791 threadMBean = 792 newPlatformMXBeanProxy(server, THREAD_MXBEAN_NAME, 793 ThreadMXBean.class); 794 } 795 return threadMBean; 796 } 797 798 public synchronized OperatingSystemMXBean getOperatingSystemMXBean() throws IOException { 799 if (hasPlatformMXBeans && operatingSystemMBean == null) { 800 operatingSystemMBean = 801 newPlatformMXBeanProxy(server, OPERATING_SYSTEM_MXBEAN_NAME, 802 OperatingSystemMXBean.class); 803 } 804 return operatingSystemMBean; 805 } 806 807 public synchronized com.sun.management.OperatingSystemMXBean 808 getSunOperatingSystemMXBean() throws IOException { 809 810 try { 811 ObjectName on = new ObjectName(OPERATING_SYSTEM_MXBEAN_NAME); 812 if (sunOperatingSystemMXBean == null) { 813 if (server.isInstanceOf(on, 814 "com.sun.management.OperatingSystemMXBean")) { 815 sunOperatingSystemMXBean = 816 newPlatformMXBeanProxy(server, 817 OPERATING_SYSTEM_MXBEAN_NAME, 818 com.sun.management.OperatingSystemMXBean.class); 819 } 820 } 821 } catch (InstanceNotFoundException e) { 822 return null; 823 } catch (MalformedObjectNameException e) { 824 return null; // should never reach here 825 } 826 return sunOperatingSystemMXBean; 827 } 828 829 public synchronized HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOException { 830 if (hasHotSpotDiagnosticMXBean && hotspotDiagnosticMXBean == null) { 831 hotspotDiagnosticMXBean = 832 newPlatformMXBeanProxy(server, HOTSPOT_DIAGNOSTIC_MXBEAN_NAME, 833 HotSpotDiagnosticMXBean.class); 834 } 835 return hotspotDiagnosticMXBean; 836 } 837 838 public <T> T getMXBean(ObjectName objName, Class<T> interfaceClass) 839 throws IOException { 840 return newPlatformMXBeanProxy(server, 841 objName.toString(), 842 interfaceClass); 843 844 } 845 846 // Return thread IDs of deadlocked threads or null if any. 847 // It finds deadlocks involving only monitors if it's a Tiger VM. 848 // Otherwise, it finds deadlocks involving both monitors and 849 // the concurrent locks. 850 public long[] findDeadlockedThreads() throws IOException { 851 ThreadMXBean tm = getThreadMXBean(); 852 if (supportsLockUsage && tm.isSynchronizerUsageSupported()) { 853 return tm.findDeadlockedThreads(); 854 } else { 855 return tm.findMonitorDeadlockedThreads(); 856 } 857 } 858 859 public synchronized void markAsDead() { 860 disconnect(); 861 } 862 863 public boolean isDead() { 864 return isDead; 865 } 866 867 boolean isConnected() { 868 return !isDead(); 869 } 870 871 boolean hasPlatformMXBeans() { 872 return this.hasPlatformMXBeans; 873 } 874 875 boolean hasHotSpotDiagnosticMXBean() { 876 return this.hasHotSpotDiagnosticMXBean; 877 } 878 879 boolean isLockUsageSupported() { 880 return supportsLockUsage; 881 } 882 883 public boolean isRegistered(ObjectName name) throws IOException { 884 return server.isRegistered(name); 885 } 886 887 public void addPropertyChangeListener(PropertyChangeListener listener) { 888 propertyChangeSupport.addPropertyChangeListener(listener); 889 } 890 891 public void addWeakPropertyChangeListener(PropertyChangeListener listener) { 892 if (!(listener instanceof WeakPCL)) { 893 listener = new WeakPCL(listener); 894 } 895 propertyChangeSupport.addPropertyChangeListener(listener); 896 } 897 898 public void removePropertyChangeListener(PropertyChangeListener listener) { 899 if (!(listener instanceof WeakPCL)) { 900 // Search for the WeakPCL holding this listener (if any) 901 for (PropertyChangeListener pcl : propertyChangeSupport.getPropertyChangeListeners()) { 902 if (pcl instanceof WeakPCL && ((WeakPCL)pcl).get() == listener) { 903 listener = pcl; 904 break; 905 } 906 } 907 } 908 propertyChangeSupport.removePropertyChangeListener(listener); 909 } 910 911 /** 912 * The PropertyChangeListener is handled via a WeakReference 913 * so as not to pin down the listener. 914 */ 915 private class WeakPCL extends WeakReference<PropertyChangeListener> 916 implements PropertyChangeListener { 917 WeakPCL(PropertyChangeListener referent) { 918 super(referent); 919 } 920 921 public void propertyChange(PropertyChangeEvent pce) { 922 PropertyChangeListener pcl = get(); 923 924 if (pcl == null) { 925 // The referent listener was GC'ed, we're no longer 926 // interested in PropertyChanges, remove the listener. 927 dispose(); 928 } else { 929 pcl.propertyChange(pce); 930 } 931 } 932 933 private void dispose() { 934 removePropertyChangeListener(this); 935 } 936 } 937 938 // 939 // Snapshot MBeanServerConnection: 940 // 941 // This is an object that wraps an existing MBeanServerConnection and adds 942 // caching to it, as follows: 943 // 944 // - The first time an attribute is called in a given MBean, the result is 945 // cached. Every subsequent time getAttribute is called for that attribute 946 // the cached result is returned. 947 // 948 // - Before every call to VMPanel.update() or when the Refresh button in the 949 // Attributes table is pressed down the attributes cache is flushed. Then 950 // any subsequent call to getAttribute will retrieve all the values for 951 // the attributes that are known to the cache. 952 // 953 // - The attributes cache uses a learning approach and only the attributes 954 // that are in the cache will be retrieved between two subsequent updates. 955 // 956 957 public interface SnapshotMBeanServerConnection 958 extends MBeanServerConnection { 959 /** 960 * Flush all cached values of attributes. 961 */ 962 public void flush(); 963 } 964 965 public static class Snapshot { 966 private Snapshot() { 967 } 968 public static SnapshotMBeanServerConnection 969 newSnapshot(MBeanServerConnection mbsc) { 970 final InvocationHandler ih = new SnapshotInvocationHandler(mbsc); 971 return (SnapshotMBeanServerConnection) Proxy.newProxyInstance( 972 Snapshot.class.getClassLoader(), 973 new Class[] {SnapshotMBeanServerConnection.class}, 974 ih); 975 } 976 } 977 978 static class SnapshotInvocationHandler implements InvocationHandler { 979 980 private final MBeanServerConnection conn; 981 private Map<ObjectName, NameValueMap> cachedValues = newMap(); 982 private Map<ObjectName, Set<String>> cachedNames = newMap(); 983 984 @SuppressWarnings("serial") 985 private static final class NameValueMap 986 extends HashMap<String, Object> {} 987 988 SnapshotInvocationHandler(MBeanServerConnection conn) { 989 this.conn = conn; 990 } 991 992 synchronized void flush() { 993 cachedValues = newMap(); 994 } 995 996 public Object invoke(Object proxy, Method method, Object[] args) 997 throws Throwable { 998 final String methodName = method.getName(); 999 if (methodName.equals("getAttribute")) { 1000 return getAttribute((ObjectName) args[0], (String) args[1]); 1001 } else if (methodName.equals("getAttributes")) { 1002 return getAttributes((ObjectName) args[0], (String[]) args[1]); 1003 } else if (methodName.equals("flush")) { 1004 flush(); 1005 return null; 1006 } else { 1007 try { 1008 return method.invoke(conn, args); 1009 } catch (InvocationTargetException e) { 1010 throw e.getCause(); 1011 } 1012 } 1013 } 1014 1015 private Object getAttribute(ObjectName objName, String attrName) 1016 throws MBeanException, InstanceNotFoundException, 1017 AttributeNotFoundException, ReflectionException, IOException { 1018 final NameValueMap values = getCachedAttributes( 1019 objName, Collections.singleton(attrName)); 1020 Object value = values.get(attrName); 1021 if (value != null || values.containsKey(attrName)) { 1022 return value; 1023 } 1024 // Not in cache, presumably because it was omitted from the 1025 // getAttributes result because of an exception. Following 1026 // call will probably provoke the same exception. 1027 return conn.getAttribute(objName, attrName); 1028 } 1029 1030 private AttributeList getAttributes( 1031 ObjectName objName, String[] attrNames) throws 1032 InstanceNotFoundException, ReflectionException, IOException { 1033 final NameValueMap values = getCachedAttributes( 1034 objName, 1035 new TreeSet<String>(Arrays.asList(attrNames))); 1036 final AttributeList list = new AttributeList(); 1037 for (String attrName : attrNames) { 1038 final Object value = values.get(attrName); 1039 if (value != null || values.containsKey(attrName)) { 1040 list.add(new Attribute(attrName, value)); 1041 } 1042 } 1043 return list; 1044 } 1045 1046 private synchronized NameValueMap getCachedAttributes( 1047 ObjectName objName, Set<String> attrNames) throws 1048 InstanceNotFoundException, ReflectionException, IOException { 1049 NameValueMap values = cachedValues.get(objName); 1050 if (values != null && values.keySet().containsAll(attrNames)) { 1051 return values; 1052 } 1053 attrNames = new TreeSet<String>(attrNames); 1054 Set<String> oldNames = cachedNames.get(objName); 1055 if (oldNames != null) { 1056 attrNames.addAll(oldNames); 1057 } 1058 values = new NameValueMap(); 1059 final AttributeList attrs = conn.getAttributes( 1060 objName, 1061 attrNames.toArray(new String[attrNames.size()])); 1062 for (Attribute attr : attrs.asList()) { 1063 values.put(attr.getName(), attr.getValue()); 1064 } 1065 cachedValues.put(objName, values); 1066 cachedNames.put(objName, attrNames); 1067 return values; 1068 } 1069 1070 // See http://www.artima.com/weblogs/viewpost.jsp?thread=79394 1071 private static <K, V> Map<K, V> newMap() { 1072 return new HashMap<K, V>(); 1073 } 1074 } 1075 }