java 编写无状态代码,一种真正实现RMI无状态化的方法续:JVM源码修改步骤

下面详细讲解如何修改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);

}

的打印结果为:

5f707aae31a6a592ba8551410ce33098.png

可见,默认情况下,虚拟机采用了随机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时

140158b95b589320af09ccf0cdc1948e.png

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

难道-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个产品中上线,运维人员表示未发现问题。

觉得好的请点个赞,做个技术人员也是不容易滴 :)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值