一种真正实现RMI无状态化的方法续:JVM源码修改步骤
一种真正实现RMI无状态化的方法续:JVM源码修改步骤
强子哥哥 发表于2年前
一种真正实现RMI无状态化的方法续:JVM源码修改步骤
  • 发表于 2年前
  • 阅读 117
  • 收藏 1
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

项目背景请参考我的上一篇文章: http://my.oschina.net/qiangzigege/blog/495910

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

 

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

target = ObjectTable.getTarget(new ObjectEndpoint(id, transport));    

 

那么getTarget函数执行了啥?

/**

     * Returns the target associated with the object id.

     */

    static Target getTarget(ObjectEndpoint oe) {

synchronized (tableLock) {

    return objTable.get(oe);

}

    }

其中objTable

  public static final Map<ObjectEndpoint,Target> objTable =

new HashMap<ObjectEndpoint,Target>();

--------

ObjectEndpointequals函数定义如下:

 

public boolean equals(Object obj) {

if (obj instanceof ObjectEndpoint) {

    ObjectEndpoint oe = (ObjectEndpoint) obj;

 return id.equals(oe.id) && transport == oe.transport;//4

else {

    return false;

}

    }

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

id.equals(oe.id)代码为

 

 public boolean equals(Object obj) {

if (obj instanceof ObjID) {

    ObjID id = (ObjID) obj;

    return objNum == id.objNum && space.equals(id.space);

else {

    return false;

}

    }

space.equals(id.space)的代码为

 

public boolean equals(Object obj) {

if (obj instanceof UID) {

    UID uid = (UID) obj;

    return (unique == uid.unique &&

    count == uid.count &&

    time == uid.time);

else {

    return false;

}

    }

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

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

再看 transport == oe.transport;//4

先看这2

Transport transport = id.equals(dgcID) ? null : this;//dgcID代表【0:0:0 2

target = ObjectTable.getTarget(new ObjectEndpoint(id, transport));

对于业务来说,判断条件结果为false,所以结果为this.

然后这里根本不用考虑。只要保证对象绑定的port一致就可以了。

------------------------所以问题就很简单了,只要关注4个值(objNum unique count time)就行了。

而且网络中传递的也是这4ID.

 

下面看看4ID生成的规则。

 

 

 /**

     * Construct a new live reference for a server object in the local

     * address space.

     */

    public LiveRef(int port) {

this((new ObjID()), port);

    }

这里是new ObjID(),所以需要去看看new ObjID())生成的规则。

 

 

/**

     * Generates a unique object identifier.

     *

     * <p>If the system property <code>java.rmi.server.randomIDs</code>

     * is defined to equal the string <code>"true"</code> (case insensitive),

     * then this constructor will use a cryptographically

     * strong random number generator to choose the object number of the

     * returned <code>ObjID</code>.

     */

    public ObjID() {

/*

 * 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 = new UID();

    objNum = secureRandom.nextLong();

else {

    space = mySpace;

    objNum = nextObjNum.getAndIncrement();

}

    }

 

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

 

private static boolean useRandomIDs() {

String value = AccessController.doPrivileged(

    new GetPropertyAction("java.rmi.server.randomIDs"));

return value == null ? true : Boolean.parseBoolean(value);

    }

然后我添加了调试信息

 

 if (useRandomIDs()) {

        System.out.println("use random id yes");

        space = new UID();

        objNum = secureRandom.nextLong();

    } else {

        System.out.println("use random id no");

        space = mySpace;

        objNum = nextObjNum.getAndIncrement();

    }

    System.out.println("id---"space +" "+objNum);

    }

的打印结果为:

 

 

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

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

-Djava.rmi.server.randomIDs=false

测试2个对象的生成规则为

 

use random id no
id----445c7b23:14cb0a97861:-8000 0

use random id no
id----445c7b23:14cb0a97861:-8000 1

可以看到objNum是按照规则生成的,01,2,3,4, 但是space还是没有规律。

但是上面2个对象的space是一样的,因为

 space = mySpace; mySpaceprivate static final UID mySpace = new UID();

是一个全局静态final 对象。

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

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

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

就是当

-Djava.rmi.server.randomIDs=false

 

 

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

 

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

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

 

 

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

修改1[java.rmi.server.ObjID]

public boolean equals(Object obj) {

             

    if (obj instanceof ObjID) {

        ObjID id = (ObjID) obj;

        return objNum == id.objNum && space.equals(id.space);

    } else {

        return false;

    }

    }

修改成

public static String newRmi = System.getProperty("java.rmi.server.randomIDs");

 

        public boolean equals(Object obj) {

        if (obj instanceof ObjID) {

            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 static Remote exportObject(Remote obj, UnicastServerRef sref)

   throws RemoteException

    {

   // if obj extends UnicastRemoteObject, set its ref.

   if (obj instanceof UnicastRemoteObject) {

       ((UnicastRemoteObject) obj).ref = sref;

   }

   return sref.exportObject(obj, null, false);

}

修改为

 

public static String newRmi = System.getProperty("java.rmi.server.randomIDs");

 

private static Remote exportObject(Remote obj, UnicastServerRef sref)

   throws RemoteException

    {

   // if obj extends UnicastRemoteObject, set its ref.

   if (obj instanceof UnicastRemoteObject) {

       ((UnicastRemoteObject) obj).ref = sref;

   }

   

   return sref.exportObject(obj, null,

   ( null!= newRmi && newRmi.equals(“false”) )?true:false);

    }

 

 

 

 

修改4

为了防止不存在前台reaper线程存在,导致VM退出(不依赖于任何其它业务),需要自启动一个前台线程

方法如下:

修改UnicastRemoteObject[java.rmi.server.UnicastRemoteObject]

public static String newRmi = System.getProperty("java.rmi.server.randomIDs");

 

public static Thread selfCreateThead = null;

 

    static{

        if (null!= newRmi && newRmi.equals(“false”) && null == selfCreateThead) {

            selfCreateThead = new Thread(new Runnable() {

                @Override

                public void run() {

                    // TODO Auto-generated method stub

                    // must always cycle

                    while (true) {

                        try {

                            Thread.currentThread().sleep(1 * 60 * 1000);// 1

                                                                        // minutes

                        } catch (InterruptedException e) {

                            // TODO Auto-generated catch block

                            e.printStackTrace();

                        }

                    }

                }

            });

            selfCreateThead.start();

        }

    }

 

 

修改5[暂时丢弃]

[sun.rmi.transport.ObjectEndpoint]

{注意:这个类非java官方开源,所以一定要将所在linux机器的class文件反编译后的java文件

 RPC技术文件夹下面的zip文件里的java文件

做一个仔细的比较,看是否有差异...}

    public boolean equals(Object obj) {

   if (obj instanceof ObjectEndpoint) {

       ObjectEndpoint oe = (ObjectEndpoint) obj;

       return id.equals(oe.id) && transport == oe.transport;

   } else {

       return false;

   }

    }

 

    /**

     * Returns the hash code value for this object endpoint.

     */

    public int hashCode() {

   return id.hashCode() ^ (transport != null ? transport.hashCode() : 0);

    }

 

 

 

 

 

 

 

修改为

   public static String newRmi = System.getProperty("java.rmi.server.randomIDs");

 

    public boolean equals(Object obj) {

        if (obj instanceof ObjectEndpoint) {

            ObjectEndpoint oe = (ObjectEndpoint) obj;

            if (null != newRmi && newRmi.equals("false")) {

                return id.equals(oe.id);

            } else {

                return id.equals(oe.id) && transport == oe.transport;

            }

        } else {

            return false;

        }

    }

 

    /**

     * Returns the hash code value for this object endpoint.

     */

    public int hashCode() {

        if (null != newRmi && newRmi.equals("false")) {

            return id.hashCode();

        } else {

            return id.hashCode() ^ (transport != null ? transport.hashCode() : 0);

        }

    }

 

 

 

 

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

 

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

 

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

Email: 837500869@qq.com

标签: RMI JVM
共有 人打赏支持
强子哥哥
粉丝 836
博文 686
码字总数 712243
作品 8
×
强子哥哥
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: