`
huxiaojun_198213
  • 浏览: 103498 次
  • 性别: 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工作...

    RMI规范说明.rar_Java RMI_RMI java_rmi

    如果远程对象需要在运行时根据需要创建,可以使用激活框架。 6. **安全性**:RMI支持标准的Java安全模型,可以控制客户端对远程对象的访问权限。通过策略文件,可以设定远程方法调用的安全策略。 7. **异常处理**...

    java_rmi.rar_RMI java_java.rmi

    - **动态导出和导入**:服务器可以在运行时动态地导出或导入远程对象,提供更大的灵活性。 学习和理解Java RMI对于开发分布式Java应用至关重要,它可以帮助你构建可扩展、高性能的应用程序,尤其适用于服务器集群和...

    java RMI技术实现的网络聊天室

    6. **说明.txt**:这个文件可能包含了项目运行的步骤、配置信息或者使用说明,对于理解和运行此项目至关重要。 7. **www.pudn.com.txt**:这可能是一个链接或引用源文件的文本,表明项目是从pudn.com这样的资源分享...

    一个RMI实例

    - **运行指南**:提供如何编译、运行服务器和客户端的说明,帮助初学者理解RMI的工作流程。 学习这个RMI实例,你需要了解以下步骤: 1. **创建远程接口**:定义一个继承自Remote的接口,声明要进行远程调用的方法...

    简单的RMI程序

    7. **readMe.txt**: 通常包含项目的简要说明,如如何构建和运行程序,以及任何特定的注意事项或指导。 在实际开发中,RMI适用于那些需要跨越网络边界,但又希望像本地调用一样简单、透明的场景。然而,RMI也有其...

    rmi.rar_Java RMI_RMI java_RMI policy.all_rmi

    6. **部署和运行**:使用RMI时,需要将远程对象导出(`java.rmi.Naming.rebind()`),然后客户端可以通过`java.rmi.Naming.lookup()`查找并调用远程对象。在实际应用中,还需要配置JVM的RMI端口和其他网络参数。 在...

    RMI-IIOP 基于SUN

    RMI是Java中的一种机制,允许一个Java对象调用另一个在网络中不同 JVM(Java虚拟机)上运行的对象的方法。RMI-IIOP则是在RMI的基础上添加了对CORBA IIOP协议的支持,使得Java对象可以透明地与非Java的CORBA对象进行...

    RMI.zip_Java RMI_RMI java_rmi

    在提供的文件列表中,"RMI规范说明X.txt"很可能是RMI规范的详细文档,涵盖了RMI的各个方面,包括如何创建和部署远程对象,以及如何处理异常和优化性能。"www.pudn.com.txt"可能是下载资源的来源信息。 学习和理解...

    rmi.rar_Java RMI_RMI source code_java RMI simple_rmi

    7. **异常处理**:RMI调用可能会遇到各种网络和运行时异常,例如`java.rmi.RemoteException`,因此在调用远程方法时应进行适当的异常处理。 通过“rmi.rar”中的源代码,你可以学习如何定义和实现远程接口,创建和...

    JAVA远程调用RMI与应用

    5. **编写一次,到处运行**:遵循Java的哲学,“编写一次,到处运行”,RMI确保了应用的可移植性和兼容性。 6. **分布式垃圾收集**:RMI具备分布式垃圾收集机制,能够自动回收不再被任何客户端引用的远程服务对象,...

    一个相当经典的RMI实例源代码及详细说明

    RMI(Remote Method Invocation,远程方法调用)是Java中的一种技术,用于构建分布式系统,使得在一台机器上的对象能够调用另一台机器上对象的方法,就像它们在同一个进程中一样。RMI使得开发者可以创建跨网络的、...

    RMI开发实例源码 动态注册和静态注册

    2. **客户端代码**:说明了如何查找并调用远程对象,可能包括异常处理,如`java.rmi.ConnectException` 和 `NotBoundException`。 3. **配置文件**:如`server.properties` 或 `client.properties`,可能包含了RMI...

    java_in_rmi.rar_Java RMI_RMI java_rmi _精通rmi

    它允许Java对象在不同的Java虚拟机(JVM)之间进行交互,仿佛它们都在同一台机器上运行。这个"java_in_rmi.rar"压缩包包含了学习和精通RMI的基础资料,非常适合初学者深入理解这一主题。 首先,RMI的核心概念是远程...

    HTTP客户端,HTTP服务器,RMI客户端和服务器

    具体到实验文件"rmi",这可能是RMI相关代码或者教程的集合,可能包含了服务器端和客户端的示例代码,以及如何在Java中设置和运行RMI服务的说明。"cs"可能代表"Client-Server",也就是客户端-服务器相关的文件,可能...

    RMI.rar_DEMO_rmi

    RMI在企业级应用中,尤其是在构建大型分布式系统时,有着广泛的应用,如EJB(Enterprise JavaBeans)中的远程bean就是基于RMI实现的。因此,掌握RMI对于提升你的Java编程技能和理解分布式系统的设计至关重要。

    (转)通用JAVA RMI服务器框架

    - `Classes`:编译后的class文件,用于运行RMI服务器和客户端。 通过学习和使用这个通用的JAVA RMI服务器框架,开发者可以更快地搭建分布式应用,减少重复工作,并提高系统的可扩展性和可靠性。同时,深入理解RMI的...

    rmi 远程调用 实现客户端之间会话

    文件列表中的“程序说明.doc”可能是对整个RMI实现的详细说明,包括代码示例和步骤解释。而“RMILab”可能是一个包含实验代码的文件夹,可能包含了服务器端和客户端的Java源代码,以及运行和测试RMI应用程序所需的...

    RMI规范说明

    RMI规范说明中的多个文件可能涵盖了RMI的详细配置、性能优化、故障排查、以及与其他Java技术如EJB、CORBA的集成等内容。通过深入学习这些文档,开发者可以更好地理解RMI的工作原理,从而在实际项目中有效地利用这一...

    rmi.rar_RMI java

    `www.pudn.com.txt`文件可能是项目介绍或者示例代码的说明文档,提供了更多关于如何运行和理解代码的上下文信息。 在实际应用中,RMI被广泛用于构建分布式系统,如分布式数据库、负载均衡服务、分布式缓存等。虽然...

Global site tag (gtag.js) - Google Analytics