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