文档章节

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

强子哥哥
 强子哥哥
发布于 2015/08/23 10:55
字数 1364
阅读 130
收藏 1
点赞 0
评论 0

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

© 著作权归作者所有

共有 人打赏支持
强子哥哥

强子哥哥

粉丝 856
博文 551
码字总数 647493
作品 8
南京
架构师
RMI:Java中的分布式计算框架

RMI全称是Remote Method Invocation-远程方法调用,Java RMI在JDK1.1中实现的,其威力就体现在它强大的开发分布式网络应用的能力上,是纯Java的网络分布式应用系统的核心解决方案之一。其实...

qq_39521554 ⋅ 05/15 ⋅ 0

《Spring技术内幕》学习笔记18——Spring使用Hessian实现远程调用

Spring目前提供了对RMI、 HttpInvoker、Hessian、Burlap及WebService等Remoting技术的集成。Spring屏蔽了这些实现技术的差异,用户只需开发简单的Java对象(Plain Old Java Objects,POJO)然后...

谜男amu ⋅ 05/16 ⋅ 0

ThreadLocal源码分析

阅读原文请访问我的博客 BrightLoong's Blog 一. 简介 提醒篇幅较大需耐心。 简介来自ThreadLocal类注释 ThreadLocal类提供了线程局部 (thread-local) 变量。这些变量与普通变量不同,每个线...

BrightLoong ⋅ 05/28 ⋅ 0

有一到五年开发经验的JAVA程序员需要掌握的知识与技能!

JAVA是一种平台,也是一种程序设计语言,如何学好程序设计不仅仅适用于JAVA,对C++等其他程序设计语言也一样管用。有编程高手认为,JAVA也好C也好没什么分别,拿来就用。为什么他们能达到如此...

java高级架构牛人 ⋅ 06/02 ⋅ 0

在Ubuntu16.04.4上安装jdk

一、安装步骤 1.下载jdk安装包 首先我们在oracle官网上下载jdk-8u161-linux-x64.tar.gz,当然也可以下载其他版本的。 2.创建java的安装目录,并且解压该安装包,这里没有固定的解压目录,很类...

oO维尼熊Oo ⋅ 05/03 ⋅ 0

如何计算Java对象所占内存的大小

摘要 本文以如何计算Java对象占用内存大小为切入点,在讨论计算Java对象占用堆内存大小的方法的基础上,详细讨论了Java对象头格式并结合JDK源码对对象头中的协议字段做了介绍,涉及内存模型、...

阿里云云栖社区 ⋅ 05/24 ⋅ 0

java开发中的常用的设计模式

设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代...

qq_38024548 ⋅ 05/28 ⋅ 0

Java虚拟机--一个类如何加载?

文末有彩蛋!!!!!! 类加载 对于虚拟机来说,一个对象的创建十分复杂,包含了很多步骤。首先,我们要从类加载说起。 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期...

贾博岩 ⋅ 04/04 ⋅ 0

作为一个java程序员这些技能你都知道吗?

一、Java特点 1、 面向对象 尽管受到其前辈的影响,但Java没被设计成兼容其他语言源代码的程序。这允许Java开发组自由地从零开始。这样做的一个结果是,Java语言可以更直接、更易用、更实际的...

java高级架构牛人 ⋅ 05/23 ⋅ 0

SWIG与JAVA 交互最全开发指南一

项目背景 最近开始研究做移动端项目,但是本人基本是做了五六年的c++的底层研发,对C++的研发可以说是驾轻就熟了,但是对于android还是属于刚入门阶段,虽然断断续续做移动端也做了一年,但是...

揽月凡尘 ⋅ 06/16 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

知乎Java数据结构

作者:匿名用户 链接:https://www.zhihu.com/question/35947829/answer/66113038 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 感觉知乎上嘲讽题主简...

颖伙虫 ⋅ 今天 ⋅ 0

Confluence 6 恢复一个站点有关使用站点导出为备份的说明

推荐使用生产备份策略。我们推荐你针对你的生产环境中使用的 Confluence 参考 Production Backup Strategy 页面中的内容进行备份和恢复(这个需要你备份你的数据库和 home 目录)。XML 导出备...

honeymose ⋅ 今天 ⋅ 0

JavaScript零基础入门——(九)JavaScript的函数

JavaScript零基础入门——(九)JavaScript的函数 欢迎回到我们的JavaScript零基础入门,上一节课我们了解了有关JS中数组的相关知识点,不知道大家有没有自己去敲一敲,消化一下?这一节课,...

JandenMa ⋅ 今天 ⋅ 0

火狐浏览器各版本下载及插件httprequest

各版本下载地址:http://ftp.mozilla.org/pub/mozilla.org//firefox/releases/ httprequest插件截至57版本可用

xiaoge2016 ⋅ 今天 ⋅ 0

Docker系列教程28-实战:使用Docker Compose运行ELK

原文:http://www.itmuch.com/docker/28-docker-compose-in-action-elk/,转载请说明出处。 ElasticSearch【存储】 Logtash【日志聚合器】 Kibana【界面】 答案: version: '2'services: ...

周立_ITMuch ⋅ 今天 ⋅ 0

使用快嘉sdkg极速搭建接口模拟系统

在具体项目研发过程中,一旦前后端双方约定好接口,前端和app同事就会希望后台同事可以尽快提供可供对接的接口方便调试,而对后台同事来说定好接口还仅是个开始、设计流程,实现业务逻辑,编...

fastjrun ⋅ 今天 ⋅ 0

PXE/KickStart 无人值守安装

导言 作为中小公司的运维,经常会遇到一些机械式的重复工作,例如:有时公司同时上线几十甚至上百台服务器,而且需要我们在短时间内完成系统安装。 常规的办法有什么? 光盘安装系统 ===> 一...

kangvcar ⋅ 昨天 ⋅ 0

使用Puppeteer撸一个爬虫

Puppeteer是什么 puppeteer是谷歌chrome团队官方开发的一个无界面(Headless)chrome工具。Chrome Headless将成为web应用自动化测试的行业标杆。所以我们很有必要来了解一下它。所谓的无头浏...

小草先森 ⋅ 昨天 ⋅ 0

Java Done Right

* 表示难度较大或理论性较强。 ** 表示难度更大或理论性更强。 【Java语言本身】 基础语法,面向对象,顺序编程,并发编程,网络编程,泛型,注解,lambda(Java8),module(Java9),var(...

风华神使 ⋅ 昨天 ⋅ 0

Linux系统日志

linux 系统日志 /var/log/messages /etc/logrotate.conf 日志切割配置文件 https://my.oschina.net/u/2000675/blog/908189 logrotate 使用详解 dmesg 命令 /var/log/dmesg 日志 last命令,调......

Linux学习笔记 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部