JMX是一个框架,提供了一种功能,可以实时查询应用程序中通过JMX向外部公布的相应参数或者是其他应用程序,同时也可以通过JMX来实时地调用应用程序使用JMX向外部公布的接口,来完成一些功能操作。
如果想要对远程服务器的进程进行监控,需要在服务器进行相关设置,启动守护进程。如果想进一步定制自己的MXBean,可以考虑在应用程序中registerMXBean,当然在web应用中也可以借助Spring MBean相关类库进行该操作。
开启JStatd守护进程
编写配置文件 jstatd.all.policy策略文件:
grant codebase "file:${java.home}/../lib/tools.jar" { permission java.security.AllPermission; };
开启jstatd守护进程,并开启日志(注意线上开启日志可能会导致磁盘空间占用较大)
nohup jstatd -J-Djava.security.policy=/home/java/jstatd.all.policy -J-Djava.rmi.server.logCalls=true &
如果 开启日志功能,则会导致生成的日志文件比较大,通常几周时间可能会超过2G,为了磁盘考虑,在运行良好的服务器上执行-J-Djava.rmi.server.logCalls=false即可。
jstatd -J-Djava.security.policy=jstatd.all.policy Could not create remote object access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write") java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write") at java.security.AccessControlContext.checkPermission(AccessControlContext.java:457) at java.security.AccessController.checkPermission(AccessController.java:884) at java.lang.SecurityManager.checkPermission(SecurityManager.java:549) at java.lang.System.setProperty(System.java:789) at sun.tools.jstatd.Jstatd.main(Jstatd.java:139)
http://docs.oracle.com/javase/7/docs/technotes/tools/share/jstatd.html,编写该文件不需要自作聪明地将java.home设置成其他属性,只需要按照文档中说明的即可。
在启动tomcat的时候(修改执行tomcat的文件,catalina.sh),需要加入以下JVM参数:
-Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.1.165
说明:
- -Dcom.sun.management.jmxremote.port:这个是配置远程connection的端口号的,要确定这个端口没有被占用;
- -Dcom.sun.management.jmxremote.ssl=false 指定了 JMX 是否启用 ssl;
- -Dcom.sun.management.jmxremote.authenticate=false指定了JMX 是否启用鉴权(需要用户名,密码鉴权),
- 2,3两个是固定配置,是 JMX的远程服务权限的;
- -Djava.rmi.server.hostname:这个是配置server的IP的;
在CentOS环境中启动jstatd仍然出现错误,
Could not bind /JStatRemoteHost to RMI Registry java.rmi.ConnectIOException: Exception creating connection to: 0.0.0.2; nested exception is: java.net.SocketException: Invalid argument or cannot assign requested address at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:631) at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:216) at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:202) at sun.rmi.server.UnicastRef.newCall(UnicastRef.java:341) at sun.rmi.registry.RegistryImpl_Stub.rebind(Unknown Source) at java.rmi.Naming.rebind(Naming.java:177) at sun.tools.jstatd.Jstatd.bind(Jstatd.java:57) at sun.tools.jstatd.Jstatd.main(Jstatd.java:143) Caused by: java.net.SocketException: Invalid argument or cannot assign requested address at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:579) at java.net.Socket.connect(Socket.java:528) at java.net.Socket.<init>(Socket.java:425) at java.net.Socket.<init>(Socket.java:208) at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket(RMIDirectSocketFactory.java:40) at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket(RMIMasterSocketFactory.java:147) at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:613) ... 7 more
通过阅读下面的文档 http://my.oschina.net/xiaotian120/blog/207015,经过服务器上的一番调研,发现问题出现在hostname上,服务端调用 hostname -i 命令,显示出来的服务器主机名为0.0.0.2(这一点日志中有体现)。
JMX监控认证
如果我们将jmxremote的认证设置为false,此时在进行JMX关联时不会进行认证,如果需要认证,将其设置为true:
-Dcom.sun.management.jmxremote.authenticate=true
但此时同样需要增加参数,用来指定jmx远程监控的访问文件以及密码文件。
-Dcom.sun.management.jmxremote.access.file=/usr/java/jdk1.6.0_32/jre/lib/management/jmxremote.access -Dcom.sun.management.jmxremote.password.file=/usr/java/jdk1.6.0_32/jre/lib/management/jmxremote.password"
启动java进程时出现下面的错误:
错误: 必须限制口令文件读取访问权限: /home/java/jmxremote.password
这是由于两个文件jmxremote.access, jmxremote.password的访问权限有问题,必须要按照如下规则设置:
chmod 400 jmxremote.access 要求该文件是任何用户均不可写的 chmod 700 jmxremote.password 一定注意这个文件默认是不可写的
此时,如果在外部需要连接该jmx连接时,请求提供安全凭证:
如果没有修改之前的两个文件,那么直接使用“monitorRole/QED”(或controllerRole)登录即可。
如果需要定制该服务,修改access和password文件即可,可以定制其权限。
注意,如果发现通过JVisualVM无法看到CPU,MXBean的一些状态信息,考虑可能是由于JVM启动参数host错误。
新建第一个MXBean
通过JMX可以轻松地为应用程序添加管理功能,在尽可能少的改变原有系统代码基础上实现对原系统的管理。
注册时使用MBeanServer进行注册的操作,给定对应的mbean,mbeanServer会根据该bean实现的接口去寻找其对应的MBean定义。
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ControllerMBean controllerMBean = new ControllerBean(); mbs.registerMBean(controllerMBean, new ObjectName("MyAppmbean:name=controller"));
在第一次运行时出现如下问题:
javax.management.NotCompliantMBeanException: jmx.ControllerBean: Class jmx.ControllerBean is not a JMX compliant MXBean at com.sun.jmx.mbeanserver.Introspector.throwException(Introspector.java:466) at com.sun.jmx.mbeanserver.Introspector.getMXBeanInterface(Introspector.java:357) at com.sun.jmx.mbeanserver.Introspector.checkCompliance(Introspector.java:166) at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:317) at com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:522) at jmx.Main.main(Main.java:16)
经过分析JMX.isMXBeanInterface方法,可以看出,在registerMBean的时候,会根据当前类实现的接口列表,查找符合MXBean定义的接口,如果接口不符合MXBean的定义就会抛出上面的错误,如何使得接口满足MXBean类型,一种是接口名称以“MXBean”结尾,一种是加入@MXBean Annotation。
public static boolean isMXBeanInterface(Class<?> interfaceClass) { if (!interfaceClass.isInterface()) return false; if (!Modifier.isPublic(interfaceClass.getModifiers()) && !Introspector.ALLOW_NONPUBLIC_MBEAN) { return false; } MXBean a = interfaceClass.getAnnotation(MXBean.class); if (a != null) return a.value(); return interfaceClass.getName().endsWith("MXBean"); // We don't bother excluding the case where the name is // exactly the string "MXBean" since that would mean there // was no package name, which is pretty unlikely in practice. }
Spring与JMX的整合
对于标准的MBean来说,主要是通过MBeanExporter来实现的,比如上面我们提到的MBean,就可以通过配置mbServer以及mbean的方式管理MXBean:
<bean id="mbServer" class="org.springframework.jmx.export.MBeanExporter"> <property name="autodetectModeName" value="AUTODETECT_ALL"/> </bean> <bean name="mydomain:myobj=MyObjectMBean" class="com.api.example.jmx.ControllerBean"/>
上面的配置中,MBeanExporter会查找本地MBean Server,指定的探测模式autodetectModeName为AUTODETECT_ALL,无需手动向MBean Server进行注册,便可以管理配置中的MBean对象。
基于注解的MBean管理
@ManagedResource表示指定该类的实例作为MBean注册到MBean Server中,然后通过对属性和方法分别使用@ManagedAttribute和@ManagedOperation来指定暴露的属性和方法,
@Component @ManagedResource(objectName = "org.springexample.jmx:name=ServerManager", description = "Server Manager") public class ServerManagerImpl { private String serverName = "springServer"; private boolean serverRunning = true; private int minPoolSize = 5; private int maxPoolSize = 10; @ManagedAttribute(description = "The server name.") public String getServerName() { return serverName; } @ManagedAttribute(description = "Server's running status.") public boolean isServerRunning() { return serverRunning; } @ManagedAttribute(description = "Whether or not the server is running.", currencyTimeLimit = 20, persistPolicy = "OnUpdate") public void setServerRunning(boolean serverRunning) { this.serverRunning = serverRunning; } @ManagedOperation(description = "Change db connection pool size.") @ManagedOperationParameters({ @ManagedOperationParameter(name = "min", description = "Minimum pool size."), @ManagedOperationParameter(name = "max", description = "Maximum pool size.") }) public int changeConnectionPoolSize(int minPoolSize, int maxPoolSize) { Assert.isTrue(minPoolSize > 0, "Minimum connection pool size must be larger than 0, min=" + minPoolSize); Assert.isTrue(maxPoolSize > minPoolSize, String.format("Minimum connection pool size must be smaller than maximum, min=%s, max=%s", minPoolSize, maxPoolSize)); this.minPoolSize = minPoolSize; this.maxPoolSize = maxPoolSize; int diff = maxPoolSize - minPoolSize; Random random = new Random(); int currentSize = (minPoolSize + random.nextInt(diff)); return currentSize; } }
基于spring基于注解的MBean管理可以针对一个普通的Java类,指定暴露的属性和方法,此时在spring applicationContext中声明的内容就要少很多,只需要声明扫描的package,以及mbean-export即可。
<context:component-scan base-package="com.api.example"/> <context:mbean-export/>
这里我们就可以通过JVisualVM来简单地动态修改类似池化的一些配置,并使其生效
。