MBean与JMX

欢迎查看Eetal的第二十二篇博客–MBean与JMX

JMX

JMX(java Management Exetensions)在Java编程语言中定义了应用程序以及网络管理和监控的体系结构、设计模式、应用程序接口以及服务。
通常使用JMX来监控系统的运行状态或管理系统的某些方面,比如清空缓存、重新加载配置文件等
优点是可以非常容易的使应用程序被管理
伸缩性的架构使每个JMX Agent Service可以很容易的放入到Agent中,每个JMX的实现都提供几个核心的Agent Service,你也可以自己编写服务,服务可以很容易的部署,取消部署。
主要作用是提供接口,允许有不同的实现
简单来说,jmx是一个用来管理javaBean并可以进行监控的扩展规范,结合MBeanServer、rmi与http等可以作为一个服务监控和提供中心

MBeanServer

MBeanServer是JMX代理的核心组件。
它是在代理中向管理操作公开的对象的注册表。
向MBeanServer注册的任何对象都对管理应用程序可见。
MBeanServer仅公开MBean的管理接口,而不是它的直接对象引用。
您要从代理的Java VM外部管理的任何资源都必须在MBeanServer中注册为MBean。
MBeanServer还提供标准化接口,用于访问同一Java VM中的MBean,为本地对象提供操作可管理资源的所有好处。
需要注意的是,一般不使用MBeanServerFactory.createMBeanServer(),使用ManagementFactory.getPlatformMBeanServer()
后者在当前尚未在ManagementFactory中注册静态成员MBeanServer时,会先使用MBeanServerFactory.createMBeanServer()创建一个MBeanServer并将其注册到静态成员,后续每次调用会直接返回该成员实例
并且ManagementFactory.getPlatformMBeanServer()方法在第一次调用createMBeanServer()实例化MBeanServer以后,会读取PlatformComponent枚举的枚举值,将一些系统必要的MBean注册到MBeanServer
JConsole监控的MBeanServer就是在ManagementFactory中注册的静态成员MBeanServe
所以如果没有特殊配置的MBeanServer,jconsole是不会监控的

Agent

Java Management Extensions(JMX)Agent是一个在Java虚拟机(Java VM)中运行的管理实体.
充当MBean和管理应用程序(JConsole等)之间的联络人
Agent只是一个规范,一般会封装我们创建和启动MBeanServer以及注册MBean的过程在一个Agent行为里,方便启动

Agent Service

Agent Service是可以对MBeanServer中注册的MBean执行管理操作的对象。
通过将管理智能包含在代理中,JMX可帮助您构建更强大的管理解决方案。
Agent Service通常也是MBean,允许通过MBeanServer控制它们及其功能。
JMX规范定义了以下Agent Service:
通过管理applet(m-let)服务的动态类加载检索并实例化从网络动态下载的新类和本机库。
监视器观察MBean属性的数字或字符串值,并可以向其他对象通知几种类型的更改。
定时器提供调度机制,并且可以以预定间隔发送通知。
关系服务定义MBean之间的关联并维护关系的一致性。

Protocol Adaptors and Connectors

Protocol adaptors and connectors使代理可从远程(通过rmi或者http等协议)管理应用程序访问。
它们通过在MBean服务器中实例化和注册的MBean的特定协议提供视图。
它们使Java VM外部的管理应用程序能够:
获取或设置现有MBean的属性
对现有MBean执行操作
实例化并注册新的MBean
注册并接收MBean发出的通知
因此,要使JMX代理易于管理,它必须至少包含一个协议适配器或连接器。
Java SE平台包括标准RMI连接器。Agent可以包括任意数量的协议适配器和连接器,允许通过不同的协议同时远程管理和监视它

Protocol Adaptors

Protocol Adaptors通过给定协议提供JMX代理的管理视图。
它们将MBean和MBean服务器的操作调整为给定协议中的表示,
并可能调整为不同的信息模型,例如SNMP。Java SE平台不包括任何协议适配器作为标准。
连接到Protocol Adaptors的管理应用程序通常特定于给定的协议。
这通常是依赖于特定管理协议的传统管理解决方案的情况。
它们不是通过MBean服务器的远程表示来访问JMX代理,而是通过映射到MBeanServer的操作来访问JMX代理。

Connectors

Connectors用于将代理与为JMX技术启用的远程管理应用程序连接,即使用JMX规范的分布式服务开发的管理应用程序。
这种通信涉及Agent中的连接器服务器和管理器中的连接器客户端。
这些组件以特定协议的方式透明地传递管理操作。
JMX Remote API为MBeanServer提供远程接口,管理应用程序可以通过该接口执行操作。
Connectors特定于给定协议,但管理应用程序可以无差别地使用任何Connectors,因为它们具有相同的远程接口。

MBean

描述一个可管理的资源。是一个java对象,遵循以下一些规则:
1.必须是公用的,非抽象的类
2.必须有至少一个公用的构造器
3.必须实现它自己的相应的MBean接口或者实现javax.management.DynamicMBean接口
4.可选的,一个MBean可以实现javax.management.NotificationBroadcaster接口MBean的类型
MBean使用ObjectName以keyValue形式注册到MBeanServer上

Standard MBean

标准MBean,也是最简单的MBean,通过类实现的接口名称来识别MBean
接口名称以MBean结尾,实现类需要匹配接口名称的前半部分

1
2
3
4
public interface YytMBean{
public void setName(String name);
public String getName();
}

1
2
3
4
5
6
7
8
9
10
11
public class Yyt implements YytMBean{
private String name;
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class YytAgent{
private String domainName;
private int rmiPort;
public YytAgent(String domainName,int rmiPort) {
this.domainName = domainName;
this.rmiPort = rmiPort;
}
public void start() throws MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, IOException {
//MBeanServerFactory.createMBeanServer(); //don't use this
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); //use this instead

YytMBean yytMBean = new Yyt();
ObjectName objectName = new ObjectName(domainName+":name=Yyt");
mBeanServer.registerMBean(yytMBean, objectName);

LocateRegistry.createRegistry(rmiPort);//开启rmi端口监听,在jmxConnectorServer.start()时会根据serviceUrl建立一个rmiServer监听该rmi端口

JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:"+rmiPort+"/"+domainName);
JMXConnectorServer jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mBeanServer);
jmxConnectorServer.start();
//开启jmx协议的监控服务器,因为java自带rmi的工具和依赖,可以直接开启,通过jconsole等支持jmx协议的客户端可以监控MBeanServer
}
}

使用JConsole工具监控

JConsole是java自带的监控程序,独立jre中没有,jdk下的jre具备

运行java应用时带上参数
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
关闭ssl和密码校验,开启远程监控端口8999,这时可以直接使用jconsole进行链接

或者在jdk\jre\lib\management目录下,复制一份jmxremote.password.template的文件,改名去掉.template后缀,并去掉文件末尾两行示例的用户名和密码注释,修改密码为自己想要的
运行加上参数
-Djava.rmi.server.hostname=应用所在服务器的ip或者域名
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=监控端口
-Dcom.sun.management.jmxremote.rmi.port=rmi监控端口(一般与监控端口保持一致)
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.ssl=false
这时使用JConsole链接会提示需要输入用户名和密码

在命令行输入jconsole运行jconsole程序
jconsole
在jconsole的界面,因为是本地,直接选择本地进程,进入监控页面
点击导航栏的MBean即可看到我们注册的MBean在列表中
jconsole
还可以对Bean的属性值进行查看和设置
jconsole

源码分析

ManagementFactory.getPlatformMBeanServer()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
Permission perm = new MBeanServerPermission("createMBeanServer");
sm.checkPermission(perm);
}

if (platformMBeanServer == null) {
platformMBeanServer = MBeanServerFactory.createMBeanServer();
for (PlatformComponent pc : PlatformComponent.values()) {
List<? extends PlatformManagedObject> list =
pc.getMXBeans(pc.getMXBeanInterface());
for (PlatformManagedObject o : list) {
// Each PlatformComponent represents one management
// interface. Some MXBean may extend another one.
// The MXBean instances for one platform component
// (returned by pc.getMXBeans()) might be also
// the MXBean instances for another platform component.
// e.g. com.sun.management.GarbageCollectorMXBean
//
// So need to check if an MXBean instance is registered
// before registering into the platform MBeanServer
if (!platformMBeanServer.isRegistered(o.getObjectName())) {
addMXBean(platformMBeanServer, o);
}
}
}
HashMap<ObjectName, DynamicMBean> dynmbeans =
ManagementFactoryHelper.getPlatformDynamicMBeans();
for (Map.Entry<ObjectName, DynamicMBean> e : dynmbeans.entrySet()) {
addDynamicMBean(platformMBeanServer, e.getValue(), e.getKey());
}
for (final PlatformManagedObject o :
ExtendedPlatformComponent.getMXBeans()) {
if (!platformMBeanServer.isRegistered(o.getObjectName())) {
addMXBean(platformMBeanServer, o);
}
}
}
return platformMBeanServer;

ManagementFactory中定义一个platformMBeanServer成员来装载创建好的MBeanServer
在执行该方法的第一步会先校验当前安全权限
在platformMBeanServer尚未绑定一个实例时,会先使用MBeanServerFactory.createMBeanServer()实例一个MBeanServer对象
接下来主要是读取PlatformComponent.values()以及别的枚举类和helper类的值和成员,将系统预设的MBean注册到MBeanServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
enum PlatformComponent {

/**
* Class loading system of the Java virtual machine.
*/
CLASS_LOADING(
"java.lang.management.ClassLoadingMXBean",
"java.lang", "ClassLoading", defaultKeyProperties(),
true, // singleton
new MXBeanFetcher<ClassLoadingMXBean>() {
public List<ClassLoadingMXBean> getMXBeans() {
return Collections.singletonList(ManagementFactoryHelper.getClassLoadingMXBean());
}
}),

/**
* Compilation system of the Java virtual machine.
*/
COMPILATION(
"java.lang.management.CompilationMXBean",
"java.lang", "Compilation", defaultKeyProperties(),
true, // singleton
new MXBeanFetcher<CompilationMXBean>() {
public List<CompilationMXBean> getMXBeans() {
CompilationMXBean m = ManagementFactoryHelper.getCompilationMXBean();
if (m == null) {
return Collections.emptyList();
} else {
return Collections.singletonList(m);
}
}
}),
.....
}

MBeanServerFactory.createMBeanServer()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
   public static MBeanServer createMBeanServer() {
return createMBeanServer(null);
}
public static MBeanServer createMBeanServer(String domain) {
checkPermission("createMBeanServer");

final MBeanServer mBeanServer = newMBeanServer(domain);
addMBeanServer(mBeanServer);
return mBeanServer;
}
public static MBeanServer newMBeanServer(String domain) {
checkPermission("newMBeanServer");

// Get the builder. Creates a new one if necessary.
//
final MBeanServerBuilder mbsBuilder = getNewMBeanServerBuilder();
// Returned value cannot be null. NullPointerException if violated.

synchronized(mbsBuilder) {
final MBeanServerDelegate delegate =
mbsBuilder.newMBeanServerDelegate();
if (delegate == null) {
final String msg =
"MBeanServerBuilder.newMBeanServerDelegate() " +
"returned null";
throw new JMRuntimeException(msg);
}
final MBeanServer mbeanServer =
mbsBuilder.newMBeanServer(domain,null,delegate);
if (mbeanServer == null) {
final String msg =
"MBeanServerBuilder.newMBeanServer() returned null";
throw new JMRuntimeException(msg);
}
return mbeanServer;
}
}

createMBeanServer方法会调用newMBeanServer方法实例化MBeanServer
newMBeanServer方法中会先使用MBeanServerBuilder.newMBeanServerDelegate()实例化一个MBeanServerBuilder
再使用MBeanServerBuilder.newMBeanServer(domain,null,delegate)方法实例化一个MBeanServer

1
2
3
4
5
6
7
8
9
public MBeanServer newMBeanServer(String  defaultDomain,
MBeanServer outer,
MBeanServerDelegate delegate) {
// By default, MBeanServerInterceptors are disabled.
// Use com.sun.jmx.mbeanserver.MBeanServerBuilder to obtain
// MBeanServers on which MBeanServerInterceptors are enabled.
return JmxMBeanServer.newMBeanServer(defaultDomain,outer,delegate,
false);
}

newMBeanServer方法实际实例化并返回的是一个JmxMBeanServer对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//in JmxMBeanServer
public static MBeanServer newMBeanServer(String defaultDomain,
MBeanServer outer,
MBeanServerDelegate delegate,
boolean interceptors) {
// Determine whether to use fair locking for the repository.
// Default is true.
final boolean fairLock = DEFAULT_FAIR_LOCK_POLICY;

checkNewMBeanServerPermission();

// This constructor happens to disregard the value of the interceptors
// flag - that is, it always uses the default value - false.
// This is admitedly a bug, but we chose not to fix it for now
// since we would rather not have anybody depending on the Sun private
// interceptor APIs - which is most probably going to be removed and
// replaced by a public (javax) feature in the future.
//
return new JmxMBeanServer(defaultDomain,outer,delegate,null,
interceptors,fairLock);
}

JmxMBeanServer(String domain, MBeanServer outer,
MBeanServerDelegate delegate,
MBeanInstantiator instantiator,
boolean interceptors,
boolean fairLock) {

if (instantiator == null) {
final ModifiableClassLoaderRepository
clr = new ClassLoaderRepositorySupport();
instantiator = new MBeanInstantiator(clr);
}

final MBeanInstantiator fInstantiator = instantiator;
this.secureClr = new
SecureClassLoaderRepository(AccessController.doPrivileged(new PrivilegedAction<ClassLoaderRepository>() {
@Override
public ClassLoaderRepository run() {
return fInstantiator.getClassLoaderRepository();
}
})
);
if (delegate == null)
delegate = new MBeanServerDelegateImpl();
if (outer == null)
outer = this;

this.instantiator = instantiator;
this.mBeanServerDelegateObject = delegate;
this.outerShell = outer;

final Repository repository = new Repository(domain);
this.mbsInterceptor =
new DefaultMBeanServerInterceptor(outer, delegate, instantiator,
repository);
this.interceptorsEnabled = interceptors;
initialize();
}

实例化一个Repository并聚合到新生成的一个DefaultMBeanServerInterceptor
Repository就是MBeanServer存放MBean的容器类,其内部维护一个Map<String,Map<String,NamedObject>> domainTb的成员
此处由于是第一次实例化MBeanServer,传入的domain为null,最终会使用ServiceName的静态的成员值DefaultDomain作为默认域

1
2
3
4
5
6
7
8
9
10
public class ServiceName {

/**
* The default domain.
* <BR>
* The value is <CODE>DefaultDomain</CODE>.
*/
public static final String DOMAIN = "DefaultDomain";
.....
}

NamedObject类是聚合ObjectName和DynamicMBean的辅助类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class NamedObject  {


/**
* Object name.
*/
private final ObjectName name;

/**
* Object reference.
*/
private final DynamicMBean object;
.....
}

后续的注册bean和获取bean等方法实现最终都是由mbsInterceptor成员去操作

LocateRegistry.createRegistry

创建注册中心并开启端口监听,返回创建的RegistryImpl实例
源码分析请参照本人的另一篇博客——java-rmi源码解析

MBeanServer.registerMBean

在JmxMBeanServer中的实现

1
2
3
4
5
6
public ObjectInstance registerMBean(Object object, ObjectName name)
throws InstanceAlreadyExistsException, MBeanRegistrationException,
NotCompliantMBeanException {

return mbsInterceptor.registerMBean(object, cloneObjectName(name));
}

上面讲到,默认的MBeanServer的mbsInterceptor成员由new DefaultMBeanServerInterceptor(outer, delegate, instantiator,repository)实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public ObjectInstance registerMBean(Object object, ObjectName name)
throws InstanceAlreadyExistsException, MBeanRegistrationException,
NotCompliantMBeanException {

// ------------------------------
// ------------------------------
Class<?> theClass = object.getClass();

Introspector.checkCompliance(theClass);

final String infoClassName = getNewMBeanClassName(object);

checkMBeanPermission(infoClassName, null, name, "registerMBean");
checkMBeanTrustPermission(theClass);

return registerObject(infoClassName, object, name);
}
private ObjectInstance registerObject(String classname,
Object object, ObjectName name)
throws InstanceAlreadyExistsException,
MBeanRegistrationException,
NotCompliantMBeanException {

if (object == null) {
final RuntimeException wrapped =
new IllegalArgumentException("Cannot add null object");
throw new RuntimeOperationsException(wrapped,
"Exception occurred trying to register the MBean");
}

DynamicMBean mbean = Introspector.makeDynamicMBean(object);

return registerDynamicMBean(classname, mbean, name);
}

DefaultMBeanServerInterceptor会将要注册的MBean封装为DynamicMBean,调用registerDynamicMBean进行注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
private ObjectInstance registerDynamicMBean(String classname,
DynamicMBean mbean,
ObjectName name)
throws InstanceAlreadyExistsException,
MBeanRegistrationException,
NotCompliantMBeanException {


name = nonDefaultDomain(name);

if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
MBEANSERVER_LOGGER.logp(Level.FINER,
DefaultMBeanServerInterceptor.class.getName(),
"registerMBean", "ObjectName = " + name);
}

ObjectName logicalName = preRegister(mbean, server, name);

// preRegister returned successfully, so from this point on we
// must call postRegister(false) if there is any problem.
boolean registered = false;
boolean registerFailed = false;
ResourceContext context = null;

try {
if (mbean instanceof DynamicMBean2) {
try {
((DynamicMBean2) mbean).preRegister2(server, logicalName);
registerFailed = true; // until we succeed
} catch (Exception e) {
if (e instanceof RuntimeException)
throw (RuntimeException) e;
if (e instanceof InstanceAlreadyExistsException)
throw (InstanceAlreadyExistsException) e;
throw new RuntimeException(e);
}
}

if (logicalName != name && logicalName != null) {
logicalName =
ObjectName.getInstance(nonDefaultDomain(logicalName));
}

checkMBeanPermission(classname, null, logicalName, "registerMBean");

if (logicalName == null) {
final RuntimeException wrapped =
new IllegalArgumentException("No object name specified");
throw new RuntimeOperationsException(wrapped,
"Exception occurred trying to register the MBean");
}

final Object resource = getResource(mbean);

// Register the MBean with the repository.
// Returns the resource context that was used.
// The returned context does nothing for regular MBeans.
// For ClassLoader MBeans the context makes it possible to register these
// objects with the appropriate framework artifacts, such as
// the CLR, from within the repository lock.
// In case of success, we also need to call context.done() at the
// end of this method.
//
context = registerWithRepository(resource, mbean, logicalName);


registerFailed = false;
registered = true;

} finally {
try {
postRegister(logicalName, mbean, registered, registerFailed);
} finally {
if (registered && context!=null) context.done();
}
}
return new ObjectInstance(logicalName, classname);
}

registerDynamicMBean方法最终调用registerWithRepository方法将DynamicMBean对象注册到Repository里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private ResourceContext registerWithRepository(
final Object resource,
final DynamicMBean object,
final ObjectName logicalName)
throws InstanceAlreadyExistsException,
MBeanRegistrationException {

// Creates a registration context, if needed.
//
final ResourceContext context =
makeResourceContextFor(resource, logicalName);


repository.addMBean(object, logicalName, context);
// May throw InstanceAlreadyExistsException

// ---------------------
// Send create event
// ---------------------
if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
MBEANSERVER_LOGGER.logp(Level.FINER,
DefaultMBeanServerInterceptor.class.getName(),
"addObject", "Send create notification of object " +
logicalName.getCanonicalName());
}

sendNotification(
MBeanServerNotification.REGISTRATION_NOTIFICATION,
logicalName);

return context;
}

前面已经讲过在创建DefaultMBeanServerInterceptor实例时,会实例化一个repository对象并装载到成员
注册bean会调用repository的addMBean方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public void addMBean(final DynamicMBean object, ObjectName name,
final RegistrationContext context)
throws InstanceAlreadyExistsException {

if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
MBEANSERVER_LOGGER.logp(Level.FINER, Repository.class.getName(),
"addMBean", "name = " + name);
}

// Extract the domain name.
String dom = name.getDomain().intern();
boolean to_default_domain = false;

// Set domain to default if domain is empty and not already set
if (dom.length() == 0)
name = Util.newObjectName(domain + name.toString());

// Do we have default domain ?
if (dom == domain) { // ES: OK (dom & domain are interned)
to_default_domain = true;
dom = domain;
} else {
to_default_domain = false;
}

// Validate name for an object
if (name.isPattern()) {
throw new RuntimeOperationsException(
new IllegalArgumentException("Repository: cannot add mbean for " +
"pattern name " + name.toString()));
}

lock.writeLock().lock();
try {
// Domain cannot be JMImplementation if entry does not exist
if ( !to_default_domain &&
dom.equals("JMImplementation") &&
domainTb.containsKey("JMImplementation")) {
throw new RuntimeOperationsException(
new IllegalArgumentException(
"Repository: domain name cannot be JMImplementation"));
}

// If domain does not already exist, add it to the hash table
final Map<String,NamedObject> moiTb = domainTb.get(dom);
if (moiTb == null) {
addNewDomMoi(object, dom, name, context);
return;
} else {
// Add instance if not already present
String cstr = name.getCanonicalKeyPropertyListString();
NamedObject elmt= moiTb.get(cstr);
if (elmt != null) {
throw new InstanceAlreadyExistsException(name.toString());
} else {
nbElements++;
addMoiToTb(object,name,cstr,moiTb,context);
}
}

} finally {
lock.writeLock().unlock();
}
}
private void addMoiToTb(final DynamicMBean object,
final ObjectName name,
final String key,
final Map<String,NamedObject> moiTb,
final RegistrationContext context) {
registering(context);
moiTb.put(key,new NamedObject(name, object));
}

domainTb成员就是前面说过的repository中存储MBean的Map,类型是Map<String,Map<String,NamedObject>>
add方法里会通过writelock进行同步
如果获取到的moiTb,代表该MBean的域名尚未加入domainTb,会将域名加入map并将MBean也加入

JmxMBeanServer

JmxMBeanServer
不提供直接返回MBean对象的方法
但提供了很多行为如 setAttributes,getAttribute,invoke等方法,根据ObjectName参数进行MBean识别
调用成员的DefaultMBeanServerInterceptor实例去执行对应的修改属性,获取属性值,执行方法等行为
DefaultMBeanServerInterceptor最终是获取repository中的MBean去执行对应行为
通过getObjectInstance可以获得该MBean的类名和域名信息的封装对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public ObjectInstance getObjectInstance(ObjectName name)
throws InstanceNotFoundException {

return mbsInterceptor.getObjectInstance(cloneObjectName(name));
}
public Object getAttribute(ObjectName name, String attribute)
throws MBeanException, AttributeNotFoundException,
InstanceNotFoundException, ReflectionException {

return mbsInterceptor.getAttribute(cloneObjectName(name), attribute);
}
public void setAttribute(ObjectName name, Attribute attribute)
throws InstanceNotFoundException, AttributeNotFoundException,
InvalidAttributeValueException, MBeanException,
ReflectionException {

mbsInterceptor.setAttribute(cloneObjectName(name),
cloneAttribute(attribute));
}
public Object invoke(ObjectName name, String operationName,
Object params[], String signature[])
throws InstanceNotFoundException, MBeanException,
ReflectionException {
return mbsInterceptor.invoke(cloneObjectName(name), operationName,
params, signature);
}

JMXConnectorServerFactory.newJMXConnectorServer

该方法创建一个JMXConnectorServer,将MBeanServer和JMXServiceURL传递过去
最终url、MbeanServer等对象会被聚合到JMXConnectorServer,作为成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public static JMXConnectorServer
newJMXConnectorServer(JMXServiceURL serviceURL,
Map<String,?> environment,
MBeanServer mbeanServer)
throws IOException {
Map<String, Object> envcopy;
if (environment == null)
envcopy = new HashMap<String, Object>();
else {
EnvHelp.checkAttributes(environment);
envcopy = new HashMap<String, Object>(environment);
}

final Class<JMXConnectorServerProvider> targetInterface =
JMXConnectorServerProvider.class;
final ClassLoader loader =
JMXConnectorFactory.resolveClassLoader(envcopy);
final String protocol = serviceURL.getProtocol();
final String providerClassName = "ServerProvider";

JMXConnectorServerProvider provider =
JMXConnectorFactory.getProvider(serviceURL,
envcopy,
providerClassName,
targetInterface,
loader);

IOException exception = null;
if (provider == null) {
// Loader is null when context class loader is set to null
// and no loader has been provided in map.
// com.sun.jmx.remote.util.Service class extracted from j2se
// provider search algorithm doesn't handle well null classloader.
if (loader != null) {
try {
JMXConnectorServer connection =
getConnectorServerAsService(loader,
serviceURL,
envcopy,
mbeanServer);
if (connection != null)
return connection;
} catch (JMXProviderException e) {
throw e;
} catch (IOException e) {
exception = e;
}
}
provider =
JMXConnectorFactory.getProvider(
protocol,
PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
JMXConnectorFactory.class.getClassLoader(),
providerClassName,
targetInterface);
}

if (provider == null) {
MalformedURLException e =
new MalformedURLException("Unsupported protocol: " + protocol);
if (exception == null) {
throw e;
} else {
throw EnvHelp.initCause(e, exception);
}
}

envcopy = Collections.unmodifiableMap(envcopy);

return provider.newJMXConnectorServer(serviceURL,
envcopy,
mbeanServer);
}

JMXConnectorServerFactory的newJMXConnectorServer最终是返回由provider生产的JMXConnectorServer
provider是接口,在jdk里对于该方法有两个实现类,一个是返回基于rmi协议,另一个是返回基于iiop协议的

1
2
3
4
5
6
7
8
9
10
11
//ServiceProvider for rmi
public JMXConnectorServer newJMXConnectorServer(JMXServiceURL serviceURL,
Map<String,?> environment,
MBeanServer mbeanServer)
throws IOException {
if (!serviceURL.getProtocol().equals("rmi")) {
throw new MalformedURLException("Protocol not rmi: " +
serviceURL.getProtocol());
}
return new RMIConnectorServer(serviceURL, environment, mbeanServer);
}

需要留意的是,在serviceProvider中调用生成RMIConnectorServer时,最终会调用传递(RMIServerImpl) null为形参参数的构造函数构造
也就是生成的RMIConnectorServer中是没有指定服务端对象的
RMIServerImpl会在JMXConnectorServer调用start方法后结合前面聚合到JMXConnectorServer中的rmi的url去实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
   public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment,
MBeanServer mbeanServer)
throws IOException {
this(url, environment, (RMIServerImpl) null, mbeanServer);
}
public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment,
RMIServerImpl rmiServerImpl,
MBeanServer mbeanServer)
throws IOException {
super(mbeanServer);

if (url == null) throw new
IllegalArgumentException("Null JMXServiceURL");
if (rmiServerImpl == null) {
final String prt = url.getProtocol();
if (prt == null || !(prt.equals("rmi") || prt.equals("iiop"))) {
final String msg = "Invalid protocol type: " + prt;
throw new MalformedURLException(msg);
}
final String urlPath = url.getURLPath();
if (!urlPath.equals("")
&& !urlPath.equals("/")
&& !urlPath.startsWith("/jndi/")) {
final String msg = "URL path must be empty or start with " +
"/jndi/";
throw new MalformedURLException(msg);
}
}

if (environment == null)
this.attributes = Collections.emptyMap();
else {
EnvHelp.checkAttributes(environment);
this.attributes = Collections.unmodifiableMap(environment);
}

this.address = url;
this.rmiServerImpl = rmiServerImpl;
}

jmxConnectorServer.start()

启动JMX链接服务器,该过程会完成rmiServerImpl的实例化(开启监听rmi服务端口)
所以如果前面没有调用LocateRegistry.createRegistry(rmiPort)对端口进行注册,会导致报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
public synchronized void start() throws IOException {
final boolean tracing = logger.traceOn();

if (state == STARTED) {
if (tracing) logger.trace("start", "already started");
return;
} else if (state == STOPPED) {
if (tracing) logger.trace("start", "already stopped");
throw new IOException("The server has been stopped.");
}

if (getMBeanServer() == null)
throw new IllegalStateException("This connector server is not " +
"attached to an MBean server");

// Check the internal access file property to see
// if an MBeanServerForwarder is to be provided
//
if (attributes != null) {
// Check if access file property is specified
//
String accessFile =
(String) attributes.get("jmx.remote.x.access.file");
if (accessFile != null) {
// Access file property specified, create an instance
// of the MBeanServerFileAccessController class
//
MBeanServerForwarder mbsf;
try {
mbsf = new MBeanServerFileAccessController(accessFile);
} catch (IOException e) {
throw EnvHelp.initCause(
new IllegalArgumentException(e.getMessage()), e);
}
// Set the MBeanServerForwarder
//
setMBeanServerForwarder(mbsf);
}
}

try {
if (tracing) logger.trace("start", "setting default class loader");
defaultClassLoader = EnvHelp.resolveServerClassLoader(
attributes, getMBeanServer());
} catch (InstanceNotFoundException infc) {
IllegalArgumentException x = new
IllegalArgumentException("ClassLoader not found: "+infc);
throw EnvHelp.initCause(x,infc);
}

if (tracing) logger.trace("start", "setting RMIServer object");
final RMIServerImpl rmiServer;

if (rmiServerImpl != null)
rmiServer = rmiServerImpl;
else
rmiServer = newServer();

rmiServer.setMBeanServer(getMBeanServer());
rmiServer.setDefaultClassLoader(defaultClassLoader);
rmiServer.setRMIConnectorServer(this);
rmiServer.export();

try {
if (tracing) logger.trace("start", "getting RMIServer object to export");
final RMIServer objref = objectToBind(rmiServer, attributes);

if (address != null && address.getURLPath().startsWith("/jndi/")) {
final String jndiUrl = address.getURLPath().substring(6);

if (tracing)
logger.trace("start", "Using external directory: " + jndiUrl);

String stringBoolean = (String) attributes.get(JNDI_REBIND_ATTRIBUTE);
final boolean rebind = EnvHelp.computeBooleanFromString( stringBoolean );

if (tracing)
logger.trace("start", JNDI_REBIND_ATTRIBUTE + "=" + rebind);

try {
if (tracing) logger.trace("start", "binding to " + jndiUrl);

final Hashtable<?, ?> usemap = EnvHelp.mapToHashtable(attributes);

bind(jndiUrl, usemap, objref, rebind);

boundJndiUrl = jndiUrl;
} catch (NamingException e) {
// fit e in the nested exception if we are on 1.4
throw newIOException("Cannot bind to URL ["+jndiUrl+"]: "
+ e, e);
}
} else {
// if jndiURL is null, we must encode the stub into the URL.
if (tracing) logger.trace("start", "Encoding URL");

encodeStubInAddress(objref, attributes);

if (tracing) logger.trace("start", "Encoded URL: " + this.address);
}
} catch (Exception e) {
try {
rmiServer.close();
} catch (Exception x) {
// OK: we are already throwing another exception
}
if (e instanceof RuntimeException)
throw (RuntimeException) e;
else if (e instanceof IOException)
throw (IOException) e;
else
throw newIOException("Got unexpected exception while " +
"starting the connector server: "
+ e, e);
}

rmiServerImpl = rmiServer;

synchronized(openedServers) {
openedServers.add(this);
}

state = STARTED;

if (tracing) {
logger.trace("start", "Connector Server Address = " + address);
logger.trace("start", "started.");
}
}

前面讲过rmiServerImpl成员是空的,所以这时会调用newServer方法进行创建

1
2
3
4
5
6
7
8
9
10
11
12
RMIServerImpl newServer() throws IOException {
final boolean iiop = isIiopURL(address,true);
final int port;
if (address == null)
port = 0;
else
port = address.getPort();
if (iiop)
return newIIOPServer(attributes);
else
return newJRMPServer(attributes, port);
}

接下来会调用objectToBind
通过toStub方法创建当前的RMIServer对象的远程代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static RMIServer objectToBind(
RMIServerImpl rmiServer, Map<String, ?> env)
throws IOException {
return RMIConnector.
connectStub((RMIServer)rmiServer.toStub(),env);
}
public Remote toStub() throws IOException {
return RemoteObject.toStub(this);
}
public static Remote toStub(Remote obj) throws NoSuchObjectException {
if (obj instanceof RemoteStub ||
(obj != null &&
Proxy.isProxyClass(obj.getClass()) &&
Proxy.getInvocationHandler(obj) instanceof
RemoteObjectInvocationHandler))
{
return obj;
} else {
return sun.rmi.transport.ObjectTable.getStub(obj);
}
}

然后调用bind方法将RMIServer的远程代理对象注册到注册中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void bind(String jndiUrl, Hashtable<?, ?> attributes,
RMIServer rmiServer, boolean rebind)
throws NamingException, MalformedURLException {
// if jndiURL is not null, we nust bind the stub to a
// directory.
InitialContext ctx =
new InitialContext(attributes);

if (rebind)
ctx.rebind(jndiUrl, rmiServer);
else
ctx.bind(jndiUrl, rmiServer);
ctx.close();
}

至此,完成了JmxConnectorServer的启动,程序可以通过jmx规范,访问MBeanServer与其上的MBean
关于remote和rmi的细节可以参考本人另一篇博客——java-rmi源码解析java-rmi源码解析

更多精彩内容

请移步

个人主页: yangyitao.top