Chapter I: JSR

聊聊JSR

Java如何诞生,想到这个话题,大部分人都可以枚举出一堆和它相关的历史,多少年、什么版本、追加语法是什么、语法特性是什么,而在这个过程中,似乎忽略了它最最底层的体系,而今天我们聊聊它的基础体系。

基本说明

JCP

JCP的全称是 Java Community Process( https://jcp.org/ ),是一个开放的国际组织,它的核心成员是Java语言开发者,JCP的原创是SUN(不知今天是否还有人听过这个名字),JCP是SUN公司在1995年创造的非正式过程,到如今已经包含了数百名来自世界各地的Java代表成员,共同定制Java发展的正式程序。

其实JCP就是社区共同对Java未来功能和发展方向定制 RoadMap 的过程和组织,您可以申请成为JCP会员、提交Java发展的功能建议,而 成为JCP会员是提交JSR的唯一途径。JCP必须保证使用兼容型、基于大家共识的方式开发高质量的规范,JCP批准的规范必须具有:

  • 参考实现(证明此规范是可实现的)

  • 技术兼容性套件(一套测试、工具、文档,测试实现是否符合规范)

JCP本身的流程也经历了12个版本的迭代,其中:1.0(1个)、2.x(11个),JCP2的流程如下:

JSR Life Cycle Dec2018

JSR

JSR的全称是 Java Specification Requests,它的中文翻译是 Java规范提案 ,表示 JCP 提出新增一个标准化技术规范的正式请求,任何人都可以提交JSR、向Java平台追加新的API、服务、功能,它已经成为了Java世界的一个重要标准。若JSR规范提案通过之后,就可以交给JDK团队成员开发,通常JSR的范围是和Java语言相关的,它通常会使用一个编号来置顶,如 JSR-370、JSR-311 等。

批准JSR的三个部分
名称 翻译 说明

规范

Specification

定义了技术的功能、接口、设计方面的细节。

参考实现

Reference Implementation

RI - 规范的一个完整、可运行的实现,用于验证规范的正确性、可行性。

技术兼容性套件

Technology Compatibility Kit

TCK - 一套测试套件,用于检查某个规范的实现是否符合兼容性要求。

JEP

JEP的全称是 JDK Enhancement Proposal,中文翻译是 JDK增强建议 ,JEP是一个JDK核心技术的增强建议文档,JEP可能要求探索一些新颖(或古怪)的想法,而这些想法通常是尚未进入正式规划的建议。但它对Java语言的贡献很明显,一般来说会要求原型设计区分可行、不可行的想法,直到能产生正式的规范说明。

JLS

JLS的全称是 Java Language Specification,中文翻译是 Java语法规范:它用于指出Java的语法标准和基础语法规则,这些语法规则描述了程序的合规以及不合规的相关说明,同时指出了程序的含义并且告诉开发人员之后会发生什么?JLS比JSR更加权威,它对Java语言的合规性保驾护航。

相互关系

主角全部登场,它们的核心流程如下:

  1. JEP:JEP提出了某个实验性的想法,以至于对这个想法做出的原型验证。

  2. JSR:JCP组织最终采纳了成熟的想法,此想法可能是直接提交的JSR、也可能来自JEP,采纳之后经过评审产生了新的规范、或者修订了现有规范,此JSR最终可能转换成JLS。

  3. JLS:JSR的研究通常会产生一个标准、有些JSR会产生部分接口规范、参考实现等。

JDK是一个庞大的生态,上述角色中注意以下几点:

  • 并非所有的JEP都能实现,某些JEP的探索最终可能会夭折。

  • 并非所有的JSR都会转换成JLS语言规范,有可能只是单纯接口、参考实现或其他。

  • JLS规范只是整个Java规范中的其中一部分,Java规范还包含其他如 JVM规范、JSP规范、EJB规范等。

其实最正宗的学习Java的途径就是学习JSR和JLS,但由于英文版十分复杂,而且细节很多,这就是重开新教程的目的,我只是一个 引路人,带大家一起去这个世界转转。

辅助阅读

参考链接

  • JLS地图(制作中)

  • JSR地图(制作中)

标题种类

种类 状态 说明

正常

Final / Active

正在使用的 JSR。

下划线

Maintenance

维护状态可用的 JSR。

删除线

Dormant / Withdrawn / Inactive

状态异常的 JSR。

JSR-1: RTSJ

JSR-1,Real-time Specification for Java,RTSJ。

说明

版本

1.0.11.0.2

结构

JSR-001

  • javax.realtime

主页

https://www.rtsj.org/

说明

此包非内置包,标准安装的JDK中是没有此包内容的,必须要特定JDK,不仅如此,JVM中此规范的虚拟机称为 JVM RTS VM,和普通应用专用的JVM也会有所区别。

故事背景

Java实时专家组(RTJEG) 在JCP和JSR-000001的指导下制定了这份规范用于扩展Java语言规范和Java虚拟机规范,并提供一个应用程序编程接口创建、验证、分析、执行和管理Java线程,这些Java线程的执行包括时间约束(所以称为 实时线程 );此规范描述了 RTJEG 在工作过程中创建以及使用的设计和指导性原则,以及在 国际标准与技术研究院(NIST) 主持下制定Java实时性需求的描述,包含了七个领域的高阶设计描述。

指导性原则

原则 说明

适用于特定Java环境

在特定Java环境中使用此规范不应该有限制,如:Java SDK / Java嵌入式应用 / J2ME

向后兼容

不可以阻止在现有、正确编写的非实时程序在 RTSJ 的实现

WORA

一次编写、到处运行,RTSJ必须意识此思路的重要性、认识到实时程序的困难,不可为了增强可预测性而降低程序可移植性

实践和高级功能

涵盖实时系统实践,允许未来执行扩展和高级功能定制

可预测的执行

要将可预测性作首选,可以牺牲计算性能指标作代价

无语法扩展

不应该引入新关键字、或对Java语言进行扩展

实现决策变化

允许在多种实现决策存在差异

  • 高效或低效的算法

  • 时间和空间效率的权衡

  • 在最小实现中不需要的调度算法

  • 字节码执行代码路径长度变化

不可规定算法、具体时间常量,要满足实现的具体语义,提供灵活性而满足需求。

知识点:不可预测性

Java程序的 不可预测性:Java程序的不可预测性指的是程序的执行结果在某些情况下无法被准确地预测或确定。影响因素如下:

  • 并发性:Java程序中的并发操作可能导致不确定的结果。当多个线程同时访问共享数据或执行相同的操作时,由于线程调度和执行顺序的不确定性,可能导致结果的不一致性。

  • 线程同步问题:在多线程环境下,如果没有正确地进行线程同步,可能会导致数据竞争、死锁或活锁等问题,进而影响程序的执行结果。

  • 依赖外部资源:Java程序通常需要与外部资源进行交互,例如数据库、文件系统或网络。外部资源的状态和可用性可能受到许多因素的影响,例如网络延迟、硬件故障等,这些因素可能导致程序的执行结果不可预测。

  • 异常处理:Java程序中的异常可能会中断正常的程序流程,导致程序以不可预测的方式终止或产生意外结果。

  • JVM优化和解释器差异:不同的Java虚拟机(JVM)实现可能对代码进行不同的优化和解释,这可能导致在不同的JVM上执行相同的Java程序时产生不一致的结果。

RTSJ七领域

  1. 线程调度和分派:Thread Scheduling and Dispatching

    • 允许实时Java线程使用底层调度机制。

    • 允许实现提供未来可扩展的调度算法。

    • 允许以编程方式适用于底层调度机制参数。

    • 可提供任意必要方法 创建、管理、准入、终止 实时程序。

    RTSJ实现了一个基本调度器,程序员必须熟悉此调度器,它拥有 优先级、可抢占 机制,并包含28个唯一优先级。

  2. 内存管理:Memory Management, 尽可能让底层系统自动实现内存管理,不干扰编程。GC回收规范:

    • 独立于特定的 GC 算法。

    • 允许程序准确地描述实现的GC算法对实时Java线程的执行时间、抢占和分派的影响。

    • 允许在任何GC算法的干扰之外进行对象的分配和回收。

  3. 同步和资源共享:Synchronization and Resource Sharing

    实时系统引入了:优先级反转,此原则针对 synchronized 关键字:允许实时安全同步的最不侵入性的规范是要求Java关键字synchronized的实现包括一个或多个算法,以防止共享序列化资源的实时Java线程之间发生优先级反转。

    Q:使用实现所需的优先级反转算法实现的synchronized关键字,不能同时防止优先级反转和允许一个线程具有比垃圾回收器更高的执行准入逻辑。
    A:提供一组无等待队列(Wait-Free Queue)解决上述情况。
    知识点:无等待队列(Wait-Free Queue)

    在实时系统中,无等待队列(Wait-Free Queue)是一种并发数据结构,用于在多个线程之间进行数据传递和同步,而无需使用阻塞或等待的操作。它的设计目标是实现无冲突、非阻塞、线程安全的数据交换。

    • 关键特性:任何线程在任意时间点都可以执行操作(插入或删除)而不会被其他线程的操作阻塞或无限期地等待。这使得无等待队列在实时系统中具有重要的作用,因为实时系统需要满足严格的时间限制和响应性要求。

    • 实现方案:为了实现无等待性质,无等待队列通常使用原子操作和一些特殊的算法和数据结构来处理并发冲突。这些算法和数据结构可以确保多个线程可以同时进行插入和删除操作,而不会出现数据不一致或竞争条件。

    • 应用场景:无等待队列在实时系统中具有许多应用场景,例如任务调度、事件处理和数据传输等。通过使用无等待队列,实时系统可以避免线程间的阻塞和等待,提高并发性能和响应性,并减少不确定性,从而更好地满足实时系统的时间约束和可预测性要求。

  4. 异步事件处理:Asynchronous Event Handling:

    现实世界是异步的!

    RTSJ推广了Java语言的异步事件处理机制。所需的类表示可能发生的事情以及在发生这些事情时执行的逻辑。值得注意的一点是,逻辑的执行由实现的调度器进行调度和分派。

  5. 异步控制转移:Asynchronous Transfer of Control, RTSJ包含一种机制扩展了Java异常处理,它允许应用程序以编程方式更改另一个Java线程的控制权,并且RTSJ将此异步控制转移限制为专门编写了其控制权可能异步更改的逻辑。

  6. 异步线程终止:Asynchronous Thread Termination, 应用程序逻辑可能需要安排实时Java线程迅速而安全地将其控制权转移到其最外层范围,从而以正常方式结束。需要注意的是,与传统、不安全和已弃用的Java线程停止机制不同,RTSJ用于异步事件处理和控制转移的机制是安全的。

  7. 物理内存访问:Physical Memory Access, 对于许多可能有效利用RTSJ实现的应用程序来说,物理内存访问是可取的,所以RTSJ允许程序员以字节级访问物理内存并在物理内存中创建对象。 :data-uri: :table-caption!:

RTSJ设计原理

  • 调度:Scheduling

  • 内存管理:Memory Management

  • 同步:Synchronization

  • 异步事件处理:Asynchronous Event Handling

  • 异步控制传输:Asynchronous Transfer of Control

  • 异步实时线程终止:Asynchronous Real-Time Thread Termination

  • 物理内存访问:Physical Memory Access

调度

实时编程核心关注点是确保机器指令 及时执行可预测性,不同调度方案的 指令序列命名 有所差异:

概念 命名

线程

Thread

任务

Task

模块

Module

Block

知识点:可调度对象

RTSJ中引入了 可调度对象(Schedulable Object) 的概念,它是基本调度器管理的对象,包括 RelatimeThreadAsyncEventHandler 以及各自的子类。可调度对象的及时执行意味着程序员可以通过对程序的分析、在特定实现上对程序进行测试或两者兼而有之来确定特定线程是否总是在给定的时效性约束之前完成执行。这是实时编程的核心:将时间约束添加到计算的正确性条件中,通常,时间约束是以 相对绝对 时间表示截止时间。

术语 调度(或调度算法) 表示一组可调度对象(一个调度)生成执行序列(或顺序),此对象试图优化一个特定的度量标准(衡量系统满足时间约束的程度),而可行性分析负责评估调度是否对度量标准具有可接受的值:

  • 硬实时系统——度量标准是“错过的截止时间数量”,对该度量标准的唯一可接受值是零。

  • 软实时系统——使用其他度量标准(如平均延迟),对所使用的度量标准可能接受各种值。

系统实现 调度 的核心关键在于支持 线程优先级,它是一个和可调度对象关联的整数值,该值向操作系统传达了 线程执行的顺序,所以 优先级 的本质是 执行资格(Execution Eligibility),系统使用 分发(Dispatching) 表示系统从准备运行的线程池中选择具有 最高 执行资格的线程运行—— 目前系统大部分实现的优先级控制由程序员负责,而并非系统控制,所以 RTSJ 中 基本调度器 的优先级分配也是由程序员负责,但它继承了超类的部分框架级方法,可以让您在编程过程中更加流程(有助于实现调度的可行性、正确性)。

RTSJ 中 基本调度器 假设处理器足够快而实现调度可行性、正确性,它可以处理按计划完成任务的负载;不仅如此 RTSJ 中定义的 基本调度器 提供了抽象类让特定场景中的调度器可子类化(如EDF),这些子类化的实现可根据实际不同场景进行扩展以完成任务的实际调度,这种设计和现今大量的 调度框架 异曲同工。RTSJ 还定义了特殊参数格式 XxxParameters(如 SchedulingParameters)参数类,这些参数类的实例保存了一个或多个调度对象针对特殊场景资源需求的相关特征,这个 参数类 可以绑定到可调度对象上,如此可调度对象就具有了和 参数类 相匹配的特定特征。——典型如 PriorityParameters 会让可调度对象具备此对象优先于其他可调度对象执行的特征;再者 RTSJ 遵循跨平台原则 (WORA),对 优先级调度器 执行严格定义和规范,限定了这部分代码在 可移植 过程中的详细规则,且同时支持不可移植的平台调度器(强依赖操作系统)。

内存管理

垃圾回收(Garbage-Collected)的内存堆一直是实时编程的 障碍,JVM中的垃圾回收器具有不可预测的延迟,RTSJ 则根据内存模型定义了多个扩展解决这种问题,这些扩展本身不干扰实时程序的运行,并提供了可预测具有确定性的方式执行内存管理,此规范中的内存管理允许在垃圾回收器之外的区域进行内存管理。

知识点:JVM中垃圾回收

系统执行了 System.gc() 之后,很多程序员有一个误区,觉得垃圾回收器就会启动,实际运行并非如此,Java语言执行了此行代码之后,只是单纯给操作系统发送了 一次垃圾回收请求,所以最终内部执行流程如下:

  • Java代码中调用 System.gc(),程序向操作系统发送一次垃圾回收请求。

  • 操作系统会根据运行过程中资源使用情况执行垃圾回收调度。

  • 若调度成功会执行 垃圾回收,若调度失败会一直等待可运行垃圾回收器的资源空间。

简单说这行代码执行之后垃圾回收器不会立即执行,而等待过程取决于操作系统,这就是垃圾回收器的 不可预测的延迟 的由来。

内存区域

RTSJ 中定义了 内存区域(Memory Area) 的概念,它表示可用来分配对象的内存区域:

  • 某些内存区域位于 之外,这些区域可以对系统和垃圾回收器的操作产生限制。

  • 某些区域中的对象为 永生对象,这些区域中的对象永远不进行垃圾回收,但这些区域依旧会被垃圾回收器扫描而保证运行过程中对象引用的完整性。

RTSJ内存区域分类
区域类型 英文 说明

作用域内存

Scoped Memory

主要用于管理应用程序定义了生命周期对象的管理,用于限定分配在此区域内对象的生命周期。

物理内存

Physical Memory

具有特殊的重要特性(如执行速度更快)的一类特定物理内存区域内创建的对象管理。

永生内存

Immortal Memory

任何可调度对象都可以引用它,不受 异常、垃圾回收 等JVM语言机制的影响,常用于非堆实时线程、非堆异步事件处理程序;它在应用程序所有可调度对象和线程之间共享内存资源,此区域中分配的对象不会因为垃圾回收而延延迟(简单说具有实时垃圾回收功能)

堆内存

Heap Memory

RTSJ 不改变堆上对象的生命周期,这种类型等价于标准的 JVM 堆内存中存储的对象。

知识点:作用域内存

作用域内存 ScopedMemory 是 RTSJ 引入的新概念,它用于限定分配在此内存区域中对象的生命周期,每次使用 new XXX 时都会从活动的内存作用域中分配内存,作用域会显示介入、附加到可调度对象上,在某个线程执行 run() 方法之前会保证这些对象进入作用域。

JVM通过类似 引用计数 的技术对作用域对象执行管理,确保作用域内存的内容在作用域中没有对象被引用时被丢弃,符合 RTSJ 规范的实现会维护每个内存区域的外部引用数量计数。程序通过 MemoryArea的enter() 方法进入新作用域、使用 ScopedMemory 创建可调度对象、打开内部作用域,同时增加 ScopedMemory 的引用计数。enter() 方法执行后会返回一个可调度对象,该对象使用了 ScopedMemory 的作用域,当此对象销毁或终止生命周期,此作用域引用计数将会减少,若引用计数降至 0,则会执行每个对象的 finalize 方法执行清理和销毁的最终步骤。

作用域可以嵌套,对象进入嵌套作用域后,后续的分配都将和新作用域关联的内存中进行,等到退出嵌套作用域后,恢复先前的作用域,后续的分配将再次从原作用域中进行。由于作用域对象的生命周期,要通过一组受限的赋值规则来限制对作用域对象的引用,所以不能将作用域对象的引用分配给外部作用域,也不能分配给堆或永生区域中对象、类、字段,简单说:作用域对象的引用只能分配到同一 作用域嵌套作用域 中,违反此规则虚拟机将会抛出异常。

同步

关于最高优先级线程:调度器在所有准备运行的线程中会优先选择此线程执行程序,但是不一定会严格按照基于优先级的调度机制执行。

等待队列

等待获取资源的线程和异步事件处理程序必须按照执行资格的顺序释放,这适用于 处理器同步块,若在活动调度策略下存在具有相同执行资格的可调度对象,这些可调度对象将按照先进先出的顺序被唤醒。场景如:

  • 等待进入同步块的线程将按照执行资格顺序获得对同步块的访问权限。

  • 准备就绪的阻塞线程将按照执行资格顺序获得对处理器的访问权限。

  • 执行资格由线程自身或其他线程明确设置的线程将按照执行资格顺序获得对处理器的访问权限。

  • 执行 yield 操作的线程将在具有相同执行资格的等待线程之后获得对处理器的访问权限。

  • 以优先级更高的执行资格抢占其他线程的线程可能在任何时间获得对处理器的访问权限,具体取决于具体的实现(必须提供文档、指明授予访问权限的基本算法)。

避免优先级反转

任何实现都必须提供具有默认行为的 synchronized 原语实现,确保没有无限制的优先级反转;此外,这必须适用于内部运行的代码以及实时线程。

优先级协议
协议 效果

继承协议

(默认)如果线程t1尝试获取由较低优先级线程t2持有的锁,只要t2持有该锁(并且如果t2本身正在等待获取由优先级更低的线程持有的锁),则会将t2的优先级提升到t1的优先级。

天花板协议(最高锁持有者协议)

前提是此策略得到了实现的支持,且监视器控制策略是可扩展的。

  • 当创建一个 监视器(Monitor) 时,系统为其分配一个"优先级天花板",程序会选择任何可能尝试进入该监视器的线程中最高的优先级线程。

  • 一旦线程进入同步代码,其(活动的)优先级将提升到监视器的天花板优先级;若由于编程错误,线程的基本优先级高于要尝试进入的监视器的天花板优先级,则抛出异常。

  • 离开监视器时,线程的活动优先级将被重置;简单情况下,它将被设置为线程之前的活动优先级,但在某些情况下(例如,在线程在监视器中时对其基本优先级进行动态更改)可能会有不同的值。

请注意,尽管RTSJ要求非堆内可调度对象的执行不能受到垃圾回收的影响而延迟,但应用程序可以通过使用堆线程、可调度对象与非堆可调度对象之间的代码进行同步,使得非堆内可调度对象进入等待队列,等待垃圾回收。RTSJ提供了无等待队列类,以提供对常规Java线程和非堆实时线程访问的对象的受保护、非阻塞、共享访问,这些类专用于实现非堆可调度对象、常规Java线程、使用堆的可调度对象之间的通信。

异步事件处理

RTSJ 中的异步事件主要由两个类构成:

  • AsyncEvent:表示可以发生的事件,如 POSIX信号、硬件中断、计算的事件,若上述某个事件发生时,则通过调用 fire() 方法指示,相关联的 AsyncEventHandler 将会被调度,同时内部的 handleAsyncEvent() 方法将被执行,此外该对象提供了管理这些对象的专用方法。

  • AsyncEventHandler:此实例可以看作类似线程的对象,它实现了 Runnable 接口,事件触发时,相关处理器程序会被调度并执行 handleAsyncEvent() 方法。它和普通的 Runnable 线程的区别是该对象内部关联了 ReleaseParameters、SchedulingParameters、MemoryParameters 的实例,用于控制 AsyncEvent 出发后处理程序的实际执行细节

AsyncEvent 的一种特殊形态是 Timer,它表示由时间驱动的事件,它包含两种:

  • OneShotTimer:在指定的时间点触发一次。

  • PeriodicTimer:会在指定的时间点首次触发,然后按指定的间隔周期性触发。

异步控制传输

异步控制传输(ATC):在许多情况下,实时程序员面临的情况是算法的计算成本高且不可预测,算法是迭代的并且在每次迭代中产生逐渐优化的结果。如果在开始计算之前,系统只能确定计算执行的时间界限,那么在已知时间界限到期时从计算异步转移控制到结果传输代码是一种很方便的编程风格。

异步控制传输的几个基本原则:

  1. 方法原则:

    • 由于遗留代码或库方法可能是在假设没有ATC的情况下编写的,所以方法必须明确对ATC的敏感性,默认情况下必须关闭ATC(这种代码中的ATC必须推迟执行)。

    • 即使方法允许ATC,某些代码段(如:同步方法、静态初始化程序、同步语句块)必须执行完毕,因此在这些部分中ATC被推迟执行。

    • 对ATC做出响应的代码不会返回到触发ATC的可调度对象中,简单说ATC是一种无条件的控制传输、不用支持返回语义(没有 return 的异步执行)。

  2. 表达原则:

    • 通过一种机制可以在目标可调度对象中显式触发ATC,此触发可以直接(来自源线程或可调度对象)或间接(通过异步事件处理程序)进行。

    • 必须能够基于任何异步事件触发ATC,包括外部事件、来自其他线程、可调度对象的显式事件触发(特别是定时器触发ATC)。

    • ATC必须能够中止(不使用 Thread 类的 stop()destroy() 方法这种危险方式)正在执行的实时线程。

  3. 语义原则:

    • 如果使用异常处理对ATC进行建模,必须有一种方法来确保异步异常只被预期处理程序捕获,而不是传播路径上的通用处理程序捕获。

    • 嵌套ATC必须正常工作。

  4. 实用主义原则:

    • 对于常见情况,如定时器处理程序和实时线程终止,应提供简单明了的习惯用法。

    • 如果具有超时的代码在定时器到期之前完成,定时器需要自动停止,并将其资源释放并返回给系统。

异步实时线程终止

许多与外部真实世界的非计算机系统(例如人类、机器、控制过程等)紧密交互的事件驱动计算机系统需要在非计算机真实世界系统发生重大变化时改变其计算的行为模式。若能在线程执行外部编写一段程序,对线程本身执行状态变更让其不可用,或使线程异常终止,这个程序将会更加方便管理实时线程。

知识点:设计思路

上边这种模式最简单的实现:

  • 将线程编码为只针对外部系统的一个(少量)可能状态执行运行协作。

  • 当外部系统执行状态转换时,执行行为变化可以由一个预测器进行管理,此预测器会终止一切针对旧状态的线程,并调用适用于新状态的线程。

  • 由于外部系统状态转换只在预测器中编码而不是在线程中编码,整体系统设计上会简单许多。

在早期Java语言版本中控制权不在预测器,而是在线程本身(stop()destroy())方法中,所以带来了设计上的诟病:

  • stop() 方法的调用会导致线程之间共享对象处于不一致的状态。

  • destroy() 方法则会导致死锁(JDK 1.5 才拿掉)。

RTSJ 通过异步事件处理器和异步控制传输机制组合来实现安全的异步实时线程终止,采取步骤如下:

  1. 使所有实时线程的应用程序方法可中断。

  2. 创建一个预测器,将一些异步事件处理程序绑定到在变化时发生的事件上来监视外部世界。

  3. 让处理程序对每个受更改影响的实时线程调用 interrupt() 方法。

  4. 在处理程序调用 interrupt() 之后,让它们创建适用于外部世界状态的一组新的实时线程。

上述方法提供了对实时线程的快速有序的清理和终止,最后注意:预测器可以包含适当数量的异步事件处理程序。

物理内存访问

RTSJ 中定义了一些类,让程序员可使用Java语言编写代码直接访问物理内存。

物理内存类
类名 说明

RawMemoryAccess

该类实例将一段物理内存模拟成一系列固定字节,访问器通过偏移来访问物理区域内容,此处偏移可为 `int, byte, short, long,byte[]`等,创建表示物理地址范围的对象,

RawMemoryFloatAccess

该类实例可以看作上述实例的加强版,追加了 float,double 类型,专门用于处理浮点数数据类型。

VTPhysicalMemory

全称 Volatile Physical Memory,表示易失性物理内存,即内存的内容在断电或重启后会丢失。该类用于在程序中直接访问易失性物理内存,并提供了读取和写入字节、短整型、整型、长整型等数据类型的方法。

LTPhysicalMemory

全称 Long-Term Physical Memory,表示长期存储的物理内存,即内存的内容在断电或重启后仍然保持不变。这种内存通常用于存储持久化数据,例如文件系统或数据库。

ImmortalPhysicalMemory

表示永久性物理内存,即内存的内容在整个系统的生命周期内都保持不变,这种内存通常用于存储不变的常量数据或静态数据结构。

物理内存区域:在许多情况下,需要RTSJ的可预测执行的系统还需要以特定地址访问各种类型的内存,以提高性能或其他原因。考虑一个系统,其中非常快的静态RAM可编程可用。一个能够优化性能的设计可能希望将各种经常使用的Java对象放置在快速静态RAM中。VTPhysicalMemory、LTPhysicalMemory和ImmortalPhysicalMemory 类允许程序员具有这种灵活性。程序员将在快速RAM占用的内存地址上构建一个物理内存对象。 :data-uri: :table-caption!:

JavaSE

本章RTSJ的语义会对标准Java类库中的语义产生一定的影响:

  • 对实时线程,java.lang.Thread 类中的优先级设置获取方法。

  • ThreadGroup 类处理实时线程时的行为。

  • Thread 类中和 ThreadGroup 相关方法用于实时线程的行为。

线程优先级

知识点:线程优先级

Java语言中的线程优先级(Thread Priority)是用来指定线程在竞争CPU时间时的相对优先级。每个线程都有一个默认优先级,可以通过 setPriority() 方法来设置线程的优先级,范围从1(最低优先级)到10(最高优先级)。

场景 说明

任务调度

线程的优先级可以影响线程在多线程环境中的调度顺序。具有更高优先级的线程更有可能在竞争CPU时间时被优先执行。这对于实时应用程序或需要及时响应的任务非常重要。

平衡资源分配

在资源有限的情况下,通过设置不同线程的优先级,可以在不同线程之间实现资源的平衡分配。较高优先级的线程可能更频繁地获得CPU时间,从而提高其执行速度。

任务重要性

通过设置不同线程的优先级,可以反映线程对任务的重要性。较高优先级的线程通常被认为对任务的执行更为关键,因此系统可以优先考虑执行这些线程的任务。

线程优先级不是绝对保证,具体线程调度行为依赖底层操作系统和JVM虚拟机的实现,开发人员编写代码时不应该过度依赖优先级来实现调度和性能保证。开发过程中注意点如:

  • 不同操作系统和Java虚拟机的线程优先级实现可能存在差异,因此在跨平台开发时应谨慎使用线程优先级。

  • 线程优先级不应作为控制并发的唯一手段,合理的线程设计和并发控制机制是更为可靠和可维护的选择。

  • 避免过度依赖线程优先级,过多的线程优先级调整可能导致不可预测的行为和性能问题。

  • 线程优先级的设置应该基于实际的需求和性能测试,仔细评估其对系统行为和性能的影响。

Java语言中 setPrioritygetPriority 方法修饰如下:

    public final void setPriority(int newPriority) {
        // ...
    }

    public final int getPriority() {
        // ...
    }

因此,RTSJ规范处理实时线程时不能直接覆盖这些方法并修改行为来满足 RTSJ 调度器的需求,为了使得 java.lang.Thread 与子类保持一致,语义修订如下:

setPriority

  • Thread.setPriority() 不能影响由 PriorityCeilingEmulationPriorityInheritance 控制的优先级导致优先级反转以确保算法的正确性,调用 Thread.setPriority() 导致实时线程的基本优先级的更改受Synchronization的语义控制(此处语义参考前文同步的设计原理)。

  • 实时线程可以使用setPriority来访问实时线程可用的扩展优先级范围,若实时线程的优先级参数对象未共享,setPriority的行为相当于包含以下代码片段:

    PriorityParameters pp = getSchedulingParameters();
    pp.setPriority(newPriority);
  • 如果实时线程的优先级参数对象与其他可调度对象共享,setPriority必须为线程提供一个与实时线程对象相同的内存区域分配的非共享 PriorityParameters 实例,实例中包含新的优先级值。

  • 如果线程是实时线程且新的优先级超出实时线程调度器允许的范围,setPriority则抛出 IllegalArgumentException 异常。

  • 如果线程是实时线程且其调度参数对象不是 PriorityParameters 的实例,setPriority则抛出 ClassCastException 异常。

getPriority

  • 当用于实时线程时,getPriority的行为相当于包含以下代码片段:

    ((PriorityParameters)t.getSchedulingParameters()).getPriority();
  • 如果调度参数不是 PriorityParameters 类型,则会抛出 ClassCastException 异常。

所有RTSJ中支持的监控控制策略都必须适用于Java线程、可调度对象。

线程组

知识点:线程组

Java语言中的线程组(ThreadGroup)是一种用于组织和管理线程的机制,它允许将线程划分为逻辑组,并提供一些方便的方法来操作和控制这些组中的线程。

场景 说明

组织和管理相关的线程

当存在一组相互关联的线程时,可以将它们放置在同一个线程组中,以方便对它们进行批量操作和管理。

控制线程优先级

通过设置线程组的默认优先级,可以统一管理组内线程的优先级,避免对每个线程单独设置优先级。

统一处理未捕获异常

通过为线程组设置默认的未捕获异常处理器,可以在发生未捕获异常时集中处理,而无需为每个线程单独设置异常处理器。

线程组(ThreadGroup)以一个根线程组对象作基础,此对象在堆内存、不可变内存中创建。所有线程组对象都持有其成员线程、子组引用、父组引用,由于堆内存和不可变内存无法持有作用域内存中内容的引用,因此可以得出一个结论:线程组永远不会在作用域内存中分配,作用域内存中分配的线程也无法持有线程组引用,因此这些线程不属于任何线程组而持有空组引用,反之也成立。

  • 当线程组进行枚举、中断、停止、恢复或挂起操作时,具有空线程组的实时线程不会被包括在内。然后若当前线程是具有空线程组的 实时线程 时:

    • Thread.enumerate 类方法返回整数1,并将其数组参数填充为当前实时线程。

    • Thread.activeCount 返回1。

    • Thread.getThreadGroup 在所有情况下返回null,不仅仅是线程已终止时。

  • 若一个线程创建的起点是 实时线程 ,则直接继承实时线程的线程组;否则,将尝试添加此线程到根线程组中。若您的Java线程没有权限使用根线程组,则构造函数就直接抛出 SecurityException 异常。

  • 由异步事件处理程序创建的Java线程的线程组分配方式和具有线程的实时线程组创建方式一致。

  • 线程组不能在作用域内存中创建,构造函数抛出 IllegalAssignmentError 异常。

  • 线程组中设置的优先级限制对实时线程没有影响。

  • 除非特定规范,实时线程具有和父 Thread 相同的线程组成员规则。

JSR-002: Boundary-Scan API

(已撤销)JSR-002: Boundary-Scan API

这个API的目标是针对JavaCardTM平台,以便使其能够在从最简单到最复杂的所有可用Java虚拟机上使用。重要点在于,此规范只需要JavaCard虚拟机,而不需要相关的API,且不希望Java边界扫描的API能够在智能卡上运行,但可以将其托管在8位或更简单的微处理器上,就像智能卡上使用的那些微处理器一样。

IEEE标准 1149.1 是一种国际公认并广泛使用的通信协议,用于促进电子系统的测试、调试和配置,通常在电子系统的整个生命周期中都使用该协议。在产品的每个生命周期阶段,通常会在不同的应用平台上运行测试、调试或配置程序(如个人计算机、工作站、自动测试设备或嵌入式处理器),此外,该协议的一个新兴应用是利用它来实现对网络连接但无法进行物理访问的电子系统的调试和重新配置。IEEE标准1149.1协议的性质要求对每个符合规定的电气系统上的4个(或可选的5个)引脚,即测试访问端口(TAP)进行控制。由于对TAP的访问和控制既与系统又与平台相关,建议使用本机方法实现控制接口。

JSR-003: JMXTM

(已撤销)JSR-003: JavaTM Management Extensions (JMXTM) Specification

说明

  • java.lang.management

  • javax.management

  • javax.management.loading

  • javax.management.modelmbean

  • javax.management.monitor

  • javax.management.openmbean

  • javax.management.relation

  • javax.management.remote

  • javax.management.timer

主页

https://docs.oracle.com/en/java/javase/20/jmx/index.html

JMX规范为各行业的Java开发人员提供了一种手段,可以对Java代码监测、创建智能的Java代理,实现分布式管理中间件和管理器,并将这种解决方案平滑集成到现有的管理以及监控体系中。JMX架构分三层:

  • 检测层:Instrumentation Level

  • 代理层:Agent Level

  • 分布式服务层:Distributed Service Level

JMX概念

基本介绍

  • 在不需要大量投入的情况下管理Java应用程序:您的Java应用程序中只需要嵌入一个托管对象服务器,将功能一部分或相关功能作为托管Bean( MBean )注册到对象服务器中,就可以直接针对这些对象进行管理了。

  • 提供可扩展的管理架构:每个JMX代理服务都是一个独立的模块,可以根据需求插入到管理代理中。这种基于组件的方法意味着JMX解决方案可以从占用空间小的设备扩展到大型电信交换机及更高级的设备。

  • 集成现有的管理解决方案:JMX智能代理可以通过HTML浏览器或通过各种管理协议(如SNMP和WBEM)进行管理。JMX API是开放接口,任何管理系统供应商都可以利用它们。

  • 利用现有的标准Java技术:在需要的情况下,JMX规范将引用现有的Java规范,如Java命名和目录接口™(JNDI)、Java数据库连接API(JDBC™)、Java事务服务(JTS)等。

  • 利用未来的管理理念:通过Java编程语言,JMX规范的API可以实现灵活且动态的管理解决方案,以利用新兴技术。例如,JMX解决方案可以使用查找和发现服务以及诸如Jini™网络技术、通用即插即用(Upnp)和服务位置协议(SLP)等协议。

  • 仅定义了管理所需接口:JMX API并非设计为通用的分布式对象系统。尽管它提供了一些适用于分布式环境的服务,但这些服务主要用于提供管理网络、系统和应用程序的功能。

知识点:面向接口设计

JMX是 面向接口设计 的一种典型思路,面向接口设计是一种程序设计思路,它强调将程序的不同组件解耦,并通过定义接口来实现它们之间的交互。它的几个关键性概念和原则如下:

  1. 接口(Interface):接口定义了组件之间的协议,它规定了组件之间如何交互和通信。接口只描述了组件的功能和行为,而没有具体的实现细节。

  2. 解耦(Decoupling):面向接口设计的目标之一是将组件之间的耦合度降到最低。通过依赖于抽象的接口,而不是具体的实现,组件之间可以独立地进行开发、测试和修改,从而提高代码的灵活性和可维护性。

  3. 多态(Polymorphism):通过面向接口设计,可以实现多态性。多态性允许在运行时根据具体对象的类型调用相应的方法,而不需要显式地依赖于具体的实现类。这提供了更大的灵活性和可扩展性。

  4. 单一职责原则(Single Responsibility Principle):面向接口设计鼓励将功能划分为独立的接口,每个接口只负责一个特定的功能或行为。这有助于提高代码的可读性、可维护性和可测试性。

  5. 依赖倒置原则(Dependency Inversion Principle):该原则强调依赖于抽象而不是具体的实现。组件之间的依赖关系应该建立在抽象的接口上,而不是具体的类或模块上。这样可以减少代码之间的直接依赖,提高代码的可扩展性和可重用性。

若您未遵循上述基本原则,即使您在程序中定义了 interface 也无法称为面向接口设计,这种设计思路的关键是从系统设计作为出发点来考虑整体系统架构的一种方式,并非在语法层去考虑。

JMX整体架构

jsr 003.0001

管理架构整体包括:

JSR-018: JAIN TM OAM

JSR-018,JAIN TM OAM API Specification :data-uri:

JSR-022: JAIN TM SLEE

JSR-022,JAIN TM SLEE API Specification :data-uri:

JSR-070: IIOP for JMXTM

JSR-070,IIOP Protocol Adapter for JMXTM Specification :data-uri:

JSR-071: JMX-TMN

JSR-071,JMX-TMN Specification :data-uri:

JSR-077: J2EE TM

JSR-077,J2EE TM Specification :data-uri:

JSR-146: WBEM Services / JMX

JSR-146,WBEM Services: JMX Provider Protocol Adapter :data-uri:

JSR-151: J2EE 1.4 / Java TM

JSR-151,Java TM 2 Platform, Enterprise Edition 1.4 (J2EE 1.4) Specification :data-uri:

JSR-160: JMX Remote API

JSR-160,Java TM Management Extensions (JMX) Remote API :data-uri:

JSR-174: Monitoring / Management

JSR-174,Monitoring and Management Specification for the JavaTM Virtual Machine :data-uri:

JSR-176: JDK 5.0

JSR-176,J2SETM 5.0 (Tiger) Release Contents :data-uri:

JSR-255: JMXTM 2.0

JSR-255,JavaTM Management Extensions (JMXTM) Specification, version 2.0 :data-uri:

JSR-262: JMX WS Agent

JSR-262,Web Services Connector for Java Management Extensions (JMX) Agents :data-uri:

JSR-282: RTSJ 2.0

JSR-282,RTSJ Version 2.0

说明

结构

参考 JSR-1

  • javax.realtime

主页

https://www.rtsj.org/

根据发布的PDF文档,此版本内容在 JSR-1 基础上并没有太大的改动,包括原则以及设计上,本文中并没有详细讲解 RTSJ 部分的内容,但是 RTSJ 如今已经被新版内容所替换,参考如下注释:

The RI has reached full compatibility with the RTSJ 1.0.2 for the features that remain in RTSJ 2.0 based on the original TCK. However, there are still some key features that are not yet implemented. The main areas that still need addressing is resource enforcement, scheduling improvements, and opening up the Clock API to support application defined clocks, as well as some minor additional methods here and there for usability.

For better or worse, the current specification is probably much more than was initially envisioned. However, it is now better focused on the actual needs of the embedded programmer. The de-emphasis of scoped memory is important both for the the long term viability of specification and the the advent of commercial grade deterministic garbage collection. In order to be able to bring it forward on current versions of Java, modularization and the complexity of lambda must also be considered.

The implementation is taking much longer than expected due the economic effects of the Pandemic and also the complexity of the specification. It is complicated by the need to test on multiple platforms, both Intel and ARM, and multiple operating systems, such as Linux, QNX, and VxWorks, to ensure the robustness of the specification. TCK work has progressed hand in hand with the RI, so that we can release changes as they have been made. I hope to finish this work by year end.

所以实际的 RTSJ 2.0 版本并没有发布,分析原因总结:

  • 2.0 版本和早期 1.0.2 的功能达到了完全兼容性,关键部分功能需要底层 Clock API 开放以支持应用程序定义的时钟才可以继续投入开发。

  • 现阶段的考虑以嵌入式系统实际需求为主,减少 ScopedMemory 的重视程度对长远计划十分重要,为了兼容最新版Java平台,还需考虑模块化进程和支持lambda的复杂度设计。

  • 为确保规范健壮性,需在多个平台反复测试,包括 Intel、ARM 的CPU、以及 Linux, QNX、VxWorks 多个操作系统级的测试。 :data-uri:

JSR-292: Dynamic Language

JSR-292, Supporting Dynamically Typed Languages

JSR-292 是 JDK 7 开始提交的申请,全称是“支持在Java平台上运行动态类型的语言”,它的主要目的是为了让 JVM 能够更好地支持各种动态语言如 Scala、Kotlin、Groovy 等。

JVM 在 JSR-292 中引入了两个新的概念:

  1. 一个新的字节码指令:invokedynamic,它可以在没有任何静态类型信息的情况下灵活执行方法调用,这样动态语言就可以避免反射或模拟方法( simulator method )等开销比较大的技术来实现方法调用。

  2. 一种新的对象——方法句柄:method handle,它是一个对方法的直接引用,可以作为参数传递或返回,它可以通过一个工厂类,MethodHandles 来创建或转换,方法句柄和 invokedynamic 指令配合,实现动态方法调用的链接和分派。

JSR-292 还扩展了 Java 标识符特性,允许使用任意字符序列作标识符——称为“奇异标识符”( Exotic Identifiers ),这样动态语言就可以使用自己的语法规则来定义变量、方法名,不受Java语言的限制。

关于 奇异标识符

奇异标识符是 JSR-292 引入的一种新的Java标识符语法,它可以使用任意字符序列作标识符,只要您使用井号( # )和双引号( " )包围起来,如:

#"\u03bb"       // 这里名字是希腊字母 lambda( λ ),它在一些函数式语言中经常出现

奇异标识符的主要作用如下:

  • 让JVM能够更好地支持其他动态语言的语法规则,而不受Java语言的限制。这样动态语言就可以使用自己的命名习惯来定义变量、方法而不需进行转换或适配。

  • 让JVM更好支持 invokedynamic 指令( JSR-292 引入的新的字节码指令 ),可以在没有静态类型信息的情况下执行方法调用,invokedynamic 指令需一个方法名一个方法类型来确定要调用的方法,方法名可以是任意字符串——不一定是合法的Java标识符。

JSR-305: Annotations for SDD

JSR-305, Annotations for Software Defect Detection in Java

JSR305是一套规范,它提供了一套注解,用于提供给 IDE 工具(如 IDEA)做代码的静态分析检查代码缺陷,近似 JSR303 Bean Validation规范。目前比较流行的实现如下:

  • Google 的 Findbugs

    <!-- https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305 -->
    <dependency>
        <groupId>com.google.code.findbugs</groupId>
        <artifactId>jsr305</artifactId>
        <version>3.0.2</version>
    </dependency>
  • Spring的包中默认带了 JSR-305。

目前这个 JSR 的状态是 Dormant,已经停止更新和维护了,这个 JSR 本是由 FindBugs 的创建者带领制定的,主要在于创建一套标准注解分析工具使用,然而尽管这个规范在某些地方被应用,但并没有被Java官方接受并纳入Java标准。还有一种情况是因为这个 JSR 已经被其他的规范或工具覆盖了,而且此规范包含的注解的功能有限,也导致了它并没有更好流行起来。

JSR-305 的核心思想和注解被保留下来了,并在 Java 代码的质量检查中产生了十分积极的影响。

JSR-373: JavaEE Management 2.0

JSR-373,JavaTM EE Management API 2.0