文档章节

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

强子1985
 强子1985
发布于 2015/08/23 10:55
字数 1364
阅读 141
收藏 1

项目背景请参考我的上一篇文章: 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

© 著作权归作者所有

共有 人打赏支持
强子1985

强子1985

粉丝 864
博文 991
码字总数 673290
作品 8
南京
架构师
一种基于HAProxy实现RMI负载均衡的方法

本方法提出了一种基于HAProxy实现RMI控制链和数据链都完全实现负载均衡的方法, 使得RMI Client通过HAProxy拿到任何一台服务器的RMI对象信息后,通过HAProxy发送此对象信息到任何另外一台服务...

强子哥哥
2015/08/23
0
0
用RMI实现基于Java的分布式计算(1)

向您介绍使用RMI实现Java的分布式计算。由于Java具有跨平台、代码可移植性、安全高效等广泛而强大的功能,因而在开发网络Java分布式应用的时候,可以用它自身的机制实现分布式计算。 概述 随...

山哥
2012/02/24
0
0
Java Serializable(序列化)的理解

1、序列化是干什么的? 简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object st...

LCZ777
2014/08/25
0
0
你不知道的 Electron (一):神奇的 remote 模块

转自IMWeb社区,作者:laynechen,原文链接 在上一篇 Electron 进程通信 中,介绍了 Electron 中的两种进程通信方式,分别为: 使用 和 两个模块 使用 remote 模块 相比于使用两个 IPC 模块,...

IMWeb团队
09/18
0
0
集合类学习之Arraylist 源码分析

1.概述 ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。 ...

Kerry_Han
2014/08/01
0
0

没有更多内容

加载失败,请刷新页面

加载更多

linux使用ntfs-3g操作ntfs格式硬盘

Linux内核目前只支持对微软NTFS文件系统的读取。 NTFS-3G 是微软 NTFS 文件系统的一个开源实现,同时支持读和写。NTFS-3G 开发者使用 FUSE 文件系统来辅助开发,同时对可移植性有益。 安装 ...

linuxprobe16
今天
1
0
kubeadm部署kubernetes集群

一、环境要求 这里使用RHEL7.5 master、etcd:192.168.10.101,主机名:master node1:192.168.10.103,主机名:node1 node2:192.168.10.104,主机名:node2 所有机子能基于主机名通信,编辑...

人在艹木中
今天
7
0
Shell特殊符号总结以及cut,sort,wc,uniq,tee,tr,split命令

特殊符号总结一 * 任意个任意字符 ? 任意一个字符 # 注释字符 \ 脱义字符 | 管道符 # #号后的备注被忽略[root@centos01 ~]# ls a.txt # 备注 a.txt[root@centos01 ~]# a=1[root@centos01...

野雪球
今天
3
0
OSChina 周二乱弹 —— 程序员圣衣

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @达尔文:分享Skeeter Davis的单曲《The End of the World》 《The End of the World》- Skeeter Davis 手机党少年们想听歌,请使劲儿戳(这里...

小小编辑
今天
18
0
[ python import module ] 导入模块

import moudle_name ----> import module_name.py ---> import module_name.py文件路径 -----> sys.path (这里进行查找文件) # from app.web import Personimport app.web.Person as Pe......

_______-
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部