bigywater.tech
Java · 321

Java RMI:远程对象调用的生命周期

RMI 里 stub 是什么?参数怎么传?远程对象什么时候被回收?服务器宕了客户端看到什么?这篇用服务器与客户端两个 JVM 逐项验证,并开启 JEP 290 反序列化过滤器看被拒绝的调用。

0.072 ms
本机回环下一次 RMI 调用的平均耗时
3.01 s
客户端不发 clean 调用直接消失后,服务器调用 unreferenced() 的延迟(租约设为 3 秒)
REJECTED
serialFilter 拒绝 ArrayList 时的 InvalidClassException 状态

基本调用与端口

实测 · 本机执行输出
  [服务器] 就绪;stub 的类:jdk.proxy1.$Proxy0;stub 实现的接口:[interface Hello];registry 端口 19099,对象端口 19100;pid 56219
  服务器进程监听的 TCP 端口: ['19099', '19100']
  1 lookup 得到的对象类:jdk.proxy1.$Proxy0;是 Proxy:true;greet:hello rmi from RMI TCP Connection(2)-127.0.0.1
    registry.list():[hello]
    2000 次 greet 的平均耗时 0.072 ms/次(本机回环)

参数的传递

实测 · 本机执行输出
  2 addAll([1,2,3]) = 6
    客户端的列表在服务器 mutate 之后:[1, 2, 3](没有变化:参数按值传递,服务器改的是序列化副本)
    传 Remote 对象作参数:服务器通过 stub 回调了客户端(按引用),stub 类是 jdk.proxy1.$Proxy1
  3 传一个不可序列化的 new Object():java.rmi.MarshalException: error marshalling arguments; nested exception is:  ← 原因 java.io.NotSerializableException: java.lang.Object

异常

实测 · 本机执行输出
  4 fail(1) 客户端捕获:java.lang.IllegalStateException: 服务器端的 IllegalStateException
  4 fail(2) 客户端捕获:java.io.FileNotFoundException: 服务器端的 FileNotFoundException
  4 fail(3) 客户端捕获:java.lang.RuntimeException: 服务器端的未声明运行时异常
  5 lookup 不存在的名字:java.rmi.NotBoundException: nope

  服务器端的输出(第二节与第三节期间):
    [服务器] mutate 之后服务器手里的列表:[1, 2, 3, 999]
    [服务器] 回调客户端:客户端收到:来自服务器;cb 的类:jdk.proxy1.$Proxy1

反序列化过滤器

实测 · 本机执行输出
  6 服务器配置了 serialFilter 拒绝 ArrayList 后,传 ArrayList:java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:  ← 原因 java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:  ← 原因 java.io.InvalidClassException: filter status: REJECTED
    传 LinkedList:3

分布式垃圾回收

实测 · 本机执行输出
  7 客户端 lookup 并调用一次后,丢掉引用并 System.gc();时间戳 1791044712196
  (a) 客户端丢掉 stub 并 System.gc():服务器 unreferenced() 距客户端丢掉引用 0.01 秒
  7b 客户端调用一次后直接 Runtime.halt(不会发 clean 调用);时间戳 1791044720957
  (b) 客户端不发 clean 直接 halt:服务器 unreferenced() 距客户端消失 3.01 秒(租约 3 秒)

服务器消失

实测 · 本机执行输出
  hello 先调用一次 from RMI TCP Connection(2)-127.0.0.1
  7 服务器被 kill -9 之后用旧 stub 调用:java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is: 
    再 lookup:java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is: 

分步演示

Java RMI
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,服务器与客户端是两个本机 JVM(127.0.0.1,端口 19099 / 19100)
    • 没有验证:跨机器与 NAT / 防火墙下的 hostname 配置、SSL 的 RMI socket factory、JMX(基于 RMI)、默认租约 10 分钟下的行为、RMI 的并发与线程池、Registry 过滤器(registryFilter)、与 CORBA / IIOP 的关系
    京ICP备2026067502号-1京公网安备11010502063487号