下面详细讲解如何修改JVM源码解决RMI的有状态化问题。

从JVM源码可以看到,client通过控制链得到对象ID后,走数据链发送到RMI Server,Server的查找过程如下:

target = ObjectTable.getTarget(newObjectEndpoint(id, transport));
那么getTarget函数执行了啥?
/**
* Returns the target associated with the object id.
*/
staticTarget getTarget(ObjectEndpoint oe) {
synchronized(tableLock) {
returnobjTable.get(oe);
}
}
其中objTable是
publicstaticfinalMapobjTable=
newHashMap();
--------
而ObjectEndpoint的equals函数定义如下:
publicbooleanequals(Object obj) {
if(objinstanceofObjectEndpoint) {
ObjectEndpoint oe = (ObjectEndpoint) obj;
returnid.equals(oe.id) &&transport== oe.transport;//行4
}else{
returnfalse;
}
}

这里重点关注 行4的内容。先看前半部分

id.equals(oe.id)代码为
publicbooleanequals(Object obj) {
if(objinstanceofObjID) {
ObjID id = (ObjID) obj;
returnobjNum== id.objNum&&space.equals(id.space);
}else{
returnfalse;
}
}
而space.equals(id.space)的代码为
publicbooleanequals(Object obj) {
if(objinstanceofUID) {
UID uid = (UID) obj;
return(unique== uid.unique&&
count== uid.count&&
time== uid.time);
}else{
returnfalse;
}
}

所以结论就是:id.equals(oe.id)只需要相关的4个值(objNumuniquecounttime)相等就行了。

----------------------------------------

再看transport== oe.transport;//行4
先看这2行
Transport transport = id.equals(dgcID) ?null:this;//dgcID代表【0:0:0 2】
target = ObjectTable.getTarget(newObjectEndpoint(id, transport));
对于业务来说,判断条件结果为false,所以结果为this.
然后这里根本不用考虑。只要保证对象绑定的port一致就可以了。
------------------------所以问题就很简单了,只要关注4个值(objNumuniquecounttime)就行了。
而且网络中传递的也是这4个ID.
下面看看4个ID生成的规则。
/**
* Construct a new live reference for a server object in the local
* address space.
*/
publicLiveRef(intport) {
this((newObjID()), port);
}
这里是newObjID(),所以需要去看看newObjID())生成的规则。
/**
* Generates a unique object identifier.
*
*
If the system propertyjava.rmi.server.randomIDs
* is defined to equal the string"true"(case insensitive),
* then this constructor will use a cryptographically
* strong random number generator to choose the object number of the
* returnedObjID.
*/
publicObjID() {
/*
* If generating random object numbers, create a new UID to
* ensure uniqueness; otherwise, use a shared UID because
* sequential object numbers already ensure uniqueness.
*/
if(useRandomIDs()) {
space=newUID();
objNum=secureRandom.nextLong();
}else{
space=mySpace;
objNum=nextObjNum.getAndIncrement();
}
}

可见这里根据是否使用随机ID来生成ID.useRandomIDs的函数定义如下:

privatestaticbooleanuseRandomIDs() {
String value = AccessController.doPrivileged(
newGetPropertyAction("java.rmi.server.randomIDs"));
returnvalue ==null?true: Boolean.parseBoolean(value);
}
然后我添加了调试信息
if(useRandomIDs()) {
System.out.println("use random id yes");
space=newUID();
objNum=secureRandom.nextLong();
}else{
System.out.println("use random id no");
space=mySpace;
objNum=nextObjNum.getAndIncrement();
}
System.out.println("id---"+space+" "+objNum);
}

的打印结果为:

java源码 WMS JAVA源码修改_System

可见,默认情况下,虚拟机采用了随机ID.

--------------------------------------那如果不采用随机规则呢?

-Djava.rmi.server.randomIDs=false
测试2个对象的生成规则为
use random id noid----445c7b23:14cb0a97861:-8000 0
use random id noid----445c7b23:14cb0a97861:-8000 1
可以看到objNum是按照规则生成的,0,1,2,3,4,但是space还是没有规律。
但是上面2个对象的space是一样的,因为
space=mySpace;而mySpace是private static final UID mySpace = new UID();

是一个全局静态final对象。

所以我们要保证new UID()的时候是一致的。

--------------------------------------------------

然后发现了一个奇怪的现象

就是当

-Djava.rmi.server.randomIDs=false时

java源码 WMS JAVA源码修改_java 编写无状态代码_02

感觉事情貌似突然变简单了。

难道-Djava.rmi.server.randomIDs=false就可以解决问题了?

----------------------------

想了半天,终于找到最终解决方案了,只需要修改3个地方

修改1:[java.rmi.server.ObjID]
publicbooleanequals(Object obj) {
if(objinstanceofObjID) {
ObjID id = (ObjID) obj;
returnobjNum== id.objNum&&space.equals(id.space);
}else{
returnfalse;
}
}
修改成
public staticStringnewRmi= System.getProperty("java.rmi.server.randomIDs");
public booleanequals(Object obj) {
if(objinstanceofObjID) {
ObjID id = (ObjID) obj;
String localSpace =space.toString();
String objSpace = id.space.toString();
if(null!=newRmi&&newRmi.equals("false")) {
if(localSpace.startsWith("0:0:0") || objSpace.startsWith("0:0:0")) {
return objNum== id.objNum&&space.equals(id.space);
}else{
return objNum== id.objNum;
}
}else{
return objNum== id.objNum&&space.equals(id.space);
}
}else{
return false;
}
}
---
修改2:加上运行参数
-Djava.rmi.server.randomIDs=false
修改3:为了防止对象被回收,修改UnicastRemoteObject的
[java.rmi.server.UnicastRemoteObject]
private staticRemote exportObject(Remote obj, UnicastServerRef sref)
throwsRemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if(objinstanceofUnicastRemoteObject) {
((UnicastRemoteObject) obj).ref= sref;
}
returnsref.exportObject(obj,null,false);
}
修改为
public staticStringnewRmi= System.getProperty("java.rmi.server.randomIDs");
private staticRemote exportObject(Remote obj, UnicastServerRef sref)
throwsRemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if(objinstanceofUnicastRemoteObject) {
((UnicastRemoteObject) obj).ref= sref;
}
returnsref.exportObject(obj,null,
( null!=newRmi &&newRmi.equals(“false”) )?true:false);
}
修改4:
为了防止不存在前台reaper线程存在,导致VM退出(不依赖于任何其它业务),需要自启动一个前台线程
方法如下:
修改UnicastRemoteObject类[java.rmi.server.UnicastRemoteObject]
public staticStringnewRmi= System.getProperty("java.rmi.server.randomIDs");
public staticThreadselfCreateThead=null;
static{
if(null!=newRmi &&newRmi.equals(“false”) &&null==selfCreateThead) {
selfCreateThead=newThread(newRunnable() {
@Override
public voidrun() {
//TODOAuto-generated method stub
// must always cycle
while(true) {
try{
Thread.currentThread().sleep(1 * 60 * 1000);// 1
// minutes
}catch(InterruptedException e) {
//TODOAuto-generated catch block
e.printStackTrace();
}
}
}
});
selfCreateThead.start();
}
}

修改5:[暂时丢弃]

[sun.rmi.transport.ObjectEndpoint]
{注意:这个类非java官方开源,所以一定要将所在linux机器的class文件反编译后的java文件
与
RPC技术文件夹下面的zip文件里的java文件
做一个仔细的比较,看是否有差异...}
publicbooleanequals(Object obj) {
if(objinstanceofObjectEndpoint) {
ObjectEndpoint oe = (ObjectEndpoint) obj;
returnid.equals(oe.id) &&transport== oe.transport;
}else{
returnfalse;
}
}
/**
* Returns the hash code value for this object endpoint.
*/
publicinthashCode() {
returnid.hashCode() ^ (transport!=null?transport.hashCode() : 0);
}
修改为
publicstaticStringnewRmi= System.getProperty("java.rmi.server.randomIDs");
publicbooleanequals(Object obj) {
if(objinstanceofObjectEndpoint) {
ObjectEndpoint oe = (ObjectEndpoint) obj;
if(null!=newRmi&&newRmi.equals("false")) {
returnid.equals(oe.id);
}else{
returnid.equals(oe.id) &&transport== oe.transport;
}
}else{
returnfalse;
}
}
/**
* Returns the hash code value for this object endpoint.
*/
publicinthashCode() {
if(null!=newRmi&&newRmi.equals("false")) {
returnid.hashCode();
}else{
returnid.hashCode() ^ (transport!=null?transport.hashCode() : 0);
}
}

比较复杂的是对象的回收机制,此方案是否实际可行,需要经过测试的验证。

目前已经在2个产品中上线,运维人员表示未发现问题。