`
huxiaojun_198213
  • 浏览: 97579 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

RMI运行时说明

    博客分类:
  • RMI
阅读更多
RMI运行时环境在客户端和服务端都扮演了重要的角色.在这种架构中,stub达到了三种目的:
1.它是序列化的,通过网络可以从服务端向客户端发送.也可以包含数据使其可以稳定地向服务端发送消息.
2.它是服务端的代理,客户端可以把stub当作是服务器.
3.它可以池化socket.每次方法调用的时候,stub就向RMI运行时请求一个特别服务的连接.这使得RMI可以在多个请求之间重用sockets(即可以共享socket).

在多个请求之间重用socket,可能会导致下面的问题:

如果一个socket被多个客户端的stubs重用,它们每一个都向不同的服务器发送了消息,那么这些消息之间应该有某种意义上的区别.那么应该如何来区分呢?

在RMI中,这种区分是通过使用java.rmi.server包下的ObjID类来进行的.

ObjID在javadoc中是这样定义的:

ObjID被唯一用来标识远程对象.每一个标识符都包含一个对象数(类型为long)和一个地址空间标识符(类型为UID,对于特定主机是唯一的).

对象标识符是在远程对象被导出时分配的.如果java.rmi.server.randomIDs属性设置为true的话,通过无参构造函数创建的ObjID对象的64位对象树将包含一个加密的强随机数.

因此,ObjID的实例可以唯一地标识服务器JVM中的特定服务.同时,ObjID类实现了序列化接口,因此,在每个远程方法调用时,其实例都可以被序列化.

在服务端,RMI运行时使用ObjID的反序列化来找出远程方法调用将要调用的skeleton.

RMI是怎样解决引导问题的?

大部分服务器在过去的使用中都是通过随机分配ObjID的实例来处理的.在其实例中有三种保留的标识符:ACTIVATOR_ID(),DGC_ID和REGISTRY_ID.

这三种IDs分别对应RMI提供的三种服务:激活构架,分布式垃圾收集和RMI注册表.

为了能够完全解决引导问题,RMI注册表需要预知的一个唯一标识符.

当一个客户端第一次试图连接RMI注册表的时候,除非它知道与服务器相关的对象标识符,否则此客户端就不能向服务器端发送消息.

也就是说,因为服务器端的RMI运行时需要一个ObjID的实例来决定使用哪个合适的服务器来响应客户端的请求,客户端为了能连接注册表,它必须要知道运行在服务器JVM中注册表的ObjID.

为了解决这个问题,RMI的设计者们定义了ObjID的保留实例-如果一个注册表运行在一个虚拟机中,它就使用REGISTRY_ID.否则,不允许任何服务器使用REGISTRY_ID.

这解决了RMI引导问题.Naming的静态方法接受一个主机和端口;构建stub时仅需的附加信息是stub连接服务器的Object ID.

由于注册表总是使用REGISTRY_ID,注册表的stub在连接注册表之前总是能在客户端被完全地构建.

这种策略也意味着,一个JVM只能导出一个注册表,这是因为不能在多个服务器间共享ObjID.

分布式垃圾收集

另一个固定的标识符是DGC_ID.这是内置于RMI中的关于分布式垃圾收集的对象标识符.

垃圾收集的基本想法是定义一系列的可到达对象,并丢弃那么不可达对象.可达对象是递归定义的:

1.每个活动线程当前都处在一个方法中或处在未知对象的实例中.那么线程所在的实例就是可到达的.

2.每个线程能够立即找到被方法级变量和实例字段引用的对象,这些对象也是可到达的.

3.从这些立即可访问的对象(此对象引用了其他对象),能够访问到其他可到达的对象.

诸如此类,一般来说,如果一个线程,从它当前位置开始,能够最终找到某个对象,那么此对象就是可达的.

在单个JVM中垃圾收集机制工作得很好.但在分布式系统中,stub问题将会出现.如果一个客户端拥有一个服务器端的引用 ,那么服务器应该是可到达的.

由于所有的stub真正拥有的是一个ObjId的实例,这意味着RMI运行时必须保持所有活动服务器的引用.为了垃圾回收,客户端的RMI运行时必须以某种方法让服务端运行时知道stub何时不再被使用.

最明显的方式是使用分布式引用计数.也就是,强制客户端stub发送两条额外的信息给服务器.

当stub被实例化发送一条消息,以便让服务器知道有一个活动的客户端(计数器加1).当stub释放的时候,再发送一条消息,以通知服务器客户端已经使用完了服务端(计数器减1).

在这种方案中,最脆弱的是第一步.下面的每一个问题都可能增加计数的困难度:

1.客户端垃圾收集算法不能保证立即回收stub.如果客户端的内存充裕,且正忙于处理具有高优先级的任务,那么垃圾收集暂时可能不会发生.在这段时期之内,客户端将隐含地强制服务端保持那些没有必要的资源.

2.客户端可能会崩溃(crash).假设一个客户端崩溃了的话,那么第二条消息将无法发送,这样就会导致服务端引用计数无法变为0,且RMI运行时将永远保持服务对象为活动状态.

3.可能会出现网络问题.即使客户端是好的,且能发送消息,但网络问题也可能出现.在这种情况下, RMI运行时决不将服务端引用减为0,这样就导致了服务端对象一直处于活动状态.


在这三个问题中,第一个问题是不可能解决的.Java语言规范明确的表明本地垃圾收集是不可依赖的.垃圾收集将来可能发生,但没有一种方式来强制它在某一时间段内发生.

在垃圾收集运行之前,没有一种方法知道一个stub变成了不可引用,因此任何一个分布式引用计数架构将不得不接受第一问题的现实.

第二和第三个问题可以通过将所有分布式引用作为临时引用来消除.其基本思想被称为租赁.基本算法如下:

1.客户端调用服务器并请求一段时间的租期.

2.服务端响应,并同意一段时间的租期(不一定与客户端请求的租期相同)

3.在这段时间之内,分布式引用计数将包含客户端(即增加计数1)

4.当租期期满的时候,如果客户端请求没有延长期限的话,则分布式应用计数将会自动减少(即减少计数1).

只要stub没有被垃圾回收的话,客户端就会自动尝试续期租赁.通过这种算法,可以灵巧地解决第二,第三个问题.

如果客户端崩溃了的话,即客户端就不再运行了,那么客户端就不能再续期租赁,结果就会导致服务器最终将其回收掉.

相似地,如果因网络问题阻止了客户端程序连接服务器,客户端也不会续期到租赁,因此服务端最终也会对其进行垃圾回收.

默认地租期为10分钟,可以通过java.rmi.dgc.leaseValue属性进行设置,时间单位为毫秒.

真实的分布式垃圾收集器

分布式垃圾收集器是一个RMI服务器程序,它实现java.rmi.dgc.DGC接口(此接口继承自java.rmi.Remote接口).此接口只包含两个方法声明:

public void clean(ObjID[] ids,long sequenceNum,VMID vmid,boolean strong)

public Lease dirty(ObjID[] ids,long sequenceNum,Lease lease)

clean()方法是由客户端在不需要服务端引用的时候,由它的运行时调用的.严格来讲,调用clean()方法是没有必要的-客户端运行时可能不续期租赁而达到相同的目的.

但是,租赁应该看作是服务器清理机制的最后一道防线(通过它可以在适当的位置减少网络和客户端失败造成的损害).

客户端运行时可以通过调用dirty()方法来获得一个租赁.不需要直接传递一个VMID(虚拟机ID,即一个JVM的唯一标识符)给dirty()方法,因Lease的实例已经传递了VMID(通过其构造函数可以看出).

注意:对于一个特定的JVM来说,它只能有一个分布式垃圾回收器,其对象标识符为DGC_ID.

Unreferecnced接口

分布式垃圾回收器负责维持租赁.在服务器端,当特定服务器的所有未解决租赁过期的时候,分布式垃圾回收器确保RMI运行时不再保留服务端引用.

因此服务端就可以被回收.在此处理过程中,如果服务端(远程对象)实现了Unreferenced接口,那么服务端在没有多个引用该对象的客户机时接收通知.Unreferenced接口只包含一个方法:

public void unreferenced()

当服务器需要立即释放资源而不是等待垃圾回收发生时,此接口非常重要.同时,它也是一个持久化代码的便利钩子-因为服务端知道不再有远程方法调用,所以它能将状态安全地存储到一个持久化介质(如,一个关系型数据库).

RMI日志

除了分布式垃圾回收之外,RMI运行时也包含广泛的日志,这些日志可以让你跟踪应用程序的行为.RMI中有三种不同的日志类型:标准日志,专门日志(包含5种类型),调试日志

标准日志

标准日志用于在服务端记录方法调用和异常信息.标准日志的使用是很容易的,可以通过设置java.rmi.server.logCalls系统属性(值为boolean类型)来启用或关闭.此属性可通过命令行或程序进行设置.如:

命令行:
Java -Djava.rmi.server.logCalls=true


程序:
System.getProperties().put("java.rmi.server.logCalls","true");


一旦你启用了日志,你可以配置日志的输出目的地.可以通使用java.rmi.server.RemoteServer类的静态方法进行设置:

public static PrintStream getLog()
用于获取当前的日志的流

public static void setLog(OutputStream out)
设置日志的输出流.

缺省情况下,日志系统使用System.err.也就是说,如果你没有设置标准日志输出目的地的话,它将会在System.err上显示.

专门日志
RMI有五种类型的专门日志用于记录运行时的特定方面.这些日志是transport log(传输日志),proxy log(代理日志),loader log(负载日志),DGC log(分布式垃圾回收日志)以及TCP log(tcp日志).

它们均为java.rmi.server.LogStream(此类从JDK1.3版本时已经被废弃)的实例.为了得到与日志相关的LogStream,可以调用其静态方法:public static LogStream log(String name).

一旦你有了LogStream的实例,就可以使用其 setOutputStream()来设置其输出目的地.如:
FileOutputStream transportLogFile = new FileOutputStream("d:/log/transportFlogFile");
LogStream.log("transport").setOutputStream(transportLogFile):


这些日志通过6个系统属性来操作,每一个属性都接受三个设置:silent(不记录信息),brief(记录少量信息),verbose(记录大量信息),可以使用s,b,v是上述三个值的简写形式.

此6个属性如下:(以下属性因为已被废弃,且属sun公司专有属性,具体含义可查看参考资料)

sun.rmi.server.dgcLevel

sun.rmi.server.logLevel

sun.rmi.loader.logLevel

sun.rmi.transport.logLevel

sun.rmi.transport.tcp.logLevel

sun.rmi.transport.proxy.logLevel

调试日志

同标准日志一样,可以通过设置系统属性sun.rmi.log.debug的boolean值来开启或禁用此功能.但不同于标准日志的是,调试日志总是将信息显示在System.err上.调试日志被用于激活框架的守护线程中.

基本RMI参数

java.rmi.server.randomIDS

其属性值为boolean型.当设置为true时,它强制RMI运行时为新导出的服务对象生成加密的安全对象标识.默认为false.

sun.rmi.server.exceptionTrace

其属性值为boolean型.当其设为true的时候(默认为false),所有的异常将会被打印到System.err上.反之,则不打印.

传输层参数

传输层是底层参数,它能够直接影响RMI底层的sokets的使用和TCP/IP的配置.有三个传输层参数:

sun.rmi.transport.connectionTimeout

用于设置在RMI关闭之前,socket的休眠时间.默认为15秒,在较慢的网络中,应该加大此值。

必须在客户端和服务器端同时设定此值,因为两端都试图重用同一个socket.如果服务端设为60秒的话,客户端设为15秒的话,那么实际上服务端设置的参数将会被客户端参数所否决。

sun.rmi.transport.tcp.readTimeout

设置底层socket读取超时时间,此参数的值实际上只能通过直接传递给socket的方式进行设值(通过socket的setSoTimeOut()).此参数的默认值为2个小时(7200,000毫秒)。

sun.rmi.transport.proxy.connectTimeout

用于设置在两个JVM建立连接时RMI等待的时间。默认为15秒。

注:上述参数的值单位均为毫秒

影响垃圾回收的参数

java.rmi.dgc.leaseValue
此参数只影响服务端,它被用来设置标准的租赁时间。时间单位为毫秒,默认为10分钟。

sun.rmi.dgc.client.gcInterval
此参数用于配置客户端运行时行为。即RMI检测stub是否活动的时间周期。当stub不再被引用的时候,客户端运行时就会发送一个clean()消息给服务端运行时。单位为毫秒,默认为1分钟。

sun.rmi.dgc.server.gcInterval
服务端对于分布式垃圾回收的刷新频率。此参数用于控制检查客户端clean()的动作频率,并试图决定是否调用unreferenced()方法。此值单位为毫秒,缺省为1分钟。

sun.rmi.dgc.checkInterval
用于指定RMI检查过期租赁的频率。此值单位为毫秒,缺省为5分钟。

sun.rmi.dgc.cleanInterval
此参数是客户端的重试参数。当客户端调用clean()时,如果操作失败(如,网络问题),此参数将设定重新调用clean()时,客户端应该等待的时间。此值单位为毫秒,缺省为3分钟。

参考资料

1.O’Reilly <<Java RMI>> Chapter 16.The RMI Runtime

2.Java RMI 规范


分享到:
评论

相关推荐

    java调用RMI小结

    有很多关于rmi的调用说明,在最后的运行时总是不太详细,通过多方查阅,写了一段代码,供大家参考,希望对大家有帮助。

    java rmi 简单易懂的实例

    java rmi 完整的简单例子 包含详细的文档说明

    RMI的简单完整例子

    RMI的一个完整demo,有文件,说明,可以直接运行看到效果 对于初学者很有用,呵呵,完全免费的啦

    RMI资料详解

    讲述 RMI 是什么,作用是什么,运行原理,主要用到的函数等。

    rmi-project:聊天RMI Java

    编译和运行项目的说明 克隆文件或下载.zip; 打开rmi-project / src目录; 使用cmd使用以下代码检查计算机的ip: ipconfig ; 在类StartClient.java和StartServer.java更改ip; 使用cmd或git bash,使用以下命令...

    state-machine-model

    如果未提供端口,它将在默认端口1099上运行: rmiregistry 编译BankServerImpl.java文件javac BankServerImpl.java编译BankClient.java文件javac BankClient.java运行BankServerImpl并在端口RMI运行时提供输入。...

    ChordDHT:使用类似Chord的逻辑和Java RMI的DHT实现

    网络使用Java RMI for RPC实现类似Chord的逻辑。 Maven用于构建运行项目所需的胖JAR。 该项目包含两个需要构建的JAR,分别是BootStrapNode JAR和ChordNode JAR,可以通过将pom.xml(第24行)中的MainClass更改为...

    Java项目开发与毕业设计指导

    当然也可以在Eclipse中配置运行时程序的参数来运行程序(运行时参数主要负责传递端口号,服务器地址等)。 Ch12:基于Agent实现的分布式计算 本程序的运行基于Aglet,首先要安装和配置Aglet:推荐安装Aglet稳定...

    AutoPlay_Menu_Builder6.0.1328注册版

     启动窗口和结束窗口启动窗口是启动自动运行菜单时显示的窗口,而结束窗口则是退出菜单时显示的窗口。可以在启动窗口和结束窗口中使用不规则外观以及淡入淡出特效。测试按钮可以测试启动窗口和结束窗口的实际效果。...

    AutoPlay_Menu_Builder5.5.0.1328注册版

     启动窗口和结束窗口启动窗口是启动自动运行菜单时显示的窗口,而结束窗口则是退出菜单时显示的窗口。可以在启动窗口和结束窗口中使用不规则外观以及淡入淡出特效。测试按钮可以测试启动窗口和结束窗口的实际效果。...

    chattie:Chattie 是一个简单的 Java 聊天应用程序,用于创建一个简单的聊天服务器

    rmi 轻松部署简单聊天客户端 - 服务器的 Java 应用程序##版本服务器-客户端:简单的服务器-客户端聊天 irc 风格,用于客户端之间的普通聊天P2P:点对点版本,带服务器同步联系人,允许基于“IRC”的聊天和一对一聊天...

    J2EE应用开发详解

    29 3.2.2 Class.forName()加载类的实例 30 3.2.3 loadClass获得类的实例 31 3.3 操作类的字段 31 3.3.1 获取对象的属性 31 3.4 操作类的方法 34 3.4.1 运行时调用对象的方法 34 3.4.2 无参构造函数 36 3.4.3 带参...

    电脑闹钟3.0(delphi)源代码

    2、修正了设置为 自动开机运行 时有时不能正确自动运行的问题; 3、增加了一个About窗口(做广告?); 4、重写了播放器代码,现在可以支持更多的格式:wav,wma,mid,rmi,mp3……等。如果在你的机器上出现某种声音...

    Simple-Distributed-FTP:简单的分布式应用程序将文件从发送到服务器组

    它是使用RMI用Java编写的。 运行简单测试 简单测试说明: 4个服务器实例一直在运行 使用GET方法运行1个客户端(下载文件) 使用PUT方法运行1个客户端(上传文件) 使用PUT方法运行1个客户端(上传现有文件-用较...

    超级有影响力霸气的Java面试题大全文档

    java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 9、说出Servlet的生命周期,并说出Servlet和CGI的区别。  Servlet被服务器实例化后,容器运行其init方法...

    系统监控软件Sigar-System_Runtime.zip

    介绍利用java程序检查服务器或主机的运行时信息,包括操作系统、CPU使用情况、内存使用情况、硬盘使用情况以及网卡、网络信息。主要的办法有两 种:第一种,使用jdk1.6以上自动的功能,实现数据的获取,但是该方法...

    java windows 计时工具

    2.RMI服务(RIM远程唤出),绑定服务的端口号,“rmi.port=值”, 如果不配置或配置错误则在 1100-9999 这个范围内使用一个可用的端口, 如果最终无可用端口则抛出异常,继续运行工具,但无法远程唤出工具。

    FibonacciService

    运行说明: 将 ds.war 放到 Apache webapps 目录中 通过执行以下命令启动 RMI 服务: java -cp crypto.jar ie.gmit.FibService 从带有 crypto.jar 的文件夹中 将浏览器指向 主要特点: RMI 服务计算斐波那契数 ...

    进销存系统文档作业例子

    java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 6、说出Servlet的生命周期,并说出Servlet和CGI的区别。 Servlet被服务器实例化后,容器运行其init方法...

    Quartz-Job-Scheduling-Framework-中文版-V0.9.1.zip

    内容提要:配置、创建并运行 Quartz RMI 端户端,演示了 Quartz RMI 客户端通过远程调度器部署一个 Job 的 的例子。 第十章. J2EE 中使用 Quartz (第一部分) 内容提要:J2EE 中引入 Quartz。在 J2EE 环境中作为 ...

Global site tag (gtag.js) - Google Analytics