49、Java RMI与脚本编程:远程对象垃圾回收及脚本应用解析

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Java RMI与脚本编程:远程对象垃圾回收及脚本应用解析

1. Java远程方法调用(RMI)概述

Java远程方法调用(RMI)允许一个JVM中的程序调用另一个JVM中Java对象的方法,为使用Java语言开发分布式应用提供了API。一个RMI应用涉及三个运行在不同JVM中的应用程序: rmiregistry 应用程序、服务器应用程序和客户端应用程序。其中, rmiregistry 应用程序随JDK一起发布,而服务器和客户端应用程序需要开发者自行开发。
- 服务器应用程序 :创建称为远程对象的Java对象,并将其注册到 rmiregistry 中,以便客户端后续进行名称查找。
- 客户端应用程序 :使用逻辑名称在 rmiregistry 中查找远程对象,获取其引用,然后调用该远程对象引用上的方法,这些方法会被发送到服务器应用程序在远程对象上执行,执行结果再从服务器应用程序返回给客户端应用程序。

2. RMI应用开发规则

开发RMI应用时,涉及远程通信的类和接口需要遵循以下规则:
- 创建远程接口 :必须继承自 Remote 接口,且接口中的所有方法必须包含至少抛出 RemoteException throws 子句。
- 实现远程对象类 :必须实现远程接口。服务器应用程序创建实现远程接口的类的对象,将该对象导出以使其成为真正的远程对象,并将其注册到 rmiregistry 中。客户端应用程序只需要远程接口。

3. 动态类下载

如果三个应用程序中的任何一个需要本地不可用的类,它们可以在运行时动态下载这些类。要使JVM能够动态下载类,必须将 java.rmi.server.useCodebaseOnly 属性设置为 false ,默认情况下该属性设置为 true ,会禁用JVM中的类动态下载。与远程对象引用一起,JVM还会接收名为 java.rmi.server.codebase 的属性值,这是JVM可以(如果其自身的 java.rmi.server.useCodebaseOnly 属性设置允许)下载与远程对象引用一起使用所需类的URL。

4. RMI服务器调试

RMI应用中有多个组件协同工作,调试较为困难。可以通过在运行RMI服务器时将JVM属性 java.rmi.server.logCalls 设置为 true 来记录所有对RMI服务器的调用,所有调用都会记录到标准错误中,也可以将RMI服务器调用记录到文件中。

5. 远程对象的垃圾回收

在RMI应用中,远程对象在服务器的JVM中创建, RMI 注册表和远程客户端会保留远程对象的引用。远程对象的垃圾回收与本地对象不同,本地对象在同一个JVM中创建和引用,垃圾回收器很容易确定其是否不再被引用。而对于远程对象,需要一个能够跟踪远程JVM中远程对象引用的垃圾回收器。
- 引用计数和租约机制 :RMI垃圾回收器基于引用计数,每个引用计数都关联一个租约,租约有一个有效时间段。当远程客户端(包括RMI注册表)获取远程对象的引用时,会向服务器上的RMI运行时发送消息,请求该远程对象引用的租约。服务器会为该客户端授予指定时间段的租约,将该远程对象的引用计数加1,并将租约返回给客户端。默认情况下,RMI服务器为远程对象授予10分钟的租约。
- 客户端的不同情况
- 客户端在租约期内完成使用 :客户端在获取租约的时间段内完成对远程对象引用的使用,会在客户端JVM中对该远程引用进行垃圾回收时向服务器发送消息,表示不再使用该远程对象。
- 客户端续租 :客户端可能希望为远程引用的租约再延长一段时间,会在租约有效期过半时向服务器发送租约续订请求,服务器会更新租约到期时间,在此期间不会对远程对象进行垃圾回收。
- 客户端崩溃 :服务器没有收到客户端的任何消息,客户端获取的远程引用的租约期到期,服务器会取消租约,并将该远程对象的引用计数减1。

以下是客户端与服务器之间消息交互的流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([客户端获取远程引用]):::startend --> B(发送dirty消息请求租约):::process
    B --> C{租约有效期过半?}:::decision
    C -->|是| D(发送续租请求):::process
    C -->|否| E{客户端完成使用?}:::decision
    E -->|是| F(发送clean消息取消租约):::process
    E -->|否| C
    D --> G(服务器更新租约):::process
    F --> H(服务器减少引用计数):::process
    H --> I{引用计数为0?}:::decision
    I -->|是| J(远程对象可能被垃圾回收):::process
    I -->|否| K(继续等待):::process

客户端向服务器发送消息的情况总结如下表:
| 发送时机 | 消息类型 | 说明 |
| ---- | ---- | ---- |
| 首次接收远程引用 | dirty | 向服务器表明拥有远程对象的引用并请求租约 |
| 续租时 | dirty | 请求延长租约有效期 |
| 完成使用远程引用 | clean | 通知服务器取消租约 |

6. 设置租约时间

默认租约期为10分钟,可以在启动RMI服务器时使用 java.rmi.dgc.leaseValue 属性设置租约期,租约期值以毫秒为单位。例如,以下命令将服务器程序的租约期设置为5分钟(300000毫秒):

C:\Java9APIsAndModules>java --module-path ^
dist\jdojo.rmi.common.jar;dist\jdojo.rmi.server.jar ^
-Djava.security.policy=file:///C:/mypolicy/rmi.policy ^
-Djava.rmi.dgc.leaseValue=300000 ^
-Djava.rmi.server.codebase=^
file:///C:/Java9APIsAndModules/dist/jdojo.rmi.common.jar ^
--module ^
jdojo.rmi.server/com.jdojo.rmi.server.RemoteServer
Remote server is ready...
7. 远程对象引用计数为零的通知

除了设置租约时间,RMI运行时还能提供远程对象引用计数降为零的通知。如果远程对象持有一些资源,希望在没有远程客户端引用它时释放这些资源,获取此通知很重要。要获取此通知,需要在远程对象实现类中实现 java.rmi.server.Unreferenced 接口,其声明如下:

public interface Unreferenced {
    void unreferenced();
}

当远程对象的远程引用计数变为零时, unreferenced() 方法会被调用。以下是一个实现 Unreferenced 接口的 RemoteUtilityImpl 类的示例:

// RemoteUtilityImpl.java
package com.jdojo.rmi.server;
import com.jdojo.rmi.common.RemoteUtility;
import java.rmi.server.Unreferenced;
import java.time.ZonedDateTime;

public class RemoteUtilityImpl implements
        RemoteUtility, Unreferenced {
    public RemoteUtilityImpl() {
    }

    @Override
    public String echo(String msg) {
        return msg;
    }

    @Override
    public ZonedDateTime getServerTime() {
        return ZonedDateTime.now();
    }

    @Override
    public int add(int n1, int n2) {
        return n1 + n2;
    }

    @Override
    public void unreferenced() {
        System.out.println(
            "RemoteUtility unreferenced at: " +
            ZonedDateTime.now());
    }
}

要测试 unreferenced() 方法是否被调用,可以先启动RMI注册表应用程序,再启动RMI服务器应用程序。只要RMI注册表在运行, unreferenced() 方法就不会被调用,需要关闭RMI注册表应用程序,等待远程对象引用的租约到期或在关闭RMI注册表时被取消,之后会在服务器程序的标准输出中看到 unreferenced() 方法打印的消息。

8. RMI注册表的使用

RMI注册表应仅作为启动远程客户端的引导手段,之后远程客户端可以通过对另一个远程对象的方法调用接收远程对象的引用。如果远程客户端通过对远程对象的远程方法调用接收远程对象引用,则该远程对象的引用无需在RMI注册表中注册。在这种情况下,最后一个远程客户端完成对远程引用的使用后,服务器会对远程对象进行垃圾回收,而不是在其绑定到RMI注册表时将其保留在内存中。

9. Java中的脚本编程

Java虚拟机(JVM)并非只能执行用Java编程语言编写的程序,它执行与语言无关的字节码,只要程序能编译成Java字节码,就可以执行任何编程语言编写的程序。
- 脚本语言的概念 :脚本语言是一种提供编写脚本能力的编程语言,这些脚本由称为脚本引擎(或解释器)的运行时环境进行评估(或解释)。脚本是使用脚本语言的语法编写的字符序列,用作解释器执行程序的源。解释器会解析脚本,生成程序的内部表示形式即中间代码,并执行该中间代码,同时将脚本中使用的变量存储在称为符号表的数据结构中。
- 脚本语言与编译语言的区别 :通常,与编译型编程语言不同,脚本语言的源代码(称为脚本)不进行编译,而是在运行时解释。不过,一些脚本语言编写的脚本可能会编译成可以由JVM运行的Java字节码。
- Java的脚本支持 :Java平台包含了脚本支持,允许Java应用程序执行用JavaScript、Groovy、Jython、JRuby等脚本语言编写的脚本,支持双向通信,脚本还可以访问主机应用程序创建的Java对象,Java运行时和脚本语言运行时可以相互通信并使用彼此的功能。Java对脚本语言的支持通过Java脚本API实现,Java脚本API中的所有类和接口都在 javax.script 包中,该包位于 java.scripting 模块中。
- 使用脚本语言的优势
- 大多数脚本语言是动态类型的,使程序编写更简单。
- 提供了一种更快的方式来开发和测试小型应用程序。
- 允许最终用户进行定制。
- 脚本语言可能提供Java中没有的特定领域功能。

以下是Java脚本编程相关模块的声明示例:

// module-info.java
module jdojo.script {
    requires java.scripting;
    requires jdk.unsupported;
    // <- needed for GraalVM JavaScript
    exports com.jdojo.script;
}

使用Java脚本API的模块需要像 jdojo.script 模块一样读取 java.scripting 模块。

综上所述,Java的RMI和脚本编程功能为开发者提供了强大的工具,能够构建分布式应用和灵活的脚本化应用。在实际开发中,合理利用这些功能可以提高开发效率和应用的灵活性。

Java RMI与脚本编程:远程对象垃圾回收及脚本应用解析

10. 执行脚本并传递参数

在Java中执行脚本并传递参数是常见的操作。下面将详细介绍如何实现这一过程。

首先,需要获取脚本引擎实例。以JavaScript脚本为例,以下是获取JavaScript脚本引擎的代码:

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class ScriptExecutionExample {
    public static void main(String[] args) {
        // 创建脚本引擎管理器
        ScriptEngineManager manager = new ScriptEngineManager();
        // 获取JavaScript脚本引擎
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        if (engine == null) {
            System.out.println("未找到JavaScript脚本引擎");
            return;
        }

        try {
            // 向脚本引擎中设置参数
            engine.put("message", "Hello from Java!");
            // 执行脚本
            engine.eval("print(message);");
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}

上述代码的执行步骤如下:
1. 创建 ScriptEngineManager 对象,用于管理脚本引擎。
2. 通过 getEngineByName 方法获取JavaScript脚本引擎。
3. 使用 put 方法向脚本引擎中设置参数,这里设置了一个名为 message 的参数。
4. 使用 eval 方法执行脚本,脚本中打印了传入的参数。

11. ScriptContext的使用

ScriptContext 在执行脚本时起着重要作用,它用于管理脚本的输入、输出和变量。以下是一个使用 ScriptContext 的示例:

import javax.script.*;
import java.io.StringWriter;

public class ScriptContextExample {
    public static void main(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // 创建ScriptContext对象
        SimpleScriptContext context = new SimpleScriptContext();
        // 创建StringWriter用于捕获脚本输出
        StringWriter writer = new StringWriter();
        context.setWriter(writer);

        try {
            // 执行脚本并指定ScriptContext
            engine.eval("print('This is a test.');", context);
            // 输出脚本执行结果
            System.out.println(writer.toString());
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}

上述代码的操作步骤如下:
1. 创建 ScriptEngineManager 和脚本引擎。
2. 创建 SimpleScriptContext 对象,并创建 StringWriter 用于捕获脚本输出。
3. 将 StringWriter 设置为 ScriptContext 的输出目标。
4. 使用 eval 方法执行脚本,并指定 ScriptContext
5. 输出脚本执行结果。

12. 在脚本中使用Java编程

在脚本中可以使用Java编程,以下是一个在JavaScript脚本中使用Java对象的示例:

import javax.script.*;

public class UseJavaInScript {
    public static void main(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        try {
            // 执行脚本,创建Java对象并调用其方法
            engine.eval("var date = new java.util.Date(); print(date);");
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}

上述代码的执行流程为:
1. 创建脚本引擎管理器和脚本引擎。
2. 使用 eval 方法执行脚本,在脚本中创建 java.util.Date 对象并打印。

13. 实现脚本引擎

实现一个脚本引擎需要遵循一定的步骤,以下是一个简单的脚本引擎实现示例:

import javax.script.*;
import java.io.Reader;

// 自定义脚本引擎工厂
class MyScriptEngineFactory implements ScriptEngineFactory {
    @Override
    public String getEngineName() {
        return "MyScriptEngine";
    }

    @Override
    public String getEngineVersion() {
        return "1.0";
    }

    @Override
    public String getLanguageName() {
        return "MyLanguage";
    }

    @Override
    public String getLanguageVersion() {
        return "1.0";
    }

    @Override
    public String getMethodCallSyntax(String obj, String method, String... args) {
        return null;
    }

    @Override
    public String getOutputStatement(String toDisplay) {
        return null;
    }

    @Override
    public String getProgram(String... statements) {
        return null;
    }

    @Override
    public Object getParameter(String key) {
        return null;
    }

    @Override
    public String[] getNames() {
        return new String[]{"myengine"};
    }

    @Override
    public String[] getMimeTypes() {
        return new String[0];
    }

    @Override
    public String[] getExtensions() {
        return new String[0];
    }

    @Override
    public ScriptEngine getScriptEngine() {
        return new MyScriptEngine(this);
    }
}

// 自定义脚本引擎
class MyScriptEngine extends AbstractScriptEngine {
    private final ScriptEngineFactory factory;

    public MyScriptEngine(ScriptEngineFactory factory) {
        this.factory = factory;
    }

    @Override
    public Object eval(String script, ScriptContext context) throws ScriptException {
        // 简单实现,直接返回脚本内容
        return script;
    }

    @Override
    public Object eval(Reader reader, ScriptContext context) throws ScriptException {
        return null;
    }

    @Override
    public Bindings createBindings() {
        return new SimpleBindings();
    }

    @Override
    public ScriptEngineFactory getFactory() {
        return factory;
    }
}

public class ImplementScriptEngine {
    public static void main(String[] args) {
        MyScriptEngineFactory factory = new MyScriptEngineFactory();
        ScriptEngine engine = factory.getScriptEngine();

        try {
            Object result = engine.eval("This is a test script.");
            System.out.println(result);
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}

实现脚本引擎的步骤如下:
1. 创建自定义的 ScriptEngineFactory 类,实现必要的方法,如 getEngineName getScriptEngine 等。
2. 创建自定义的 ScriptEngine 类,继承自 AbstractScriptEngine ,实现 eval 等方法。
3. 在 main 方法中创建 ScriptEngineFactory 对象,获取脚本引擎并执行脚本。

14. 总结

本文详细介绍了Java远程方法调用(RMI)和Java中的脚本编程。RMI允许一个JVM中的程序调用另一个JVM中Java对象的方法,通过引用计数和租约机制实现远程对象的垃圾回收。同时,Java平台提供了脚本支持,允许执行多种脚本语言编写的脚本,支持双向通信和在脚本中使用Java对象。以下是本文的关键知识点总结:
| 主题 | 关键知识点 |
| ---- | ---- |
| Java RMI | 远程对象创建、注册,动态类下载,远程对象垃圾回收,引用计数和租约机制,RMI注册表的使用 |
| Java脚本编程 | 脚本语言概念,Java的脚本支持,执行脚本并传递参数,ScriptContext的使用,在脚本中使用Java编程,实现脚本引擎 |

通过合理运用这些技术,可以开发出更灵活、高效的Java应用程序。

内容概要:本文围绕“基于改进秃鹰算法的微电网群经济优化调度”展开研究,提出了一种改进的秃鹰搜索算法(BES),旨在解决微电网群在复杂运行环境下的多目标、强约束、非线性及高维经济调度问题。通过引入特定优化策略,增强了基础算法的全局搜索能力和收敛效率,克服了传统智能算法易陷入局部最优的缺陷。研究构建了一个包含分布式电源、储能系统多元负荷的微电网群调度模型,以最小化系统综合运行成本为核心目标,综合考虑功率平衡、设备出力能力、储能运行特性等多重约束条件。通过仿真实验验证了所提算法在调度精度、稳定性和计算效率方面相较于传统方法具有明显优势,并进一步展示了其在降低能源开支、提升可再生能源消纳水平方面的实际应用价值。; 适合人群:具备一定电力系统基础知识或优化算法背景,从事新能源调度、智能优化算法研究应用等相关领域的研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于微电网群、综合能源系统等场景下的经济调度优化;②为秃鹰算法及其他群体智能算法的改进、复现性能对比提供参考范例;③服务于科研仿真、算法验证及工程化应用需求。; 阅读建议:建议读者结合文中提供的Matlab代码实现进行实践操作,重点关注算法改进机制调度模型的构建逻辑,同时可借助网盘资源获取完整资料,以加深对算法性能表现应用场景的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值