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