OpenJDK-秘籍-全-

OpenJDK 秘籍(全)

原文:zh.annas-archive.org/md5/eb0bb777667f31c2b6ad4f879b6fa565

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

OpenJDK 是一个独特的项目,为那些想深入了解 JVM 后面庞大而复杂的基础设施的人提供了众多激动人心的机会。有大量的知识和探索空间。几乎任何人都可以根据自己的兴趣找到一些内容,从 HTTP、Web、软件依赖性问题开始,到硬件特定的 JIT 优化技术和并发挑战结束。这种多样性非常独特,可以说没有其他开源项目能提供类似的东西。另一个因素是,在那个规模上没有那么多其他开源项目;可能只有 Linux 内核。这样的规模需要非同寻常的组织方法,而参与这个过程,了解它是如何工作的,是一种非常有趣的洞察。

这本烹饪书将引导你通过一系列步骤,尽可能顺利地进入 OpenJDK 的世界。它首先解释了如何下载源代码,如何构建 OpenJDK 的不同版本,如何在机器上设置它,以及有哪些不同的选项可用。然后,你将学习如何设置用于编辑和调试 C++ 和 Java 源代码的开发环境(IDE),以及如何开始进行修改。它将介绍一些示例,你可能会决定在 OpenJDK 的各个部分进行更改。进一步来说,它将涵盖可用于测试、基准测试和确保你所做的更改不会破坏现有功能的工具。由于 OpenJDK 是一个具有自己规则和流程的大项目,因此将有一部分内容涵盖涉及更改或修复错误的程序、项目的生命周期、JSRs、JEPs 等等。最后,将有一个关于计划包含在即将发布的版本中的未来工作的部分;这部分将是任何对 OpenJDK 的未来方向感兴趣并想尝试一些新事物的人最感兴趣的章节,而这些新事物在稳定的产品中尚未提供。

此外,这本书包含了许多实用的示例,这些示例对任何使用 OpenJDK 或任何其他 Java 技术的开发者都应该是很有用的。它们以简单形式提供,允许你快速复制并用于自己的项目。

本书涵盖的内容

第一章,OpenJDK 入门,提供了 OpenJDK 的概述,解释了它是什么,以及涵盖了在机器上运行和正确配置 OpenJDK 所需的基本步骤。

第二章,构建 OpenJDK 6,涵盖了构建 OpenJDK 版本 6 所需的步骤。这个构建与 OpenJDK 7 和 OpenJDK 8 非常不同,需要做更多手动工作。

第三章,构建 OpenJDK 7,涵盖了构建 OpenJDK 版本 7 所需的步骤。与 OpenJDK 6 相比,构建 OpenJDK 7 是一个更简单、更愉快的过程。

第四章,构建 OpenJDK 8,涵盖了构建 OpenJDK 版本 8 所需的步骤。

第五章,构建 IcedTea,教你如何构建一套在 OpenJDK 之外开发的工具。这些工具被一些专有组件所取代,这些组件作为开源不可用,包括浏览器插件、Java WebStart 等。

第六章,使用其他虚拟机实现构建 IcedTea,涵盖了几个有趣的虚拟机项目,这些项目也可以从 IcedTea 提供的功能中受益,以及如何使用这些虚拟机和非 x86 CPU 构建该产品。

第七章,与 WebStart 和浏览器插件一起工作,将涵盖 WebStart 和浏览器插件组件的配置和安装,这是 IcedTea 项目最大的部分。

第八章,破解 OpenJDK,涵盖了开始深入研究 OpenJDK 源代码所需的一些基础知识。这些事情包括 IDE 的安装和设置、调试以及更新 HotSpot 源代码。还有一些有用的示例,说明开发者可以做什么,例如实现自己的内省细节。

第九章,测试 OpenJDK,将介绍 OpenJDK 用于测试源代码的方法,因为编写代码是不够的,我们还需要编写高质量的代码,这些代码必须经过测试。本章还将展示一些使用最新可用工具的示例。

第十章,为 OpenJDK 做贡献,解释了 OpenJDK 是如何变化和演变的,变化是如何执行的,以及一个人需要做什么来参与或促进 OpenJDK 的变化。其中一些变化,如果它们足够大,可能需要数年才能出现在生产版本中。

第十一章,故障排除,介绍了任何项目中最重要的部分之一:错误修复。了解哪些工具和流程参与其中非常重要。在本章中,我们将涵盖一些重要步骤,从提交缺陷到将修复推送到共享仓库。

第十二章, 使用未来技术,涵盖了 OpenJDK 的一些未来发展方向。与任何大型项目一样,OpenJDK 有一个路线图,其中包含一些令人兴奋和有希望的项目,用于下一版本。这正是本章的内容。它列出了下载源代码、构建和尽可能运行一些示例所需的全部步骤。

第十三章, 构建自动化,提供了一些自动化构建过程的实用技巧。对于经常在 OpenJDK 中进行更改或始终希望拥有包含所有最新更改的构建的开发者来说,这将是有用的。

你需要为本书准备什么

通常,你除了需要一台连接到互联网的机器外,最好运行 Linux。如果你打算使用 Windows 机器,那么你需要安装 Cygwin。

也可能有每个食谱单独附带的其他特定要求。

本书面向对象

本书是为那些想要深入了解 OpenJDK 并开始进行更改的开发者而编写的。这可能是一个有一些酷主意并希望贡献的人,或者是一个发现了一个诱人的错误并想要修复的人。此外,它还将为那些出于某种原因想要更改现有 VM 的人提供一个良好的起点。这可能是因为研究或针对特定硬件或非常具体的需求定制 VM。

总体而言,对于任何想要简单地查看 OpenJDK 内部结构、了解其是什么以及如何工作的人来说,这将是有用的。

部分

本书包含以下部分:

准备工作

本节告诉我们可以在食谱中期待什么,并描述了如何设置任何软件或任何为食谱所需的初步设置。

如何操作...

本节描述了执行“烹饪”食谱所需的步骤。

它是如何工作的...

本节通常包括对上一节发生情况的简要和详细说明。

更多内容...

它包含有关食谱的附加信息,以使读者对食谱更加好奇。

相关内容

本节可能包含对食谱的引用。

惯例

在本书中,你会找到许多不同风格的文本,以区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称如下所示:"我们可以通过使用include指令来包含其他上下文。"

代码块如下设置:

private static int doUpdateBytes(int crc, byte[] b, int off, int len) {
    return updateBytes(crc, b, off, len);
}

任何命令行输入或输出都如下所示:

update-java-alternatives  --list

新术语重要词汇以粗体显示。屏幕上看到的单词,例如在菜单或对话框中,在文本中如下所示:"然后点击完成"。

注意

警告或重要注意事项如下所示。

小贴士

小贴士和技巧看起来像这样。

读者反馈

我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或不喜欢什么。读者反馈对我们来说非常重要,因为它帮助我们开发出您真正能从中受益的书籍。

要向我们发送一般性反馈,请简单地发送电子邮件至 <feedback@packtpub.com>,并在邮件主题中提及书籍的标题。

如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请参阅我们的作者指南,网址为 www.packtpub.com/authors

客户支持

现在您已经是 Packt 图书的骄傲拥有者了,我们有一些事情可以帮助您充分利用您的购买。

下载示例代码

您可以从您在 www.packtpub.com 的账户中下载示例代码文件,适用于您购买的所有 Packt Publishing 书籍。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。

错误报告

尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现了错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进后续版本。如果您发现任何错误,请通过访问 www.packtpub.com/submit-errata,选择您的书籍,点击错误提交表单链接,并输入您的错误详情来报告它们。一旦您的错误得到验证,您的提交将被接受,错误将被上传到我们的网站或添加到该标题错误部分的现有错误列表中。

要查看之前提交的错误报告,请访问 www.packtpub.com/books/content/support,并在搜索字段中输入书籍的名称。所需信息将出现在错误部分下。

盗版

版权材料在互联网上的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何形式的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过 <copyright@packtpub.com> 联系我们,并提供涉嫌盗版材料的链接。

我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。

问题

如果您在这本书的任何方面遇到问题,您可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决问题。

第一章. 开始使用 OpenJDK

在本章中,我们将涵盖以下主题:

  • 区分 OpenJDK 和 Oracle JDK

  • 在 Windows 上安装 OpenJDK

  • 在 Windows 上配置 OpenJDK

  • 在 Linux 上安装 OpenJDK

  • 在 Linux 上配置 OpenJDK

  • 在 OpenJDK 组和项目中导航

简介

OpenJDK 现在是一个官方的 Java 7 参考实现,现在也是 Java 8 的。这意味着 Java 生态系统中最基本的项目现在是开源的。这也意味着 OpenJDK 可以通过多种方式安装——从源码构建到如果有的话,通过包管理器安装二进制包。

Sun 努力发布开源 JDK 是项目公开宣布的开始,发生在 2006 年的 JavaOne 会议上。HotSpot 是在 GPLv2 许可证下发布的。Java 类库JCL)的完整源代码在 2007 年 5 月以 GPL 许可证发布,除了几个与 GPL 不兼容的许可证的专有部分。

然而,在 2009 年 4 月的 OpenJDK 7 b53 更新中,OpenJDK 需要一些专有部分(占总代码行的 4%到 1%,具体取决于更新号),这些部分以单独的专有包的形式存在。

人们可能会认为初始安装和配置相当简单,不需要某种详细的解释。在许多方面,这是真的;但在这个过程中也有一些困难。

我们将首先区分 OpenJDK 和 Oracle JDK。后者基于前者,但并非全部。每个都有自己的优点和缺点。OpenJDK 的主要优点是它是开源的,而 Oracle JDK 总是被推荐并且是现成的。此外,OpenJDK 6 是 Java 6 停止后唯一可维护的 Java 6 实现。

然后,我们将介绍 Windows 的安装过程以及一些 Windows 版本可能遇到的问题。之后,我们将描述一些典型的配置文件,以针对各种需求配置已安装的 OpenJDK 实例,例如服务器实例和开发人员实例。

然后,我们将更深入地探讨一些复杂的问题,例如在各种 Linux 系统上安装 OpenJDK。至少有两种常见的方法:依赖于发行版的发行版推荐方法,以及适用于所有 Linux 系统的另一种方法。

Linux 的配置基本上与 Windows 相同,但也有一些需要说明的差异。这些差异主要与系统哲学有关,即它是如何实现的,以及确切地做了什么。

然后,我们将以介绍性的方式探讨 OpenJDK 的内部结构。我们将考虑已经使用的 OpenJDK 项目,并学习如何使用我们以后需要的工具。此外,我们还将简要地查看 OpenJDK 组,了解它们在做什么以及它们可能如何影响 OpenJDK 的进一步发展。

最后但同样重要的是,您将学习如何从 Adopt OpenJDK 项目中受益,该项目也是 OpenJDK 社区的一部分。Adopt OpenJDK 是一个旨在提高 OpenJDK 可用性准备就绪、测试新语言版本以及做任何使 OpenJDK 对用户和开发者更有用和受欢迎的事情的努力。

本章的编写目的是介绍性的,不涵盖 Oracle Java 中常见的某些细节。然而,它提供了工作的必要基础。

我们将使用 Java 7,因为它稳定且是可用的最新 Java 版本。所有截图和过程都假设我们使用 Java 7,除非明确提及其他版本。

如果您已经将 OpenJDK 构建并安装为默认版本,并且您了解 OpenJDK 和 Oracle JDK 之间的区别,以及 Adopt OpenJDK 的存在,您可以完全跳过这一章。

区分 OpenJDK 和 Oracle JDK

虽然 OpenJDK 是 Java 平台的官方参考实现,但某些 Oracle 提供的软件不是开源的。其中最著名的是 Java 浏览器插件,但差异远不止于此。这个食谱将向您展示如何区分 OpenJDK 和 Oracle JDK。

准备工作

要遵循这个食谱,您需要一个已安装的 OpenJDK 实例。如果您还有一个 Oracle JDK 实例,这将有助于您感受两者的区别。此外,我们将假设您有一个 Linux 安装和一个已安装并准备使用的update-java-alternatives命令。要了解如何在各种系统上安装 OpenJDK,请参阅本章后面的食谱。如果您没有安装update-alternatives(对于 Fedora、Gentoo 等),请访问配置 Linux 上的 OpenJDK食谱或参考您的发行版文档/论坛。

如何操作...

请查看以下步骤,了解 OpenJDK 和 Oracle JDK 之间的区别:

  1. 我们将打开一个终端并输入以下命令:

    update-java-alternatives  --list
    
    
  2. 我们将看到已安装的 Java 实现的全列表:

    $ update-java-alternatives  --list
    java-1.6.0-openjdk-amd64 1061 /usr/lib/jvm/java-1.6.0-openjdk-amd64
    java-1.7.0-openjdk-amd64 1071 /usr/lib/jvm/java-1.7.0-openjdk-amd64
    java-6-oracle 1073 /usr/lib/jvm/java-6-oracle
    java-7-oracle 1081 /usr/lib/jvm/java-7-oracle
    java-8-oracle 1082 /usr/lib/jvm/java-8-oracle
    
    
  3. 让我们将 Oracle Java 设置为默认。我们将使用root权限运行以下命令:

    update-java-alternatives  --set java-7-oracle
    
    

    小贴士

    此命令可能会产生错误,例如“没有 apt 的替代品”。没关系,只需忽略它们即可。

  4. 然后,我们将前往www.java.com/en/download/installed.jsp?detect=jre并检查我们的浏览器插件版本。我们将看到激活链接(紧随激活实体名称之后)。

    我们可以从我们操作的结果中看到,Java 浏览器插件已经安装。

  5. 让我们尝试将 OpenJDK 设置为默认的 Java 环境(在您的案例中实际实例名称可能不同):

    update-java-alternatives  --set java-1.7.0-openjdk-amd64
    
    
  6. 然后我们将进入浏览器页面并刷新它。可能需要重新启动浏览器,以便更改生效,如下面的截图所示:如何操作...

我们可以看到,该插件并非来自 JDK 本身,而是来自一个名为 IcedTea 的项目。

IcedTea 是一个开源项目,其目标是尽可能多地替换 Java 生态系统的专有部分。该插件本身来自 IcedTea-Web,它是 Java Web Start 和 Java 浏览器插件的开源实现。

在大多数发行版中,IcedTea插件默认安装。但有必要记住,它是一个开源插件,绝对不是一个参考插件。这意味着它的功能可能略有不同于 Oracle 插件。也可能某些功能可能无法使用。

它是如何工作的...

Oracle JDK 仍然有一些专有组件,浏览器插件就是一个例子。在本章中,我们需要看到 OpenJDK 和 Oracle JDK 组件之间的区别。

此外,OpenJDK 和 Oracle JDK 之间巨大的差异在于许可证。OpenJDK 是开源的,而 Oracle JDK 包含专有部分,因此它根据 Oracle 二进制许可证授权。OpenJDK 是开源的事实,通过能够研究和修改其源代码,提供了全新的好处(以及令人兴奋的发现)。还值得一提的是,超过 90%的 Oracle JDK 基于 OpenJDK 源代码。这意味着 OpenJDK 的质量在没有任何妥协的情况下。与 Oracle JDK 相比,OpenJDK 中缺失的不仅仅是浏览器插件。

参见

  • 在第五章 构建 IcedTea 中,有关于如何从源代码构建 IcedTea 的详细说明。

在 Windows 上安装 OpenJDK

Windows 是世界上使用最广泛的操作系统,许多开发者将其作为他们的主要系统。尽管它很受欢迎,但 Windows 并没有像 Linux 那样得到 OpenJDK 开发社区的强烈支持,安装产品并不像在 Linux 上那么容易。这个配方将涵盖在 Windows 上安装 OpenJDK 所需的步骤。

这个配方提供了一个简单但去中心化的安装程序方法,尽管最新版本提供了自己的软件包仓库。然而,在 Windows 上,安装最新版 OpenJDK 的唯一官方方式是从源代码构建。

准备工作

要遵循这个配方,我们需要一个已安装的 Windows 系统。Windows 7 或 Windows 8 将是最佳选择,因为 Windows XP 已经被微软官方停止支持。

如何操作...

在 Windows 上有一个官方的 OpenJDK 构建,但它仅用于参考目的。它是官方的,安装简单,但没有安全更新或改进。然而,存在非官方构建,由 Alex Casco 维护。我们将尝试以两种方式安装 OpenJDK:

  1. 我们将从一个官方参考构建开始。要获取它,我们需要访问jdk7.java.net/java-se-7-ri/并接受许可条款。然后,下载并运行安装程序。

    小贴士

    虽然 OpenJDK 源代码由开放许可证许可,但这个官方构建由 Oracle 二进制代码许可证和 GPLv2 许可。如果您想保持 OpenJDK 的开源性,请使用由 GPLv2 许可的版本。

  2. 将下载的文件解压缩到您喜欢的位置。让我们称它为C:/OpenJDK

  3. 通过导航到开始 | 运行,输入cmd,然后点击运行按钮来打开 Windows 命令行。

  4. 运行以下命令:

    C:\OpenJDK\bin\java.exe -version
    
    

    它将输出 Java 版本信息。输出应该看起来像这样:

    openjdk version 1.7.0
    OpenJDK Runtime Environment <build 1.7.0-b146>
    OpenJDK Client VM <build 21.0-b16, mixed mode>
    

恭喜!我们刚刚安装了 OpenJDK 官方二进制文件。

它是如何工作的...

参考实现是 OpenJDK 唯一可用的官方二进制构建版本。但它缺乏安全性,仅用于参考目的。它是一个需要解压缩才能使用的简单存档。

为了绕过这种不愉快的情况,并给 Windows 用户提供一个机会,无需从源代码构建即可将 OpenJDK 作为二进制文件安装,OpenJDK 的一位贡献者建立了一个完全非官方但非常有用的 OpenJDK 构建集,适用于各种平台。

此外,这个二进制构建版本与官方版本不同,它是开源的,并且使用 GPL 许可证。因此,我们甚至可以在一个完全开源的环境中使用它,而无需添加任何可能让我们陷入麻烦的专有组件。

小贴士

在那些非官方构建中,您也会找到一个 Mac 的安装程序。

还有更多...

虽然官方参考二进制文件已经过时,但有一个非官方项目提供了来自最新源代码的 OpenJDK 构建。

现在我们将从非官方构建中安装 OpenJDK 7:

  1. 前往github.com/alexkasko/openjdk-unofficial-builds

  2. 选择适合 Windows 的适当构建并下载它。

  3. 解压缩它并运行install.exe还有更多...

  4. 当出现前面的消息时,点击运行按钮。

  5. 仔细阅读并接受许可协议,然后点击下一步

  6. 在下一个窗口中选择安装路径。它默认指向您的家目录,所以请小心——这样的安装可能只有您自己才能访问。

  7. 如果目标目录不存在,让安装程序创建它。还有更多...

  8. 如果您只想将此 JDK 设置为系统所有用户的默认值,而不仅仅是您自己的,请仅检查前面的截图中的红色突出显示的复选框。如果您根本不需要此 JDK 为默认值,您可以取消勾选第四个框。

  9. 然后点击下一步按钮,等待安装完成。

  10. 然后最后一次点击下一步按钮。

  11. 然后点击完成

参考信息

虽然安装 OpenJDK 最简单的方法是解压缩二进制文件,手动或自动进行,但毫无疑问,使用源代码将在所有可能级别上给我们带来更多的灵活性。

想了解更多,请阅读以下章节:

  • 第二章,构建 OpenJDK 6 到 第四章,构建 OpenJDK 8,了解从源代码构建 OpenJDK

  • 第六章,使用其他虚拟机实现构建 IcedTea,以使用其他虚拟机构建 OpenJDK

  • 第十三章,构建自动化,以使用未来技术,这些技术将相当长一段时间内无法以二进制形式获得

在 Windows 上配置 OpenJDK

虽然初始配置对于大多数任务已经足够,但仍可能需要做一些配置。在 OpenJDK 的情况下,这是通过设置系统变量来完成的。在这里,我们将仅讨论在手动解包 JDK 时经常出现的情况——如何将其设置为默认值。

准备工作

为了遵循这个食谱,我们需要在我们的 Windows 系统上安装一个 OpenJDK 实例。Windows 7 或 Windows 8 将是最佳选择,因为 Windows XP 已经被微软正式停止支持。

如何操作...

首先,我们需要将我们的 OpenJDK 实现作为默认 Java 实例安装。这对于开发通常是必要的:

  1. 为了做到这一点,我们将转到开始 | 控制面板 | 系统 | 高级 | 环境变量 | 用户变量(或系统变量用于系统范围的配置)并将 Java 可执行文件的路径添加到PATH系统变量中,如图所示:如何操作...

    小贴士

    如果存在其他路径指向其他 Java 可执行文件,我们可能也需要删除它们,但最好还是记住它们,因为我们可能需要恢复我们旧的默认 Java 设置。

  2. 如果我们从非官方构建安装 OpenJDK,可能根本不需要更改PATH变量。

  3. 为了验证我们新配置的变量,我们将转到命令提示符并输入以下内容:

    java -version
    
  4. 预期的输出是我们新安装的构建版本。

它是如何工作的…

为了将新安装的 OpenJDK 实例设置为默认 JDK,我们需要更改系统变量。更改后,我们的 Java 可执行文件将对系统可见。

还有更多...

同样的程序用于设置CLASSPATH变量。这并不是非常必要,如果你使用其他库如 GNU classpath,你可能已经知道了。

在 Linux 上安装 OpenJDK

Linux 操作系统允许进行许多内部调整,以及修改系统的源代码。它也被称为一个复杂的操作系统,并非所有发行版都易于使用。有很多人在使用它,它是开源的,例如 OpenJDK 本身。安装过程因选择的发行版而异,我们将介绍三种最常用的包管理器的安装过程,以及适用于几乎所有 x86 Linux 分发的安装过程。

准备工作

要遵循这个食谱,你需要一个已安装的 Linux 系统。如果它的内核版本是 2.6 或更高,那就更好了,尽管据报道 OpenJDK 在 2.4 内核上也能工作。此外,如果你有.deb.rpm.ebuild包管理器,安装任何包的推荐方法是使用这些来安装。

如何做...

当涉及到各种软件包的安装时,这个过程取决于我们的 Linux 发行版。

对于基于 Debian 的发行版:

  1. 打开终端并输入:

    apt-get install openjdk-7-jdk
    

    提示

    我们应该有 root 权限或使用sudo来访问系统文件。

  2. 这将自动触发安装。如果我们收到一个错误消息,表明找不到包,我们应该在谷歌上搜索适合我们发行版的 OpenJDK 包的适当名称。

对于基于 RPM 的发行版,我们首先需要搜索包名,因为不同发行版的包名各不相同,如下所示:

yum search openjdk

你将看到如下输出:

java-1.6.0-openjdk.x86_64 : OpenJDK Runtime Environment
java-1.6.0-openjdk-demo.x86_64 : OpenJDK Demos
java-1.6.0-openjdk-devel.x86_64 : OpenJDK Development Environment
java-1.6.0-openjdk-javadoc.x86_64 : OpenJDK API Documentation
java-1.6.0-openjdk-src.x86_64 : OpenJDK Source Bundle
java-1.7.0-openjdk.x86_64 : OpenJDK Runtime Environment
java-1.7.0-openjdk-demo.x86_64 : OpenJDK Demos
java-1.7.0-openjdk-devel.x86_64 : OpenJDK Development Environment
java-1.7.0-openjdk-javadoc.noarch : OpenJDK API Documentation
java-1.7.0-openjdk-src.x86_64 : OpenJDK Source Bundle

你可以安装所有具有所需版本的包。然后,我们将运行另一个命令,使用我们刚刚找到的包名:

yum install <a found package name>

这也将触发自动下载和安装。

如果我们有一个基于 Gentoo 的发行版,只需输入以下内容:

emerge openjdk-1.7

这将根据你的发行版,解压并安装一个二进制包,或者更有可能的是,自动从源代码构建这个包。

还有更多...

除了推荐的方式之外,还有一个通用的安装程序。虽然它可能会对你的操作系统造成一些损害,但这个程序相当简单,所以除非你真的知道你在做什么,否则不要使用它:

  1. 这是解压 OpenJDK 系统并自行安装的方法。要获取构建版本,我们将再次参考非官方构建页面,github.com/alexkasko/openjdk-unofficial-builds

  2. 然后将下载的包解压到一个文件夹中,并从该文件夹运行以下命令:

    java -jar ./install.jar
    
  3. 将出现一个 GUI 安装器窗口。阅读并接受许可协议,选择目录,并允许创建 OpenJDK,如果它不存在,如下所示:还有更多...

  4. 如果你想将此安装设置为默认安装,请勾选前面的复选框。然后点击下一步按钮。

  5. 等待安装完成,并最后一次点击下一步

  6. 然后点击完成

在 Linux 上配置 OpenJDK

Linux 配置配置文件与 Windows 的不同,因为那些系统在处理资源以及硬件的方式上略有不同。在这里,我们将简要解释这些差异以及克服它们的方法。此外,不同的 Linux 发行版,像往常一样,有不同的处理配置的方式。我们将尝试介绍最显著的几种。

准备中

要遵循此食谱,您需要在 Linux 系统上安装一个 OpenJDK 实例。debrpmebuild发行版非常适合我们,尽管我们还将看到一种通用的 Linux 配置方法。

此外,我们还需要确保 bash 启动文件正确安装。

小贴士

在大多数 Linux 发行版中,不建议使用通用方式配置需要 root 访问权限的任何内容,这种方法的成果往往会在每次更新中消失。通常,有发行版推荐的指南,其中描述了问题解决方案。

如何做到这一点…

首先,让我们检查您的 bash 启动文件是否已安装。最简单的方法是使用它们配置 OpenJDK。它们是系统范围的且易于使用,尽管它们的用法有一些缺点,例如更新冲突:

  1. 在您的终端中输入以下行:

    cat /etc/profile
    

    如果文件存在并且包含某种 shell 脚本,那么你的 bash 启动文件的设置可能正确。如果不正确,请按照您的发行版说明进行设置。

  2. 然后添加/etc/profile.d/openjdk.sh文件。

  3. 为了配置不同的事情,编写以下内容:

    To set JAVA_HOME
    JAVA_HOME=<youJDK installation directory>
    export JAVA_HOME
    
    To append JAVA_HOME to PATH
    pathappend $JAVA_HOME/bin PATH
    
    To adjust CLASSPATH directory
    AUTO_CLASSPATH_DIR=<classpath dir>
    pathprepend . CLASSPATH
    
    for dir in `find ${AUTO_CLASSPATH_DIR} -type d 2>/dev/null`; do
        pathappend $dir CLASSPATH
    done
    
    for jar in `find ${AUTO_CLASSPATH_DIR} -name "*.jar" 2>/dev/null`; do
        pathappend $jar CLASSPATH
    done
    
    export CLASSPATH
    

    小贴士

    应尽可能避免使用CLASSPATH环境变量。它通常由主要配置为 JDK 1.2 及以下版本的旧版 Java 应用程序使用。使用javajavac命令的-classpath选项代替。

上述代码相当简单——它只是将所有 JAR 文件追加到类路径中。

它是如何工作的…

此脚本在 shell 初始化期间调用,因此每次您执行 shell 初始化时,这些变量都会被导出。因此,这些变量是系统范围的,所以在玩弄它们时要小心,因为如果在文件中犯了一些错误,它们可能会永久性地导致您的 Java 失败。

还有更多…

在 Linux 上,您可以使用tree命令查看已安装 OpenJDK 的目录结构。

要这样做,安装tree包(如果可能,请使用您的发行版文档)并输入:

tree -L 1 <path-to-openjdk> -lah

您将看到以下内容:

/usr/lib/jvm/java-7-openjdk-amd64
├── [  22]  ASSEMBLY_EXCEPTION -> jre/ASSEMBLY_EXCEPTION
├── [4.0K]  bin
├── [  41]  docs -> ../../../share/doc/openjdk-7-jre-headless
├── [4.0K]  include
├── [4.0K]  jre
├── [4.0K]  lib
├── [4.0K]  man
├── [  20]  src.zip -> ../openjdk-7/src.zip
└── [  22]  THIRD_PARTY_README -> jre/THIRD_PARTY_README

这是第一级目录结构,其中:

  • ASSEMBLY_EXCEPTION是关于许可的,THIRD_PARTY_README也是如此。

  • docs文件夹用于各种 OpenJDK 文档(变更日志、版权、作者等)。

  • include目录用于包含路径(例如,对于JNI)。

  • jre目录是 Java 运行时放置的地方。

  • lib目录是放置各种 OpenJDK 库的地方(例如 Jigsaw 或 CORBA 支持;主要是由所有 OpenJDK 代码组成)。

  • man命令是 OpenJDK 的手册页条目。它包含 OpenJDK 类、javadocs和其他手册条目。在极不可能发生互联网连接丢失的情况下,它可能非常有用。

在 OpenJDK 组和项目中导航

OpenJDK 不是一个庞大的项目。它由大量子项目组成,由相对较小的开发者团队开发。我们将查看它们,并了解 OpenJDK 内部的工作原理。

准备工作

要遵循此食谱,您需要一个已安装的 OpenJDK 实例和稳定的互联网连接。这个食谱更多的是为了对过程的初步理解,而不是实际应用,所以如果您熟悉这些事情,请不要犹豫,可以完全跳过这个食谱。

如何操作...

我们将看到 OpenJDK 由什么组成:

  1. 访问openjdk.java.net/

  2. 在右侧列中,还有关于组和项目的概述。

  3. 我们将从中选择一个来完成这个过程。

    小贴士

    该过程将在第八章 Hacking OpenJDK中详细描述。

  4. 让选定的项目为JDK9

  5. 前往openjdk.java.net/projects/jdk9/JDK9项目页面。

没有什么可看的,因为只有基本的入职信息。大部分的项目业务都在问题跟踪器中。

在官方网站上阅读有关项目的信息后,我们将前往 JDK JIRA 查看这里发生了什么。我们将访问bugs.openjdk.java.net/browse/JDK/fixforversion/14949的 JIRA 中的JDK9部分。

在这里,我们可以看到与 JDK9 直接相关的问题,并了解过程是如何进行的。

它是如何工作的...

组是一组可能在不同项目中工作但属于一个大范围的开发者。开发者参与选择的项目,项目由组赞助。

小贴士

要参与一个组并成为贡献者,请遵循openjdk.java.net/contribute/中的说明。

有四种主要的项目类型:

  • 功能

  • 改进

  • 替换

  • 可移植性

例如,JDK9 项目是一个特色项目。图形光栅化项目是一个替换项目,而 Swing 组是一个专注于 Swing 改进的整个组。

各种端口的项目显然是可移植性的。

参见

  • 请参阅第八章,Hacking OpenJDK,第十一章,故障排除,以及第十三章,构建自动化——它们将非常适合您

  • 在第四章,构建 OpenJDK 8中查看尾巴。

第二章:构建 OpenJDK 6

在本章中,我们将涵盖以下内容:

  • 准备 CA 证书

  • 在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 6

  • 为最兼容的 Linux 构建设置最小构建环境

  • 为 Windows 构建安装 Cygwin

  • 在 Windows 上为 OpenJDK 6 构建 32 位 FreeType 库

  • 在 Windows 上为 OpenJDK 6 构建 64 位 FreeType 库

  • 在 Windows 7 SP1 上构建 32 位 OpenJDK 6

  • 在 Windows 7 x64 SP1 上构建 64 位 OpenJDK 6

简介

OpenJDK 6 是 Java 平台标准版 6 的免费和开源实现。目前,该项目由社区积极维护,由红帽公司担任主导角色。

在所有 Java 平台版本中,Java 6 的寿命最长。其参考实现 Sun Java 6 于 2006 年 12 月发布,而 Java 平台下一个版本的参考实现 OpenJDK 7 直到 2011 年 7 月才推出。在这 5 年期间,许多应用程序都是基于这个平台版本构建的。

此外,在这些年间,参考实现的作者 Sun Microsystems 被甲骨文公司收购,因此该产品被更名为 Oracle Java。2013 年 2 月,甲骨文公司结束了 Oracle Java 6 的公共支持,但这并不意味着 Java 6 的终结:红帽公司担任 OpenJDK 6 的主导角色,现在它继续定期发布新版本。

OpenJDK 6 代码库与 Oracle 和 Sun Java 6 以及 OpenJDK 7 代码库都大相径庭。它最初是 OpenJDK 7 构建 20 的一个分支,第一个通过 Java 兼容性工具包测试套件的版本在 OpenJDK 7 正式发布之前就已经发布。版本方案与 Oracle Java 6 的版本方案不同。每个版本都没有更新号,只有构建号,例如 b01 和 b02。在撰写本书时,2014 年 1 月的发布版本为 b30。

您可以在以下图表中看到 OpenJDK 6 的家族树:

简介

图表参考:blogs.oracle.com/darcy/entry/openjdk_6_genealogy

OpenJDK 6 支持 Linux、Windows 和 Solaris 操作系统。以下将仅讨论 Windows 和 Linux 版本。对于 Linux 和 Windows 操作系统,支持 x86 和 x86_64 架构。为了符合 OpenJDK 术语,将使用i586术语表示 x86 架构,而amd64将用于 x86_64 架构。OpenJDK 6 不支持交叉编译,因此必须使用 i586 操作系统来构建 i586 版本,对于 amd64 也是如此。对于 Linux 版本,这两个架构的构建过程几乎相同,但对于 Windows 版本则大相径庭。

准备 CA 证书

公钥加密在互联网上被广泛使用。当网络浏览器打开一个安全网站时,它会将服务器端证书与网站域名进行核对。为了执行此类检查,所有网络浏览器都有一个可能用于签署网站服务器端证书的证书颁发机构CA)证书列表。此类检查可能被禁用,但它们是安全网络浏览、客户端银行等必要的一部分。

当 Java 程序(例如,从安全站点下载文件)使用网站访问时,前面的示例中的浏览器等程序应检查网站证书。此类检查通常由底层 SSL API 实现执行,并且对于浏览器,CA 证书列表必须对 OpenJDK 运行时可用。

此列表以Java 密钥库JKS)格式存储在openjdk_directory/jre/security/cacerts文件中。在官方 OpenJDK 6 的 tar 包中,cacerts文件不包含任何证书。如果使用这样的空文件进行运行时访问安全网站,将会抛出一个难以理解的异常。

以下代码片段将导致异常,其根本原因如下:

new URL("https://github.com/").openStream().close();
Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
    at java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:...
        at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
        at java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters....
        at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:73)
        ... 47 more

准备工作

为了防止此类异常,应准备一个合适的cacerts文件。它可以在 OpenJDK 构建过程中使用,或者稍后添加到jre/security目录中。应获取 CA 证书列表并将其转换为 JKS 格式。为了下载和转换 CA 列表,我们需要一个安装了cURLkeytool实用工具的较新版本的 Ubuntu(或类似基于 Linux 的)操作系统。您还需要catawkcsplit标准实用工具;这些工具应作为coreutils包的一部分已经安装。

如何操作...

以下步骤将帮助我们准备 CA 证书:

  1. 安装cURL实用工具:

    sudo apt-get install curl
    
    
  2. keytool实用工具作为预构建 OpenJDK 包的一部分进行安装:

    sudo apt-get install openjdk-7-jdk
    
    
  3. 从 cURL 库网站下载 Firefox 网络浏览器使用的 CA 列表,预先转换为 PEM 格式到cacert.pem文件:

    curl -L http://curl.haxx.se/ca/cacert.pem -o cacert.pem
    
    
  4. 使用cert_前缀将cacert.pem文件分割成多个文件。每个文件将包含一个 CA 证书:

    cat cacert.pem | awk '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/{ print $0; }' > cacert-clean.pem
    csplit -k -f cert_ cacert-clean.pem "/-----BEGIN CERTIFICATE-----/" {*}
    
    
  5. 创建一个 JKS 密钥库,并使用keytool将所有 CA 证书加载到其中:

    for CERT_FILE in cert_*; do
     ALIAS=$(basename ${CERT_FILE})
     echo yes | keytool -import -alias ${ALIAS} -keystore cacerts -storepass 'changeit' -file ${CERT_FILE} || :
    done
    
    
  6. 检查cacerts文件的内容:

    keytool -list -keystore cacerts -storepass 'changeit'
    
    

现在cacerts文件已准备好使用。

它是如何工作的...

Firefox 网络浏览器使用的 CA 证书列表作为 Mozilla 开源安全库的一部分免费提供,该库称为 NSS。此列表以文本格式在mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt处可用。我们使用了相同的文件,但预先将其转换为 PEM 格式,该格式可在 cURL 网站上找到。

keytool 工具理解 PEM 格式的证书,但它只能逐个加载证书,因此需要事先将大的 PEM 文件分割。此外,keytool 工具对 PEM 文件有严格的要求:不允许在 /-----BEGIN CERTIFICATE-----//-----END CERTIFICATE-----/ 字符串之前或之后有文本内容。使用 awk 工具来删除不需要的前缀和后缀。

然后使用 csplit 工具使用 /-----BEGIN CERTIFICATE-----/ 分隔符来分割文件。

接下来,将分割的文件逐个加载到密钥库中。密钥库是在第一次加载证书时创建的。

changeit 密码用于此密钥库,这可能是一个相当不安全的密码选择。然而,对于 cacerts 文件来说,这并不重要,因为 CA 证书只包含公钥,不需要隐藏在密码后面。

还有更多...

此配方的大部分内容并非特定于 Ubuntu 操作系统;任何类 Unix 环境(例如,Windows 上的 Cygwin)都足够使用。

此配方中的 Bash 脚本可以被任何其他脚本语言(如 Python 或 PowerShell)替换。

可以使用除 Firefox 中的 CA 列表之外的任何其他 CA 证书集。可能需要删除某些 CA 证书,例如用户不信任的特定 CA 证书,或者可能添加一些额外的 CA 证书,例如访问公司内部网络资源的内部企业 CA 证书。

参见

在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 6

OpenJDK 的构建过程严重依赖于类 Unix 开发工具。基于 Linux 的操作系统通常对这些工具提供顶级支持,因此,在 Linux 上构建 OpenJDK 可能比在 Windows 上简单。对于像 Fedora 或 Ubuntu 这样的主要发行版,构建工具链和所有依赖项已经作为软件包包含在发行版中,可以轻松安装。

选择 Ubuntu 12.04 LTS 作为本书的操作系统,因为它是最受欢迎的 Linux 发行版之一。对于运行其他操作系统的 Ubuntu 12.04 的读者,可以在网上找到用于最流行的虚拟化工具(如 Oracle VirtualBox 或 VMware)的虚拟镜像。

要为 i586 和 amd64 架构构建二进制文件,应使用相应的 Ubuntu 版本。对于这两个架构,构建说明完全相同,因此在此配方中不会进一步提及。

准备工作

对于此配方,我们需要一个干净的 Ubuntu 12.04(服务器或桌面版本)运行。

如何做...

以下步骤将帮助我们构建 OpenJDK:

  1. 安装 OpenJDK 6 的预打包二进制文件:

    sudo apt-get install openjdk-6-jdk
    
    
  2. 安装 GCC 工具链和构建依赖项:

    sudo apt-get build-dep openjdk-6
    sudo apt-get install libmotif-dev
    
    
  3. 下载并解压官方 OpenJDK 6 构建版本 30 的 tarball:

    mkdir openjdk-6-src-b30-21_jan_2014
    cd openjdk-6-src-b30-21_jan_2014
    wget https://java.net/projects/openjdk6/downloads/download/openjdk-6-src-b30-21_jan_2014.tar.xz
    tar xJf openjdk-6-src-b30-21_jan_2014.tar.xz
    rm openjdk-6-src-b30-21_jan_2014.tar.xz
    
    
  4. 使用您喜欢的文本编辑器打开 jdk/make/javax/sound/jsoundalsa/Makefile 文件,并将第 68 行从 LDFLAGS += -lasound 更改为:

    OTHER_LDLIBS += -lasound
    
    
  5. 创建一个名为 buildenv.sh 的新文本文件,包含以下环境设置:

    export LD_LIBRARY_PATH=
    export CLASSPATH=
    export JAVA_HOME=
    export LANG=C
    export ALT_BOOTDIR=/usr/lib/jvm/java-6-openjdk
    
  6. 将环境导入当前 shell 会话(注意它前面有一个点和空格):

    . buildenv.sh
    
    
  7. openjdk-6-src-b30-21_jan_2014 目录开始构建过程:

    make 2>&1 | tee make.log
    
    
  8. 等待构建完成,然后尝试运行新构建的二进制文件:

    cd build/linux-amd64/j2sdk-image/
    ./bin/java –version
    openjdk version "1.6.0-internal"
    OpenJDK Runtime Environment (build 1.6.0-internal-ubuntu_22_jan_2014_13_12-b00)
    OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode)
    
    

它是如何工作的...

预打包的二进制文件是必需的,因为一些构建步骤是使用外部 Java 运行时运行的。

build-dep 命令用于安装构建指定软件包所需的所有依赖项。由于 Ubuntu 打包的 OpenJDK 6 与官方 OpenJDK 6 非常接近,此命令将安装几乎所有所需的依赖项。

libmotif-dev 软件包是唯一需要的额外软件包。它包含 Motif GUI 工具包的头文件。

需要对 jdk/make/javax/sound/jsoundalsa/Makefile 文件进行调整,以符合 Ubuntu 12.04 GCC 4.6 工具链。对于原始 GCC 4.2 工具链,可能不需要此调整,并且对于更近期的 OpenJDK 6 官方源代码,可能也不需要,因为此更改将被包含在 OpenJDK 6 上游。

tee 命令用于同时将输出写入日志文件和屏幕。

在 amd64 平台上成功构建后,JDK 文件将放置在 build/linux-amd64/j2sdk-image,JRE 文件将放置在 build/linux-amd64/j2re-image。在 i586 平台上,将使用 build/linux-i586 路径。

更多...

Javadoc 生成过程耗时很多,是构建过程中最消耗内存的步骤。可以通过设置一个额外的环境变量来跳过:

export NO_DOCS=true

此构建已生成一个里程碑标签和构建号 b00。预定义的构建号和里程碑可以通过额外的环境变量设置:

export MILESTONE=ubuntu-build
export BUILD_NUMBER=b30

在构建过程中可以使用额外的环境变量提供 cacerts 文件:

export ALT_CACERTS_FILE=path/to/cacerts

对于 amd64 构建,由 ALT_BOOTDIR 变量提供的预安装 Java 可以是 amd64 或 i586 构建。i586 二进制文件消耗更少的内存,并且可以在硬件有限的情况下用于 amd64 构建。

OpenJDK 6 的构建过程实际上是单线程的;对于此版本,不支持并行构建(使用 make -j N)。

参见

设置最兼容的 Linux 构建的最小构建环境

Linux 上的 OpenJDK 6 是为当时可用的 GCC 工具链开发的,并且进行了过度的测试。现代 Linux 发行版有更新的工具链,例如,Ubuntu 12.04 有 GCC 4.6。更新的工具链可能有更先进的优化,并提供略微更快的代码,但较旧的工具链应该对 OpenJDK 更稳定。

Oracle 发布了 OpenJDK 6 的最小构建环境描述。它说:

"使用 MBE 构建将生成与同一基础操作系统和硬件架构的更多变体兼容的位,并且可以安装和正确运行。"

我们将在以下最小构建环境平台之一上构建 OpenJDK 6——Debian Linux 5.0 Lenny。对于 i586 和 amd64 架构,构建说明完全相同,因此在此配方中不再提及。

构建步骤与之前的配方类似,所以我们将专注于不同的步骤。

准备工作

在此配方中,我们需要运行 Debian Lenny。安装镜像可以从 Debian CDImage 存档下载。Lenny 可能与一些较新笔记本电脑上的某些硬件不兼容,但应该可以在 Oracle VirtualBox 或 VMware 等虚拟化工具上正常运行。

如何操作...

以下步骤将帮助我们设置最小构建环境:

  1. 将存档仓库添加到 /etc/apt/sources.list 文件:

    deb http://archive.kernel.org/debian-archive/debian lenny main contrib non-free
    deb-src http://archive.kernel.org/debian-archive/debian lenny main contrib non-free
    
    
  2. 安装 OpenJDK 6 二进制文件和构建依赖项:

    sudo apt-get install openjdk-6-jdk
    sudo apt-get build-dep openjdk-6
    sudo apt-get install libmotif-dev
    
    
  3. 下载并解压 Apache Ant:

    wget http://archive.apache.org/dist/ant/binaries/apache-ant-1.8.4-bin.tar.gz
    tar xzf apache-ant-1.8.4-bin.tar.gz
    
    
  4. 创建一个包含环境设置的新的文本文件 buildenv.sh,并将其内容导入当前 bash shell:

    export LD_LIBRARY_PATH=
    export CLASSPATH=
    export JAVA_HOME=
    export LANG=C
    export ALT_BOOTDIR=/usr/lib/jvm/java-6-openjdk
    export ANT_HOME=path/to/apache-ant-1.8.4/
    export PATH=$ANT_HOME/bin:$PATH
    . buildenv.sh
    
    
  5. 下载并解压官方 OpenJDK 6 构建版本 30 的 tarball:

    mkdir openjdk-6-src-b30-21_jan_2014
    cd openjdk-6-src-b30-21_jan_2014
    wget https://java.net/projects/openjdk6/downloads/download/openjdk-6-src-b30-21_jan_2014.tar.gz
    tar xzf openjdk-6-src-b30-21_jan_2014.tar.gz
    rm openjdk-6-src-b30-21_jan_2014.tar.gz
    
    
  6. openjdk-6-src-b30-21_jan_2014 目录开始构建过程:

    make 2>&1 | tee make.log
    
    
  7. 等待构建完成。

它是如何工作的...

需要设置存档仓库,因为官方 Lenny 仓库已不再可用。

需要一个更新的 Apache Ant 版本,因为 Lenny 中捆绑的 1.7.0 版本太旧,OpenJDK 6 需要 Ant 1.7.1 或更高版本。

还有更多...

其他较老的 Linux 发行版,如 Ubuntu 8.04 或 Fedora 9,也可以用作最小构建环境。

参见

安装 Cygwin for Windows 版本

与大多数其他跨平台开源项目一样,OpenJDK 使用类似 Unix 的工具进行构建过程。例如 gmakeawkcpio 在 Unix 和 Linux 世界中无处不在,几乎可以在任何类似 Unix 的平台上找到。然而,这些工具基于 Unix 进程行为,当我们想在 Windows 上运行它们时,这会成为一个问题。

Cygwin 是由 Red Hat, Inc. 开发的一套免费和开源工具,为 Windows 提供对类似 Unix 环境的有限支持。在 Windows 上构建 OpenJDK 时是必需的。

OpenJDK 需要 Cygwin i586 来进行 i586 和 amd64 构建。对于 Windows 7 SP1 i586 和 Windows 7 SP1 amd64 的安装程序是相同的,所以我们在此配方中不会进一步提及架构。

准备工作

要安装并成功使用 Cygwin,我们需要一个干净的 Windows 7 SP1 安装,且没有运行防病毒软件。

如何操作...

以下步骤将帮助我们安装 Cygwin:

  1. cygwin.com/ 网站下载 setup-x86.exe

  2. 运行安装程序,并将 mirrors.kernel.org 作为软件包的镜像。

  3. 选择附加软件包:

    Devel/binutils
    Interpreters/m4
    Utils/cpio
    Archive/zip
    Archive/unzip
    System/procps
    
  4. 将安装程序安装到包含 ASCII 字母或数字路径的目录中,路径中不应包含空格。

它是如何工作的...

任何防病毒软件(或可能扫描其他应用程序内存的类似软件)都是禁止的,因为它可能会干扰 Cygwin。Cygwin 使用复杂的技术在 Windows 上模拟 Unix 的 fork 功能。这些技术在运行防病毒扫描器的情况下可能不起作用。

只有 Cygwin 上的 i586 版本支持构建 OpenJDK。amd64 Cygwin 可能或可能不支持。

Cygwin 社区在全球范围内托管了许多软件包的镜像。我们使用 www.kernel.org/ 镜像,因为它应该拥有最完整的预构建软件包集:

  • binutils 软件包是 ar 实用工具所必需的。这是一种特殊的归档器,可以用来创建和更新静态库

  • m4 软件包是用于与经典 Unix 宏处理器脚本一起工作的必需品

  • cpio 实用工具被用作归档器,因为在某些环境中,它可能比更流行的 tar 实用工具快得多

  • procps 软件包对于免费实用工具是必需的,该工具用于获取有关空闲和已用内存的信息

还有更多...

OpenJDK 6 也可以使用商业 MKS 工具包构建。这种方法提供了更短的构建时间,但不支持 OpenJDK 8 及以上版本。

Cygwin 的安装可以从一个 Windows 系统复制到另一个系统而不需要安装。这对于在干净的 Windows 系统上构建可能很有用。除了常用的 Windows 二进制文件外,Cygwin 还使用特殊类型的符号链接文件,例如,bin/awk实际上指向bin/gawk.exe。如果将这些符号链接加载到或从版本控制系统(如 Git)中,它们可能会损坏。可以通过用具有符号链接名称的实际二进制文件的副本替换符号链接来解决这个问题:gawk.exeawk.exe等等。

相关阅读

在 Windows 上为 OpenJDK 6 构建 32 位 FreeType 库

现代软件中使用的多数字体都编码为矢量格式,以支持适当的缩放。矢量字体有多个标准,例如,唐纳德·E·克努特教授的 Metafont,Adobe 的 Type1,Apple 和 Microsoft 的 TrueType,以及 Adobe 和 Microsoft 的 OpenType。

矢量字体的光栅化是一个非常复杂的过程,大多数桌面软件(如网页浏览器或文本处理器)都使用第三方库来处理字体。

Sun Microsystems 为 Sun Java 实现许可了一个第三方闭源字体库。这个库的源代码不能与 OpenJDK 的初始版本一起发布。在 OpenJDK 早期,启动了字体缩放器替换项目,以采用开源字体库。

FreeType 是一个免费的开源(在宽松许可下)字体光栅化库。它在开源桌面软件中得到广泛应用。OpenJDK 团队选择了 FreeType 作为闭源字体库的替代品,现在在所有支持的平台上使用。Windows 上的 OpenJDK 构建需要预构建的静态和动态 FreeType 库。

准备工作

对于这个食谱,我们应该有一个运行 Windows 7 SP1 i586 的系统。

如何操作...

以下步骤将帮助我们构建 FreeType:

  1. 将 Visual Studio.NET 2003 安装到默认安装路径。只需要先决条件和 Visual C++组件。

  2. freetype.org/下载 FreeType 2.5.2 源代码 tar 包并解压。

  3. 打开文件include\config\ftoption.h并取消注释:

    #define FT_CONFIG_OPTION_SUBPIXEL_RENDERING
    
  4. 将行/* #define FT_EXPORT(x) extern x *//* #define FT_EXPORT_DEF(x) x */更改为:

    #define FT_EXPORT(x) __declspec(dllexport) x
    #define FT_EXPORT_DEF(x) __declspec(dllexport) x
    
  5. 在 Visual Studio.NET 2003 中打开项目文件builds\windows\visualc\freetype.dsw

  6. Solution Configuration 更改为 Release Multithreaded 并构建解决方案。freetype252MT.lib 文件将被放置在 objs 目录中。

  7. 重命名文件 freetype.lib 并将其保存以用于 OpenJDK 构建。

  8. 项目属性 中将 配置类型 更改为 动态库 (.dll) 并构建解决方案。freetype.dllfreetype.exp 文件将被放置在 objs\release_mt 目录中。

在 OpenJDK 构建过程中,带有三个结果文件的目录将由 ALT_FREETYPE_LIB_PATH 环境变量表示。

它是如何工作的...

FT_CONFIG_OPTION_SUBPIXEL_RENDERING 宏在 FreeType 实现中启用亚像素渲染功能。

应该根据当前平台的调用约定调整 FT_EXPORTFT_EXPORT_DEF 宏。我们将它们更改为使用 Windows 特定的调用约定。

FreeType 没有为 Visual Studio.NET 2003 预定义项目文件。相反,我们正在使用为 Visual Studio 6 创建的项目文件。

参见

在 Windows 上为 OpenJDK 6 构建 64 位 FreeType 库

Windows amd64 的 FreeType 构建 与 i586 构建 类似,但配置更为复杂。与 i586 版本不同,amd64 库可以使用 Microsoft Visual Studio 2005 Express Edition 和 Windows Server 2003 SP1 平台 SDK 中的免费工具进行构建。

准备工作

对于这个配方,我们需要运行 Windows 7 SP1 amd64。

如何操作...

以下步骤将帮助我们构建 FreeType:

  1. www.microsoft.com/en-in/default.aspx 下载 Visual Studio 2005 Express Edition 并将其安装到默认位置。关于 Windows 7 兼容性问题的消息可以忽略。

  2. 安装 VS80sp1-KB926748-X86-INTLVS80sp1-KB932232-X86-ENU Visual Studio 更新。

  3. www.microsoft.com/en-in/default.aspx 下载 Windows Server 2003 SP1 平台 SDK 并使用默认安装路径进行安装。构建需要 AMD-64 组件。

  4. 执行上一个配方中的步骤 2、3 和 4 以下载和调整 FreeType 源代码。

  5. 开始菜单进入Microsoft Platform SDK for Windows Server 2003 SP1 | 打开构建环境窗口 | Windows Server 2003 64 位构建环境 | 设置 Win Svr 2003 x64 构建环境 (零售)

  6. 从出现的 cmd.exe 窗口中运行(管理员权限警告可以忽略):

    >"C:\Program Files (x86)\Microsoft Visual Studio 8\Common7\IDE\VCExpress.exe"
    
    
  7. 在主菜单中导航到工具 | 选项 | 项目和解决方案 | VC++ 目录,并调整以下目录:

    Executable files:
    
    C:\Program Files\Microsoft Platform SDK\Bin\win64\x86\AMD64
    C:\Program Files\Microsoft Platform SDK\Bin
    C:\Windows\System32
    
    Include files:
    C:\Program Files\Microsoft Platform SDK\Include
    C:\Program Files\Microsoft Platform SDK\Include\crt
    
    Library files:
    C:\Program Files\Microsoft Platform SDK\Lib\AMD64
    
  8. 打开位于此路径的 FreeType 解决方案freetype-2.5.2\builds\windows\vc2005\freetype.sln

  9. 在菜单中,导航到项目 | 属性 | 配置属性 | 配置管理器,并在活动解决方案平台下选择<新建 ...>

  10. 输入new platform x64,并从从以下设置复制中选择Win32

  11. 配置管理器表单中,导航到活动解决方案配置 | LIB 发布多线程,然后转到活动解决方案平台 | x64

  12. 在同一表单中,在 FreeType 项目的网格中,导航到配置 | 发布多线程,并在平台菜单下保留Win32值。

  13. 配置属性菜单中,检查配置类型是否设置为静态库 (.lib)。

  14. 导航到配置属性 | C/C++ | 预处理器,并将处理器定义字符串中的WIN32值更改为WIN64

  15. 构建下的构建解决方案运行,freetype252MT.lib库将被放置到freetype-2.5.2\objs\win32\vc2005目录中。将其重命名为freetype.lib,并保存以备后用。

  16. 配置属性菜单中,将配置类型更改为动态库 (.dll)并选择应用

  17. 导航到配置属性 | 链接器 | 输入,并将bufferoverflowU.lib放入附加依赖项字段。

  18. 导航到配置属性 | 链接器 | 命令行,并将/MACHINE:AMD64放入附加选项字段。

  19. 清理并构建解决方案。目标库freetype.dllfreetype.exp将被放置到freetype-2.5.2\objs\release_mt目录中。

在 OpenJDK 构建期间,带有三个结果文件的目录将由ALT_FREETYPE_LIB_PATH环境变量表示。

它是如何工作的...

Visual Studio 2005 Express 不支持 amd64 架构,因此使用 Windows SDK 中的 amd64 编译器的设置占据了此食谱的大部分。

参见

  • 在 Windows 上为 OpenJDK 6 构建 32 位 FreeType 库的食谱

  • FreeType 官方网站freetype.org/

在 Windows 7 SP1 上构建 32 位 OpenJDK 6

Windows 构建比 Linux 构建要复杂得多。尽管如此,Windows 上也有 GCC 工具链。通过 MinGW 项目,OpenJDK 使用来自 Visual Studio 和 Windows SDK 的官方 Microsoft 编译器。这带来了很多复杂性,因为 Microsoft 工具链与 Cygwin 提供的类 Unix 环境不兼容。

需要 Visual Studio.NET 2003 来构建 i586 二进制文件。Windows i586 上的 OpenJDK 6 是本书中唯一不能使用免费工具构建的版本。选择 VS 2003 用于 OpenJDK 6 是因为它支持 Windows 2000,这在 Sun Java 6 开发时期至关重要。

准备工作

对于这个配方,我们应该有一个没有安装任何防病毒软件的 Windows 7 SP1 i586 运行。不允许使用防病毒软件,因为它可能会干扰 Cygwin 运行时。

如何操作...

以下步骤将帮助我们构建 OpenJDK 6:

  1. 将 Visual Studio.NET 2003 安装到默认安装路径。只需要先决条件和 Visual C++.NET 组件。

  2. 将预安装的 Cygwin 版本安装或复制到 c:\cygwin

  3. 将 Microsoft DirectX 9.0 SDK(夏季 2004)下载并安装到默认安装路径。请注意,此分发版不再可以从 www.microsoft.com/en-in/default.aspx 网站获取。我们可以从其他在线位置下载它,并检查文件详细信息:

    name: dxsdk_sum2004.exe
    size: 239008008 bytes
    sha1sum: 73d875b97591f48707c38ec0dbc63982ff45c661
    
  4. www.microsoft.com/en-in/default.aspx 下载平台软件开发工具包可再分发版:Windows 95、98 和 Me 系统的 Unicode 层,版本 1.1.3790.0,并将其安装到 c:\unicows 目录。

  5. openjdk-unofficial-builds GitHub 项目下载 unicows.lib,并将其放入 c:\unicows 目录。

  6. apache.org/ 网站下载 Apache Ant 版本 1.8.4 ZIP 分发版,并将其解压缩到 c:\ant 目录。

  7. www.cmake.org/ 网站下载 GNU make 工具的二进制文件,使用 www.cmake.org/files/cygwin/make.exe-cygwin1.7,将其重命名为 make.exe,并将其放入 c:\make 目录。

  8. 创建 c:\path_prepend 目录,并从 Cygwin 安装中复制 find.exesort.exe 文件。

  9. openjdk-unofficial-builds GitHub 项目(目录 6_32)下载预构建的 FreeType 库,并将二进制文件放入 c:\freetype 目录,将头文件放入 c:\freetype\include 目录。

  10. 将 OpenJDK 6 二进制文件或 Oracle Java 6 安装到 c:\jdk6

  11. java.net/projects/openjdk6/downloads 网页下载官方 OpenJDK 6 构建版本 30 的源代码 tarball,并将其解压缩到 c:\sources 目录(警告:tarball 不包括 root 目录)

  12. hotspot\make\windows\makefiles 目录下的 sa.make 文件中,将第 100 行从 SA_CFLAGS = $(SA_CFLAGS) /ZI 修改为:

    SA_CFLAGS = $(SA_CFLAGS) /Zi
    
  13. 创建一个 build.bat 批处理文件,并在其中写入以下环境变量设置:

    @echo off
    set LD_LIBRARY_PATH=
    set CLASSPATH=
    set JAVA_HOME=
    set PATH=c:/path_prepend;C:/WINDOWS/system32;C:/WINDOWS;C:/WINDOWS/System32/Wbem;c:/make;c:/cygwin/bin;c:/jdk6/bin;c:/ant/bin
    set ALT_BOOTDIR=c:/jdk6
    set ALT_FREETYPE_LIB_PATH=c:/freetype
    set ALT_FREETYPE_HEADERS_PATH=c:/freetype/include
    set ALT_UNICOWS_LIB_PATH=c:/unicows
    set ALT_UNICOWS_DLL_PATH=c:/unicows
    call "C:/Program Files/Microsoft Visual Studio .NET 2003/Common7/Tools/vsvars32.bat"
    bash
    echo Press any key to close window ...
    pause > nul
    
  14. 从 Windows 资源管理器运行 build.bat。应该会出现一个带有启动 bash 的 cmd.exe 窗口。

  15. 在 bash 命令提示符下运行以下命令:

    cd /cygdrive/c/sources
    chmod –R 777
    make > make.log 2>&1
    
  16. 启动另一个 Cygwin 控制台,并运行以下命令:

    cd /cygdrive/c/sources
    tail -f make.log
    
  17. 等待构建完成。

它是如何工作的...

Cygwin 安装在本章的 为 Windows 构建安装 Cygwin 配方中介绍。

为了简洁起见,这里使用磁盘 C 的根目录。通常,可以使用由 ASCII 字母或数字组成的任意路径,且不包含空格。

Visual Studio 2003 在 Windows 7 上不受官方支持。它警告可能存在兼容性问题,并且可能在 GUI 界面中出现故障,但它的命令行工具运行良好,并且对于 OpenJDK 构建不需要 GUI 界面。

也可以使用更新的 DirectX SDK 版本。

不同的 GNU make 版本在 Windows 上可能存在不同的问题。这个特定版本来自 cmake 项目,已在不同的 Windows 版本上进行了测试,并且运行良好。

此脚本使用来自 openjdk-unofficial-builds GitHub 项目的预构建 FreeType 2.4.10 库。可以使用 Visual Studio 2003 从源代码构建 FreeType。请参阅本章中的 在 Windows 上为 OpenJDK 6 构建 32 位 FreeType 库 脚本。

需要一个针对 HotSpot 服务性 Makefile 文件的补丁来绕过在最近的安全更新之后发现的 VS2003 错误。这个更改将被上传到官方 OpenJDK 6 源代码中,并且可能对于更新的源代码包不再需要。

在环境设置中,应特别注意 PATH 变量内容的顺序。sortfind Cygwin 实用程序位于 PATH 变量的开头,这样它们就不会被具有相同名称但功能不同的 Windows 实用程序所掩盖。make 实用程序位于 Cygwin 之前,这样就不会被可能包含在 Cygwin 安装中的另一个版本的 make 所掩盖。

chmod 777 命令是必需的,以修复可能导致构建后期阶段出现错误的 Cygwin 文件权限。

make 输出将被重定向到 make.log 文件。2>&1 语句确保 stdoutstderr 都将被重定向。

tail -f 命令允许我们在构建过程中监视 make.log 文件的内容。

在批处理文件末尾添加了 pause > nul 命令,以防止在运行时错误的情况下 cmd.exe 窗口消失。

更多...

为了构建最兼容的二进制文件,应使用相同的脚本,但应使用 Windows 2000 操作系统而不是 Windows 7。

在 Windows 2000 中,不需要使用 chmod 777 命令。

参见

在 Windows 7 x64 SP1 上构建 64 位 OpenJDK 6

Windows 7 上的 amd64 构建与 i586 构建类似,但有一些额外的复杂性。

Cygwin(至少是更常见的 i586 版本)在 amd64 Windows 上运行得要差得多。由于地址空间大小大得多,Cygwin 分叉技术运行得慢,且可靠性较低。

Visual Studio.NET 2003 不支持 amd64 架构,因此使用 Windows Server 2003 SP1 平台 SDK。

准备工作

对于这个配方,我们应该有一个没有安装任何防病毒软件的 Windows 7 SP1 i586 运行。不允许使用防病毒软件,因为它可能会干扰 Cygwin 运行时。

如何做...

以下步骤将帮助我们构建 OpenJDK:

  1. 从 Microsoft 网站下载 Windows Server 2003 SP1 平台 SDK,并使用默认安装路径进行安装。构建需要 AMD-64 和 MDAC(Microsoft 数据访问服务)组件。

  2. 按照从 在 Windows 7 SP1 上构建 32 位 OpenJDK 6 的配方中的步骤 2 到 11 进行(对于 FreeType 库使用 6_64 目录)。

  3. 前一个配方中的步骤 12 对于 amd64 构建不是必需的,可以跳过。

  4. 创建一个 build.bat 批处理文件,并写入以下环境变量设置:

    @echo off
    set LD_LIBRARY_PATH=
    set CLASSPATH=
    set JAVA_HOME=
    set PATH=C:/path_prepend;C:/WINDOWS/system32;C:/WINDOWS;C:/WINDOWS/System32/Wbem;C:/make;C:/cygwin/bin;C:/jdk6/bin;C:/ant/bin
    set ALT_BOOTDIR=c:/jdk6
    set ALT_FREETYPE_LIB_PATH=c:/freetype
    set ALT_FREETYPE_HEADERS_PATH=c:/freetype/include
    set ALT_UNICOWS_LIB_PATH=c:/unicows
    set ALT_UNICOWS_DLL_PATH=c:/unicows
    call "C:/Program Files/Microsoft Platform SDK"/SetEnv.cmd /X64 /RETAIL
    bash
    echo Press any key to close window ...
    pause > nul
    
  5. 按照前一个配方中的步骤 14 到 17 进行。

它是如何工作的...

大多数来自 i586 构建的说明也适用于 amd64 构建,除了那些特定于 VS2003 的。

修补 Makefile 以提高可服务性不是必需的,因为 Makefile 的这部分是针对 i586 构建的。

还有更多...

为了构建最兼容的二进制文件,应使用相同的配方,但应使用 Windows 2003 Server amd64 操作系统而不是 Windows 7。

在 Windows 2003 中,不需要 chmod 777 命令。

参见

  • 为 Windows 构建安装 Cygwin 的配方

  • 关于构建调整的信息,请参阅 在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 6 的配方

  • 在 Windows 上为 OpenJDK 6 构建 64 位 FreeType 库 的配方

  • 准备 CA 证书 的配方

  • OpenJDK 6 的官方构建说明,请参阅 hg.openjdk.java.net/jdk6/jdk6/raw-file/tip/README-builds.html

第三章. 构建 OpenJDK 7

在本章中,我们将介绍:

  • 在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 7

  • 在 Mac OS X 上构建 OpenJDK 7

  • 在 Windows 上构建 32 位 FreeType 库用于 OpenJDK 7

  • 在 Windows 上构建 64 位 FreeType 库用于 OpenJDK 7

  • 在 Windows 7 SP1 上构建 32 位 OpenJDK 7

  • 在 Windows 7 x64 SP1 上构建 64 位 OpenJDK 7

  • 准备用于 32 位和 64 位 Windows 构建的独立工具链

简介

OpenJDK 7 是 Java 平台标准版 7 的免费和开源实现。在撰写本书时,它是准备用于生产使用的最新 OpenJDK 版本。

最初,为 OpenJDK 7 计划了许多高级变更,例如模块化虚拟机和 lambda 表达式支持,但由于各种技术和组织原因,在 Sun Microsystems 被甲骨文公司收购后,大多数新特性都被推迟到 OpenJDK 的下一个版本。这加快了发布速度。它于 2011 年 7 月 28 日达到 通用可用 状态,并且是第一个作为 Java 平台参考实现的 OpenJDK 版本。

OpenJDK 7 的主要更新编号为更新 2、更新 4 和更新 6,在接下来的年份中发布。之后,版本编号发生了变化,下一个更新 40(在撰写本书时是最新版本)于 2013 年 9 月发布。下一个计划发布的更新 60 预计在 2014 年 5 月发布,而 OpenJDK 7 的生命周期将在 2015 年初的更新 80 中结束。

OpenJDK 7 的发布周期与 Oracle Java 的发布周期不同。Oracle Java 更新是为了常规的安全相关更改和 OpenJDK 更新。Oracle 的安全更改被传播到 OpenJDK 7(以及适用的情况下 OpenJDK 6),但 OpenJDK 并不是立即发布。相反,发布是在主要变更和累积安全变更上进行的,通常包含更新版本的 HotSpot 虚拟机。

OpenJDK 7 支持在 Linux、Windows、Mac OS X 和 Solaris 操作系统上运行。以下将进一步讨论 Windows、Linux 和 Mac OS X 版本。对于 Linux 和 Windows 操作系统,支持 x86 和 x86_64 架构。对于 Mac OS X,仅支持 x86_64。为了符合 OpenJDK 术语,将使用 i586 术语表示 x86 架构,而 amd64 将用于 x86_64。OpenJDK 7 不支持交叉编译,因此必须使用 i586 操作系统来构建 i586 版本,对于 amd64 也是如此。对于 Linux 版本,这两个架构的构建过程几乎相同,但对于 Windows 版本则差异很大。

在本章中,我们将使用来自官方 OpenJDK 7 更新 40 tarball 的源代码。

在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 7

此配方与第二章 构建 OpenJDK 6 on Ubuntu Linux 12.04 LTS 中的配方 Building OpenJDK 6 on Ubuntu Linux 12.04 LTS 相似。

OpenJDK 的构建过程严重依赖于类 Unix 开发工具。基于 Linux 的操作系统通常对这些工具提供顶级支持,因此,在 Linux(以及 Mac OS X)上构建 OpenJDK 可能比在 Windows 上简单。对于像 Fedora 或 Ubuntu 这样的主要发行版,构建工具链和所有依赖项已经作为软件包包含在发行版中,并且可以轻松安装。

选择 Ubuntu 12.04 LTS 作为本书的操作系统,因为它是最受欢迎的 Linux 发行版之一。对于运行其他操作系统的读者,可以在网上找到 Ubuntu 12.04 虚拟镜像,用于最流行的虚拟化工具,如 Oracle VirtualBox 或 VMware。

要为 i586 和 amd64 架构构建二进制文件,应使用相应的 Ubuntu 版本。对于这两个架构,构建说明是相同的,因此在此配方中不会进一步提及。

准备工作

对于这个配方,我们需要一个干净的 Ubuntu 12.04(服务器或桌面版本)运行。

如何操作...

以下说明将帮助我们构建 OpenJDK 7:

  1. 安装预包装的 OpenJDK 6 二进制文件:

    sudo apt-get install openjdk-6-jdk
    
    
  2. 安装 GCC 工具链 和构建依赖项:

    sudo apt-get build-dep openjdk-6
    
    
  3. 下载并解压官方 OpenJDK 7 更新 40 存档(结果将放置在 openjdk 目录中):

    wget http://download.java.net/openjdk/jdk7u40/promoted/b43/openjdk-7u40-fcs-src-b43-26_aug_2013.zip
    unzip -q openjdk-7u40-fcs-src-b43-26_aug_2013.zip
    
    
  4. 创建一个包含以下环境设置的新的文本文件 buildenv.sh

    export LD_LIBRARY_PATH=
    export CLASSPATH=
    export JAVA_HOME=
    export LANG=C
    export ALT_BOOTDIR=/usr/lib/jvm/java-6-openjdk
    
  5. 将环境变量导入当前 shell 会话中(注意在它前面有一个点和空格):

    . buildenv.sh
    
  6. openjdk 目录开始构建过程:

    make 2>&1 | tee make.log
    
  7. 等待构建完成,然后尝试运行新构建的二进制文件:

    cd build/linux-amd64/j2sdk-image/
    ./bin/java –version
    openjdk version "1.7.0-internal"
    OpenJDK Runtime Environment (build 1.7.0-internal-ubuntu_2014_02_08_08_56-b00)
    OpenJDK 64-Bit Server VM (build 24.0-b56, mixed mode)
    
    

它是如何工作的...

需要预包装的 OpenJDK 6 二进制文件,因为一些构建步骤是使用外部 Java 运行时运行的。

使用 build-dep 命令安装构建指定包所需的所有依赖项。由于 Ubuntu 打包的 OpenJDK 6 与官方 OpenJDK 6 非常接近,此命令将安装几乎所有所需的依赖项。

在 amd64 平台上成功构建后,JDK 文件将放置在 build/linux-amd64/j2sdk-image 目录中,JRE 文件将放置在 build/linux-amd64/j2re-image 目录中。在 i586 平台上,将使用 build/linux-i586 路径。一个包含没有演示和示例的 JDK 的 Server JRE 额外包将放置在 j2sdk-server-image 目录中。

还有更多...

Javadoc 生成需要花费很多时间,是构建过程中最消耗内存的步骤。可以使用额外的环境变量跳过:

export NO_DOCS=true

与之前的版本不同,OpenJDK 7 支持并行(多核)本地库编译。以下环境变量可以分别用于 jdkhotspot 模块:

PARALLEL_COMPILE_JOBS=N
HOTSPOT_BUILD_JOBS=N

此构建已生成里程碑标签和构建号 b00。可以使用额外的环境变量设置预定义的构建号和里程碑:

export MILESTONE=ubuntu-build
export BUILD_NUMBER=b30

在构建过程中,可以使用额外的环境变量提供 cacerts 文件:

export ALT_CACERTS_FILE=path/to/cacerts

对于预安装的 Java(由变量ALT_BOOTDIR提供),amd64 构建可以是 amd64 或 i586 构建。i586 二进制文件消耗更少的内存,并且可以在硬件有限的情况下用于 amd64 构建。

OpenJDK 7 在 Linux 上的最小构建要求与上一版本相同,因此可以使用 Debian 5.0 Lenny 构建最兼容的版本。

参见

  • 来自第二章的准备 CA 证书配方,构建 OpenJDK 6

  • 来自第二章的在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 6配方,构建 OpenJDK 6

  • 来自第二章的为最兼容的 Linux 构建设置最小构建环境配方,构建 OpenJDK 6

  • OpenJDK 7 的官方构建说明可在hg.openjdk.java.net/jdk7u/jdk7u/raw-file/tip/README-builds.html找到

在 Mac OS X 上构建 OpenJDK 7

OpenJDK 7 支持 Mac OS X 平台作为一等公民,使用适当的toolchain版本构建它几乎和 Linux 上一样简单。

从历史上看,Java 在 Mac OS X 上得到了一等支持。JDK 基于 Sun 代码库,但由 Apple 构建并完全集成到其操作系统环境中。直到 Mac OS X 10.4 Tiger,使用标准 Swing 工具包编写的图形用户界面应用程序可以访问大多数 Cocoa 原生界面功能。用 Java 编写的应用程序非常接近原生应用程序,同时仍然是跨平台的。

但随着下一版本的发布,Java 的支持水平下降了。从 Mac OS X 10.5 Leopard 开始,新的 Cocoa 功能不再支持 Java。Apple Java 6 的发布被推迟(与其他平台相比),超过了一年。Java 6 于 2006 年 12 月发布,但直到 2008 年 4 月才对 Mac OS X 用户可用。最终在 2010 年 10 月,Apple 官方宣布停止 Java 支持。Apple Java 6 仍在更新安全更新,并且可以安装在 Mac OS X 10.9 Mavericks(当时最新的版本)上,但 Apple 不会发布未来的 Java 版本。

第三方开源 Java 发行版在 Mac OS X 上存在。最著名的是 SoyLatte——FreeBSD Java 1.6 补丁集基于 X11 的 Mac OS X Intel 机器的移植。SoyLatte 早于 OpenJDK,在 Java 研究许可下授权,并支持 Java 6 构建。现在它是 OpenJDK BSD-Port 项目的一部分。

目前,Mac OS X 的官方最新稳定 Java 版本是 Oracle Java 7,它与 OpenJDK 非常接近。在此配方中,我们将在 Mac OS X 10.7.5 Lion 上构建 OpenJDK 7 更新 40。选择此操作系统版本是因为 10.7.3 是官方最低构建要求平台,应该提供最兼容的二进制文件,而 10.7.5 与它非常接近,但可能在较新的 Intel Ivy Bridge 处理器上运行,并且也可能使用流行的虚拟化工具(如 Oracle VirtualBox 或 VMware)相对容易地虚拟化。

准备工作

对于这个配方,我们需要一个干净运行的 Mac OS X 10.7.5 Lion。

如何操作...

以下步骤将帮助我们构建 OpenJDK 7:

  1. developer.apple.com/xcode/ 下载 Xcode 3.4.2 for Lion(2012 年 3 月 22 日)并安装它(需要 Apple 开发者账户,注册免费)。

  2. 使用之前提到的相同下载链接下载 Xcode 命令行工具—2012 年 3 月晚些时候(2012 年 3 月 21 日)并安装它。

  3. 从终端运行此命令以设置命令行工具:

    sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer/
    
    
  4. 导航到 应用程序 | 实用工具 并运行 X11.app 以将其作为附加下载安装。

  5. 安装 JDK 7—Oracle 发行版,或者可以使用预构建的 OpenJDK 二进制文件。

  6. archive.apache.org/dist/ant/binaries/ 下载 Apache Ant 1.8.4 并解压它。

  7. download.java.net/openjdk/jdk7u40/promoted/b43/ 下载源存档 openjdk-7u40-fcs-src-b43-26_aug_2013.zip 并解压它。

  8. 创建一个新文本文件 buildenv.sh 并包含以下环境设置:

    export LD_LIBRARY_PATH=
    export CLASSPATH=
    export JAVA_HOME=
    export LANG=C
    export ANT_HOME=path/to/ant
    export PATH=path/to/ant/bin:$PATH
    export ALT_BOOTDIR=path/to/jdk7
    
  9. 将环境导入当前终端会话中(注意它前面有一个点和空格):

    . buildenv.sh
    
  10. openjdk 目录开始构建过程:

    make 2>&1 | tee make.log
    
  11. 等待构建完成,然后尝试运行新构建的二进制文件:

    cd build/macosx-x86_64/j2sdk-image/
    ./bin/java –version
    openjdk version "1.7.0-internal"
    OpenJDK Runtime Environment (build 1.7.0-internal-obf_2014_02_07_21_32-b00)
    OpenJDK 64-Bit Server VM (build 24.0-b56, mixed mode)
    

它是如何工作的...

除了用于原生源主要部分的 Xcode 命令行工具外,还需要 Xcode 本身来构建特定平台的代码。

OpenJDK 在 Mac OS X 上正在远离使用 X11 服务器,但版本 7 构建仍然需要它。Mac OS X 10.7 狮子版预装了 X11,只需运行一次即可为构建进行配置。

可以使用 Apple JDK6 而不是 OpenJDK7,但需要额外的配置。

Apache Ant 构建工具对于构建的一些模块是必需的。

还有更多...

此配方使用官方 Apple GCC(和 G++)版本 4.2 构建。在此版本之后,由于许可原因,官方 Apple 对 GCC 的支持已停止。Clang—最初由 Apple 开发的开源编译器—是较新版本 Mac OS X 的默认和首选编译器。虽然较新版本的 OpenJDK 在 Mac OS X 上支持 Clang,但版本 7 仍然需要 GCC。

使用相同的步骤,可以在 Mac OS X 10.8 Mountain Lion 上构建 OpenJDK 7。唯一的额外要求是,应该单独安装 X11 服务器,使用 XQuartz 项目。

Xcode 的新版本和 Mac OS X 10.9 Mavericks 的最新更新可能会破坏 OpenJDK 构建。如果希望使用较新的操作系统/工具链进行构建,最好检查 OpenJDK 邮件列表上的当前构建情况和提出的解决方案。

参见

  • 来自第二章的准备 CA 证书配方,构建 OpenJDK 6

  • 来自第二章的在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 6配方,构建 OpenJDK 6,获取有关构建调优的信息

  • 在 OpenJDK Wikipedia 上关于 Mac OS X 的官方构建说明,请参阅wikis.oracle.com/display/OpenJ

在 Windows 上为 OpenJDK 7 构建 32 位 FreeType 库

现代软件中使用的多数字体都编码为矢量格式,以支持适当的缩放。矢量字体有多个标准,例如唐纳德·E·克努特教授的 Metafont,Adobe 的 Type1,Apple 和微软的 TrueType,以及 Adobe 和微软的 OpenType。

矢量字体的光栅化是一个非常复杂的任务,大多数桌面软件(如网页浏览器或文本处理器)都使用第三方库来处理字体。

Sun Microsystems 为 Sun Java 实现许可了一个第三方封闭源代码字体库。这个库的源代码不能与 OpenJDK 的初始版本一起公开发布。在 OpenJDK 的早期,启动了 Font Scaler Replacement 项目,以采用开源字体库。

FreeType 是一个免费且开源(在宽松许可下)的字体光栅化库。它在开源桌面软件中得到广泛应用。OpenJDK 团队选择了 FreeType 作为封闭源代码字体库的替代品,并且现在在所有支持的平台上使用 OpenJDK。在 Windows 上构建 OpenJDK 需要预构建的静态和动态 FreeType 库。

可以使用与 i586 和 amd64 OpenJDK 构建相同的 Microsoft Windows SDK for Windows 7 和 .NET Framework 4(版本 7.1)来为 OpenJDK 7 构建 FreeType。我们将使用免费提供的 Visual Studio 2010 Express Edition 来配置 FreeType 项目的构建设置。

准备工作

对于这个配方,我们应该有一个运行 Windows 7 SP1 i586 的系统。

如何操作...

以下步骤将帮助我们构建 FreeType:

  1. 从微软网站下载 Microsoft .NET Framework 4 并安装它。

  2. 从微软网站下载 Microsoft Windows SDK for Windows 7 和 .NET Framework 4(版本 7.1),并将其安装到默认位置,镜像文件名为 GRMSDK_EN_DVD.iso

  3. 从 Microsoft 网站下载 Visual Studio 2010 Express Edition 并安装到默认位置。只需要 Visual C++组件。

  4. freetype.org/下载 FreeType 2.5.2 源代码 tar 包并解压。

  5. 打开include\config\ftoption.h文件并取消注释第 95 行:

    #define FT_CONFIG_OPTION_SUBPIXEL_RENDERING
    
  6. 将第 269 行和第 271 行的/* #define FT_EXPORT(x) extern x *//* #define FT_EXPORT_DEF(x) x */改为#define FT_EXPORT(x) __declspec(dllexport) x#define FT_EXPORT_DEF(x) __declspec(dllexport) x

  7. 在 Visual Studio 中打开solutions\windows\vc2010\freetype.sln解决方案。

  8. 在主菜单中,转到项目 | 属性 | 配置属性,在平台工具集字段中选择 Windows7.1 SDK。

  9. 在主屏幕上选择释放多线程作为解决方案配置

  10. 运行构建,freetype252MT.lib库将被放置到freetype\objs\win32\vc2010目录中;将其重命名为freetype.lib,并保存以供以后使用。

  11. 在主菜单中,转到项目 | 属性 | 配置属性,将配置类型更改为动态库 (.dll),并构建解决方案。freetype252MT.dllfreetype252MT.exp文件将被放置到objs\release_mt目录中。将这些文件重命名为freetype.dllfreetype.exp,并在 OpenJDK 构建期间与之前生成的freetype.lib一起使用。

它是如何工作的...

使用 Visual Studio 的自身工具集可能构建 i586 版本的 FreeType,但我们使用了 Windows SDK7.1 工具集以确保与使用相同工具集的 OpenJDK 构建的兼容性。

FT_CONFIG_OPTION_SUBPIXEL_RENDERING宏在 FreeType 实现中启用子像素渲染功能。

FT_EXPORTFT_EXPORT_DEF宏应根据当前平台的调用约定进行调整。我们将它们更改为使用 Windows 特定的调用约定。

参考信息

在 Windows 上为 OpenJDK 7 构建 64 位 FreeType 库

Windows amd64 版本的 FreeType 构建与之前配方中的 i586 构建类似。本配方中只将写出不同的步骤。请参阅之前的配方,在 Windows 上为 OpenJDK 7 构建 32 位 FreeType 库,以获取更详细的说明。

准备工作

对于这个配方,我们应该有 Windows 7 SP1 amd64 运行。

如何操作...

以下步骤将帮助我们构建 FreeType:

  1. 从微软网站下载 Microsoft .NET Framework 4(版本 7.1)、Microsoft Windows SDK for Windows 7 和 Visual Studio 2010 Express Edition,并将它们安装到默认位置。应使用GRMSDKX_EN_DVD.iso文件中的 SDK 的 amd64 版本。

  2. 按照前一个菜谱中的步骤 4 到 9 下载和调整 FreeType 源代码,并在 Visual Studio 中配置项目。

  3. 在主屏幕上选择x64作为解决方案平台

  4. 按照前一个菜谱中的步骤 10 和 11 进行操作。库将被放置在freetype\builds\windows\vc2010\x64\Release Multithreaded目录中。

它是如何工作的...

使用 Visual Studio 2010 的 Express Edition 无法构建 FreeType amd64。应该使用专业版或 Windows SDK 工具集。由于我们将使用 Windows SDK 7.1 进行 OpenJDK 构建,因此我们也将其用于适当的 FreeType 构建。

参见

  • 在 Windows 上为 OpenJDK 7 构建 32 位 FreeType 库的菜谱

在 Windows 7 SP1 上构建 32 位 OpenJDK 7

与版本 6 相比,Windows 平台上的 OpenJDK 7 构建过程进行了重大改进。尽管如此,构建环境设置仍然比 Linux 和 Mac OS X 复杂得多。构建的大部分复杂性来自于它通过 Cygwin 工具使用类似 Unix 的构建环境。

i586 构建的官方编译器要求是 Microsoft Visual Studio C++ 2010 专业版。Visual Studio 2010 的 Express Edition 也可以用于 i586 构建。虽然这个版本是免费的(就像“免费啤酒”一样),但它有一个 30 天的评估期,之后需要注册。虽然注册也是免费的,但这可能对某些使用场景(例如,自动构建服务)造成问题。

我们将使用 Microsoft Windows SDK Version 7.1 for Windows 7,而不是 Visual Studio 2010。此 SDK 也可从微软网站免费获取,并且无需注册即可使用。它使用与 Visual Studio 2010 Express 相同的编译器。它仅包含命令行工具(没有 GUI),但如果需要 GUI,也可以作为 Visual Studio 2010 的外部工具集使用。

准备工作

对于这个菜谱,我们应该有一个没有安装任何杀毒软件的 Windows 7 SP1 i586 系统运行。不允许使用杀毒软件,因为它可能会干扰 Cygwin 运行时。

如何操作...

以下步骤将帮助我们构建 OpenJDK:

  1. 从微软网站下载 Microsoft .NET Framework 4 并安装。

  2. 从微软网站下载 Microsoft Windows SDK for Windows 7 并安装到默认位置。.NET 开发工具和通用工具组件不是必需的。

  3. 将 Microsoft DirectX 9.0 SDK(夏季 2004 版)下载并安装到默认安装路径。请注意,这个版本现在不再可在微软网站上找到。然而,它可能可以从其他地方下载。文件详情如下:

    name: dxsdk_sum2004.exe
    size: 239008008 bytes
    sha1sum: 73d875b97591f48707c38ec0dbc63982ff45c661
    
  4. 安装或复制预安装的 Cygwin 版本到 c:\cygwin

  5. 创建 c:\path_prepend 目录,并将 Cygwin 安装中的 find.exesort.exe 文件复制到其中。

  6. www.cmake.org/ 下载 GNU make 工具的二进制文件,使用 www.cmake.org/files/cygwin/make.exe-cygwin1.7,将其重命名为 make.exe,并将其放入 c:\make 目录。

  7. apache.org/ 下载 Apache Ant 版本 1.8.4 的 zip 分发版,并将其解压缩到 c:\ant 目录。

  8. openjdk-unofficial-builds GitHub 项目(目录 7_32)下载预构建的 FreeType 库,并将二进制文件放入 c:\freetype\lib 目录,将头文件放入 c:\freetype\include 目录。

  9. 将 OpenJDK 6 二进制文件或 Oracle Java 6 安装到 c:\jdk6

  10. download.java.net/openjdk/jdk7u40/promoted/b43/ 网页下载官方 OpenJDK 7 更新 40 源存档,并将其解压缩到 c:\sources 目录。

  11. 创建 build.bat 批处理文件并写入以下环境变量设置:

    @echo off
    rem clear variables
    set LD_LIBRARY_PATH=
    set CLASSPATH=
    set JAVA_HOME=
    rem ALT_* variables
    set ALT_BOOTDIR=C:/jdk7
    set ALT_FREETYPE_LIB_PATH=C:/freetype/lib
    set ALT_FREETYPE_HEADERS_PATH=C:/freetype/include
    set NO_DOCS=true
    rem set compiler environment manually
    set WINDOWSSDKDIR=C:/Program Files/Microsoft SDKs/Windows/v7.1/
    set VSDIR=C:/Program Files/Microsoft Visual Studio 10.0/
    set VS100COMNTOOLS=%VSDIR%/Common7/Tools
    set Configuration=Release
    set WindowsSDKVersionOverride=v7.1
    set ToolsVersion=4.0
    set TARGET_CPU=x86
    set CURRENT_CPU=x86
    set PlatformToolset=Windows7.1SDK
    set TARGET_PLATFORM=WIN7
    set LIB=%VSDIR%/VC/Lib;%WINDOWSSDKDIR%/Lib
    set LIBPATH=%VSDIR%/VC/Lib
    rem set path
    set PATH=C:/path_prepend;%VSDIR%/Common7/IDE;%VS100COMNTOOLS%;%VSDIR%/VC/Bin;%VSDIR%/VC/Bin/VCPackages;%WINDOWSSDKDIR%/Bin;C:/WINDOWS/system32;C:/WINDOWS;C:/WINDOWS/System32/Wbem;C:/make;C:/cygwin/bin;C:/jdk7/bin;C:/ant/bin
    set INCLUDE=%VSDIR%/VC/INCLUDE;%WINDOWSSDKDIR%/INCLUDE;%WINDOWSSDKDIR%/INCLUDE/gl;
    bash
    echo Press any key to close window ...
    pause > nul
    
  12. 从 Windows 资源管理器运行 build.bat。应该会出现一个带有 bash 启动的 cmd.exe 窗口。

  13. 从 bash 命令提示符运行以下命令:

    cd /cygdrive/c/sources
    chmod –R 777 .
    make > make.log 2>&1
    
  14. 启动另一个 Cygwin 控制台并运行以下命令:

    cd /cygdrive/c/sources
    tail -f make.log
    
  15. 等待构建完成。

它是如何工作的...

Cygwin 安装在 第二章 的 为 Windows 构建安装 Cygwin 配方中介绍,构建 OpenJDK 6

为了简洁起见,这里使用磁盘 C 的根目录。通常,任意路径由 ASCII 字母或数字组成,且不能使用空格。

也可以使用 DirectX SDK 的新版本。

不同的 GNU make 版本在 Windows 上可能存在不同的问题。这个来自 cmake 项目的特定版本已在不同的 Windows 版本上进行了测试,并且运行良好。

此配方使用来自 openjdk-unofficial-builds GitHub 项目的预构建 FreeType 2.4.10 库。可以使用相同的 Windows SDK 7.1 工具链从源代码构建 FreeType。请参阅本章中的 在 Windows 上为 OpenJDK 7 构建 32 位 FreeType 库 配方。

在环境设置中,应特别注意 PATH 变量内容的顺序。排序和查找 Cygwin 实用程序应放在 PATH 变量的开头,以避免被具有相同名称但功能不同的 Windows 实用程序所掩盖。Make 应在 Cygwin 之前,以避免被 Cygwin 安装中可能包含的另一个版本的 make 所掩盖。

需要 chmod 777 命令来修复可能引起构建后期阶段错误的 Cygwin 文件权限。

make 输出将被重定向到 make.log 文件。2>&1 语句确保 stdoutstderr 都将被重定向。

tail -f命令允许我们在构建过程中监视make.log文件的内容。

在批处理文件的末尾添加了pause > nul命令,以防止在运行时错误的情况下cmd.exe窗口消失。

还有更多...

为了构建最兼容的二进制文件,应使用相同的配方,但应使用 Windows XP 操作系统而不是 Windows 7。

在 Windows XP 中,不需要chmod 777命令。

可以使用tee命令代替>tail,将构建日志同时写入文件和控制台。

可以使用 Windows SDK 的SetEnv.Cmd脚本(使用适当的标志)来设置编译器环境,而不是手动设置变量。

可以使用 Visual Studio 2010 Express 或专业版代替 Windows SDK 7.1。

可以使用预构建的 OpenJDK 7 或 Oracle Java 7 作为启动 JDK,而不是 6。

参见

  • 来自第二章的在 Windows 7 SP1 上构建 32 位 OpenJDK 6的配方,构建 OpenJDK 6

  • 来自第二章的为 Windows 构建安装 Cygwin的配方,构建 OpenJDK 6

  • 来自第二章的在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 6的配方,构建 OpenJDK 6,有关构建调优的信息

  • 本章中的在 Windows 上为 OpenJDK 7 构建 32 位 FreeType 库的配方

  • 本章中的准备 CA 证书的配方

  • OpenJDK 7 的官方构建说明,请参阅hg.openjdk.java.net/jdk7u/jdk7u/raw-file/tip/README-builds.html

在 Windows 7 x64 SP1 上构建 64 位 OpenJDK 7

Windows 7 上的 amd64 构建类似于 i586 构建,但有一些额外的复杂性。

由于 Cygwin(至少是更常见的 i586 版本)在 amd64 Windows 上运行得非常糟糕。由于地址空间大小大得多,Cygwin 的 fork 技术运行得慢得多,并且可靠性较低。

Visual Studio 2010 Express Edition 不支持 amd64 架构,因此应使用 Windows 7 的 Microsoft Windows SDK 版本 7.1 或 Visual Studio 的专业版。

准备工作

对于这个配方,我们应该有一个没有安装防病毒软件的 Windows 7 SP1 amd64 系统运行。不允许使用防病毒软件,因为它可能会干扰 Cygwin 运行时。

如何操作...

以下步骤将帮助我们构建 OpenJDK:

  1. 按照前一个配方,在 Windows 7 SP1 上构建 32 位 OpenJDK 7,使用 Windows SDK 的 amd64 版本和7_64目录中的 FreeType 库,执行步骤 1 到 10。

  2. 创建一个build.bat批处理文件,并在其中写入以下环境变量设置:

    @echo off
    rem clear variables
    set LD_LIBRARY_PATH=
    set CLASSPATH=
    set JAVA_HOME=
    rem ALT_* variables
    set ALT_BOOTDIR=C:/jdk7
    set ALT_FREETYPE_LIB_PATH=C:/freetype/lib
    set ALT_FREETYPE_HEADERS_PATH=C:/freetype/include
    set NO_DOCS=true
    rem set compiler environment manually
    set WINDOWSSDKDIR=C:/Program Files/Microsoft SDKs/Windows/v7.1/
    set VSDIR=C:/Program Files (x86)/Microsoft Visual Studio 10.0/
    set VS100COMNTOOLS=%VSDIR%/Common7/Tools
    set Configuration=Release
    set WindowsSDKVersionOverride=v7.1
    set ToolsVersion=4.0
    set TARGET_CPU=x64
    set CURRENT_CPU=x64
    set PlatformToolset=Windows7.1SDK
    set TARGET_PLATFORM=WIN7
    set LIB=%VSDIR%/VC/Lib/amd64;%WINDOWSSDKDIR%/Lib/x64
    set LIBPATH=%VSDIR%/VC/Lib/amd64
    rem set path
    set PATH=C:/path_prepend;%VSDIR%/Common7/IDE;%VS100COMNTOOLS%;%VSDIR%/VC/Bin/x86_amd64;%VSDIR%/VC/Bin;%VSDIR%/VC/Bin/VCPackages;%WINDOWSSDKDIR%/Bin;C:/WINDOWS/system32;C:/WINDOWS;C:/WINDOWS/System32/Wbem;C:/make;C:/cygwin/bin;C:/jdk7/bin;C:/ant/bin
    set INCLUDE=%VSDIR%/VC/INCLUDE;%WINDOWSSDKDIR%/INCLUDE;%WINDOWSSDKDIR%/INCLUDE/gl;
    bash
    echo Press any key to close window ...
    pause > nul
    
  3. 按照前一个配方中的步骤 12 到 15 进行操作。

它是如何工作的...

Windows 7 上的 amd64 构建类似于 i586 构建,但由于 Cygwin 在 64 位操作系统上运行较慢,因此可能会慢得多。

可以使用 i586 或 amd64 引导 JDK 进行构建,唯一的区别是 amd64 需要更多的内存(不少于 1024MB)。

对于更多详细信息,请参阅前一个配方在 Windows 7 SP1 上构建 32 位 OpenJDK 7中的它是如何工作的...部分。

还有更多...

要构建最兼容的二进制文件,应使用相同的配方,但应使用 Windows 2003 Server amd64 操作系统代替 Windows 7。

在 Windows 2003 中,不需要chmod 777命令。

可以使用预构建的 OpenJDK 7 或 Oracle Java 7 作为引导 JDK,而不是 6。

参见

  • 本章中的在 Windows 7 SP1 上构建 32 位 OpenJDK 7配方

  • 第二章中的在 Windows 7 x64 SP1 上构建 64 位 OpenJDK 6配方

  • 第二章中的为 Windows 构建安装 Cygwin配方

  • 第二章中的在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 6配方,有关构建调优信息

  • 本章中的在 Windows 上为 OpenJDK 7 构建 64 位 FreeType 库配方

  • 第二章中的准备 CA 证书配方

  • hg.openjdk.java.net/jdk7u/jdk7u/raw-file/tip/README-builds.html查看 OpenJDK 7 的官方构建说明

为 32 位和 64 位 Windows 构建准备独立的工具链

在本章前面的配方中,我们使用 Windows SDK 版本 7.1 为 Windows 构建了 OpenJDK 7。此 SDK 在使用之前需要安装。安装过程需要.NET Framework 2(用于运行包含在 Windows 7 中的安装程序)和.NET Framework 4。在某些使用场景中,安装这些组件可能非常耗时。例如,为了自动化构建,可能希望使用完全干净的 Windows 镜像进行构建。除了速度慢之外,.NET Framework 和 SDK 安装程序是图形工具,可能难以用于自动安装脚本。

在此配方中,我们将创建一组文件和一个环境脚本,可用于在完全干净的 Windows 安装(具有相应的架构)上构建 OpenJDK 7 i586 和 amd64,而无需通过 GUI 安装程序安装任何工具。这样的一组文件可以放在版本控制系统下,以便在构建之前检出。

准备工作

对于此配方,我们应该有两个 Windows 7 SP1 的干净镜像。其中一个应该具有 i586 架构,另一个应该具有 amd64 架构。

如何操作...

以下步骤将帮助我们准备一个独立的工具链:

  1. 从 Microsoft 网站下载 Windows 7 i586 的 Microsoft Windows SDK (GRMSDK_EN_DVD.iso) 并在默认位置安装到 i586 Windows 实例。不需要 .NET DevelopmentCommon Utilities 组件。

  2. 创建一个 toolchain 目录。我们将把各种工具和库放在那里,并将此目录称为 <toolchain>

  3. 将 SDK 文件从 C:\Program Files\Microsoft SDKs\Windows\v7.1 复制到 <toolchain>\winsdk71\sdk 目录。

  4. 将与 SDK 一起提供的 Visual Studio 文件从 C:\Program Files\Microsoft Visual Studio 10.0 复制到 <toolchain>\winsdk71\vs2010e 目录。

  5. 从 Microsoft 网站下载 Windows 7 amd64 的 Microsoft Windows SDK (GRMSDKX_EN_DVD.iso) 并在默认位置安装到 amd64 Windows 实例。不需要 .NET DevelopmentCommon Utilities 组件。

  6. 将 SDK amd64 安装的 C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\x64 目录复制到 <toolchain>\winsdk71\sdk\Bin\x64 目录。

  7. 从 Microsoft 网站下载并安装 Microsoft DirectX 9.0 SDK(夏季 2004 版)到 i586 Windows 实例的默认安装路径。请注意,此分发在 Microsoft 网站上不再可用。它可能可以从其他在线位置下载,文件详细信息如下:

    name: dxsdk_sum2004.exe
    size: 239008008 bytes
    sha1sum: 73d875b97591f48707c38ec0dbc63982ff45c661
    
  8. 将文件从 C:\Program Files\Microsoft DirectX 9.0 SDK (Summer 2004) 目录复制到 <toolchain>\directx 目录。

  9. 在 Windows i586 实例上,将 C:\Windows\System32 目录中的文件复制到 <toolchain>\msvcr\7_32 目录:

    msvcp100.dll
    msvcr100.dll
    msvcr100_clr0400.dll
    
  10. 在 Windows amd64 实例上,将上一步相同的文件(它们应该具有 amd64 架构)复制到 <toolchain>\msvcr\7_64 目录。

  11. 创建一个 env_32.bat 文件,包含以下环境配置,可用于 i586 构建:

    set TOOLCHAIN=<toolchain>
    set VS=%TOOLCHAIN%/winsdk71/vs2010e
    set WINSDK=%TOOLCHAIN%/winsdk71/sdk
    set ALT_COMPILER_PATH=%VS%/VC/Bin
    set ALT_WINDOWSSDKDIR=%WINSDK%
    set ALT_MSVCRNN_DLL_PATH=%TOOLCHAIN%/msvcr/7_32
    set ALT_DXSDK_PATH=%TOOLCHAIN%/directx
    set WINDOWSSDKDIR=%WINSDK%
    set VS100COMNTOOLS=%VS%/Common7/Tools
    set Configuration=Release
    set WindowsSDKVersionOverride=v7.1
    set ToolsVersion=4.0
    set TARGET_CPU=x86
    set CURRENT_CPU=x86
    set PlatformToolset=Windows7.1SDK
    set TARGET_PLATFORM=XP
    set LIB=%VS%/VC/Lib;%WINSDK%/Lib
    set LIBPATH=%VS%/VC/Lib
    set PATH=%VS%/Common7/IDE;%VS%/Common7/Tools;%VS%/VC/Bin;%VS%/VC/Bin/VCPackages;%WINSDK%/Bin;C:/WINDOWS/system32;C:/WINDOWS;C:/WINDOWS/System32/Wbem;%TOOLCHAIN%/msvcr/7_32
    set INCLUDE=%VS%/VC/INCLUDE;%WINSDK%/INCLUDE;%WINSDK%/INCLUDE/gl;
    
  12. 创建一个 env_64.bat 文件,包含以下环境配置,可用于 amd64 构建:

    set TOOLCHAIN=...
    set VS=%TOOLCHAIN%/winsdk71/vs2010e
    set WINSDK=%TOOLCHAIN%/winsdk71/sdk
    set ALT_COMPILER_PATH=%VS%/VC/Bin/x86_amd64
    set ALT_WINDOWSSDKDIR=%WINSDK%
    set ALT_MSVCRNN_DLL_PATH=%TOOLCHAIN%/msvcr/7_64
    set ALT_DXSDK_PATH=%TOOLCHAIN%/directx
    set WINDOWSSDKDIR=%WINSDK%
    set VS100COMNTOOLS=%VS%/Common7/Tools
    set Configuration=Release
    set WindowsSDKVersionOverride=v7.1
    set ToolsVersion=4.0
    set TARGET_CPU=x64
    set CURRENT_CPU=x64
    set PlatformToolset=Windows7.1SDK
    set TARGET_PLATFORM=XP
    set LIB=%VS%/VC/Lib/amd64;%WINSDK%/Lib/x64
    set LIBPATH=%VS%/VC/Lib/amd64
    set PATH=%VS%/Common7/IDE;%VS%/Common7/Tools;%VS%/VC/Bin/x86_amd64;%VS%/VC/Bin;%VS%/VC/Bin/VCPackages;%WINSDK%/Bin;C:/WINDOWS/System32;C:/WINDOWS;C:/WINDOWS/System32/wbem;%LIBS_DIR%/msvcr/7_64;%TOOLCHAIN%/msvcr/7_32;%VS%/Common7/IDE
    set INCLUDE=%VS%/VC/INCLUDE;%WINSDK%/INCLUDE;%WINSDK%/INCLUDE/gl;
    
  13. toolchain 目录添加到源控制仓库。这个文件集是一个独立的工具链,可以用来在具有相应架构的干净 Windows 映像上构建 OpenJDK 7(以及 OpenJDK 8)的 i586 和 amd64 版本。

它是如何工作的...

本食谱中的过程相当直接:收集要复制到干净 Windows 映像上的已安装文件,并为它们准备环境。

可能的一个问题是,虽然来自 Windows SDK 7.1 的 Microsoft 链接工具 link.exe 不需要 .NET 4 运行时来链接原生二进制文件,但它需要 .NET 共享库,msvcr100_clr0400.dll(参见本食谱的第 7 步)。此库必须在 PATH 中找到,否则构建将在 HotSpot VM 链接阶段失败,并出现一个不明确的错误。

还有更多...

在本食谱中,为了简洁,从环境文件中移除了不特定于 Microsoft 工具链(Cygwin、FreeType 等)的 OpenJDK 配置。为了执行 OpenJDK 构建,应将其重新添加到环境文件中。

独立工具链的结果不特定于 OpenJDK,也可以用于在 Windows 上构建其他软件。

参见

第四章:构建 OpenJDK 8

在本章中,我们将涵盖:

  • 使用 GNU Autoconf

  • 在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 8

  • 使用 ccache 加速 OpenJDK 8 的构建过程

  • 在 Mac OS X 上构建 OpenJDK 8

  • 在 Windows 7 SP1 上构建 OpenJDK 8

简介

Java 8 规范为 Java 平台带来了许多创新。除了新的语言特性,如函数式接口和 Lambda 表达式支持,以及库特性,如流和新的日期/时间 API 之外,OpenJDK 8 还有一个新的构建系统。由于本章是关于构建 OpenJDK 的,我们将深入探讨最后一个创新。

很长一段时间里,Sun Java 和 OpenJDK 构建系统围绕着发布流程发展。发布工程师的需求总是优先于开发者的需求,并且开发者对构建过程的需求与发布需求大相径庭。对于发布准备,构建过程必须是稳定的。发布构建通常是从头开始构建的,它们总是包括整个项目,速度和环境配置的复杂性对他们来说不是问题。相反,需要部分、增量以及尽可能快的构建作为开发的生命线。此外,构建脚本必须尽可能干净和易于管理,以便于轻松更改和微调。

在 OpenJDK 7 发布之前,构建过程具有以下巨大的跨平台项目的所有以下缺陷,如下列出:

  • 复杂的环境设置

  • 没有可靠的增量构建

  • 并行构建支持不完整

  • 不支持编译缓存

  • 在某些部分构建速度不合理

  • 不同类型的构建脚本(一些用于 GNU,一些用于 Apache Ant)

  • 有限的源列表选择能力下隐式编译 Java 源代码

  • 对环境的检查过于严格(对于发布是必需的,但在非标准环境中则成为负担)

  • 丢弃额外的二进制组件

  • 依赖特定版本的构建工具

  • 不支持交叉编译

由于 OpenJDK 项目的出现,公共访问构建导致构建系统变得更加适合开发者和“普通构建者”。在 OpenJDK 7 的时间线中,构建过程经历了一些清理,构建变得更加容易,但基本问题仍然相同。

最后,在 OpenJDK 8 的开发过程中做出了巨大的努力。构建系统被完全重写,其中大部分是从头开始重写的。除了清理和重构 Makefiles 以及移除 Apache Ant 之外,主要问题也得到了解决:速度、多核支持、部分和增量构建、交叉编译以及更简单的环境设置。

后者的改进很大程度上是由于引入了预构建步骤,该步骤为当前环境准备项目。此构建配置步骤使用 GNU Autoconf 构建系统执行。我们将在下面的菜谱中更详细地探讨它。

OpenJDK 8 支持 Linux、Windows、Mac OS X 和 Solaris 操作系统。以下将进一步讨论 Windows、Linux 和 Mac OS X 版本。在 Linux 和 Windows 操作系统上,支持 x86 和 x86_64 架构。在 Mac OS X 上,仅支持 x86_64。

OpenJDK 8 中的处理器架构配置已更改。以前,x86 架构被命名为 i586,x86_64 被命名为 amd64。但现在 x86 和 x86_64 名称被直接使用。此外,由于交叉编译支持,x86 版本可以在进行少量配置更改的 x86_64 操作系统上构建。因此,在本章中,我们将重点关注 x86_64,并在需要时提及 x86 构建。

使用 GNU Autoconf

GNU Autoconf,或简称为 GNU 构建系统,是一套构建工具,旨在帮助在不同类 Unix 系统之间使源代码包可移植。

Autoconf 包含多个与构建相关的工具,通常充当 Makefile 生成器。Makefile 模板在configure构建步骤中使用用户指定的环境信息和配置选项进行处理。

Autoconf 系统相当复杂,关于其在现代项目中的使用存在一些争议。对于某些项目,它可能过于陈旧、过于复杂,并且与其他平台相比,过于倾向于类 Unix 系统。但对于 OpenJDK 这样的高度跨平台项目,它已经大量依赖于类 Unix 工具,因此在配置构建步骤中使用 autotool 是合理的。

Autoconf 系统使用 GNU make 进行实际的构建步骤,但与 make 工具相比,整个 Autoconf 包的可移植性较低,特定的构建设置可能对 Autoconf 包的版本有要求。幸运的是,这种负担可以从开发者转移到构建系统工程师。对于构建配置步骤,Autoconf 生成一个独立的 shell 脚本。这样的脚本,通常命名为configure,不仅可以不使用其他构建系统工具而使用,还可以在有限的类 Unix 环境中运行,例如 Windows 平台上的 Cygwin。

在 OpenJDK 中,configure脚本事先准备并添加到源代码 tarball 中,因此构建过程不需要除了 GNU make 之外的任何 autotools 构建工具。

在这个配方中,我们将探索使用不同选项的 OpenJDK 8 构建配置。

准备工作

对于这个配方,你需要一个具有类 Unix 环境的操作系统:Linux、Mac OS X,或者安装了 Cygwin 的 Windows。请参阅第二章中关于安装 Cygwin 的配方“*为 Windows 构建安装 Cygwin”。

如何操作...

以下步骤将帮助我们配置 OpenJDK 8 构建环境:

  1. 从 Oracle 安装 JDK 7 或预构建的 OpenJDK 二进制文件。

  2. 下载并解压缩官方 OpenJDK 8 源代码存档(在撰写本书时,此存档不可用)。

  3. 使用 --help 选项运行构建配置脚本:

    bash ./configure --help
    
    

    您现在可以看到可用的配置选项列表。让我们尝试其中的一些。

  4. 运行以下命令以在构建期间直接指定要使用的 JDK 作为启动 JDK:

    bash ./configure --with-boot-jdk=path/to/jdk
    
    
  5. 运行以下命令以取消包含的加密算法实现的默认限制:

    bash ./configure –-enable-unlimited-crypto
    
    
  6. 运行以下命令以在构建过程中不生成调试符号,也不将它们包含在目标分发中:

    bash ./configure --disable-debug-symbols --disable-zip-debug-info
    
    
  7. 运行以下命令以在所有平台上强制将 FreeType 库捆绑到 OpenJDK 中:

    bash ./configure --enable-freetype-bundling
    
    
  8. 运行以下命令以将 CA 证书指定给 keystore 文件。请参阅第二章中的准备 CA 证书配方,构建 OpenJDK 6

    bash ./configure --with-cacerts-file=path/to/cacerts
    
    
  9. 运行以下命令以指定构建的里程碑和构建号,而不是生成的那些:

    bash ./configure –with-milestone=my-milestone --with-build-number=b42
    
    

它是如何工作的...

在 OpenJDK 中,由于 OpenJDK 源代码库策略,configure 脚本默认未标记为可执行。因此,在这个配方中,显式使用 bash 来运行脚本。

configure 脚本在 build 目录中准备构建配置。它执行许多检查并写入特定于环境的详细信息,用户以环境变量的形式提供选项。这些变量将在实际构建过程中自动读取。

还有更多...

OpenJDK 支持许多选项;大多数选项可以同时指定以配置脚本。

参见

构建 OpenJDK 8 Ubuntu Linux 12.04 LTS

此配方与第三章中的在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 7配方相似。

OpenJDK 的构建过程严重依赖于类 Unix 开发工具。基于 Linux 的操作系统通常对这些工具提供顶级支持,因此,在 Linux(以及 Mac OS X)上构建 OpenJDK 可能比在 Windows 上简单。对于像 Fedora 或 Ubuntu 这样的主要发行版,构建工具链和所有依赖项都已作为软件包包含在发行版中,可以轻松安装。

选择 Ubuntu 12.04 LTS 作为本书的操作系统,因为它是最受欢迎的 Linux 发行版之一。对于运行其他操作系统的读者,可以在网上找到 Ubuntu 12.04 虚拟镜像,用于最流行的虚拟化工具,如 Oracle VirtualBox 或 VMware。

要为 x86 和 x86_64 架构构建二进制文件,应使用相应的 Ubuntu 版本。对于这两个架构,构建说明完全相同,因此在此配方中不会进一步提及。OpenJDK 8 支持交叉编译,因此 x86 版本可以在 x86_64 操作系统上构建。但这种交叉编译需要非平凡的库配置,我们在此配方中不会使用它。

在 Linux 上,应使用 Oracle Linux 6.4 amd64 和 GCC 版本 4.7 来构建应产生最兼容 OpenJDK 8 二进制的最小构建环境。我们将配置 Ubuntu 12.04 使用 GCC 4.7 以接近最小构建环境。

准备工作

对于此配方,我们需要运行干净的 Ubuntu 12.04(服务器或桌面版本)。

如何操作...

以下步骤将帮助我们构建 OpenJDK 8:

  1. 安装 OpenJDK 7 的预包装二进制文件:

    sudo apt-get install openjdk-7-jdk
    
    
  2. 安装 GCC 工具链和构建依赖项:

    sudo apt-get build-dep openjdk-7
    
    
  3. 添加额外的软件包仓库:

    sudo add-apt-repository ppa:ubuntu-toolchain-r/test
    sudo apt-get update
    
    
  4. 安装 C 和 C++编译器,版本 4.7:

    sudo apt-get install gcc-4.7
    sudo apt-get install g++-4.7
    
    
  5. 进入/usr/bin目录并设置默认编译器符号链接:

    sudo rm gcc
    sudo ln -s gcc-4.7 gcc
    sudo rm g++
    sudo ln -s g++-4.7 g++
    
    
  6. 下载并解压缩官方 OpenJDK 8 源代码存档(此存档在撰写本书时不可用)

  7. 运行 autotools 配置脚本:

    bash ./configure
    
    
  8. 开始构建:

    make all 2>&1 | tee make_all.log
    
    
  9. 运行构建二进制文件:

    cd build/linux-x86_64-normal-server-release/images/
    ./bin/java -version
    openjdk version "1.8.0-internal"
    OpenJDK Runtime Environment (build 1.8.0-internal-ubuntu_2014_02_22_08_51-b00)
    OpenJDK 64-Bit Server VM (build 25.0-b69, mixed mode)
    
  10. 返回源根目录并构建紧凑配置文件中的图像:

    make profiles 2>&1 | tee make_profiles.log
    
    
  11. 检查build/linux-x86_64-normal-server-release/images目录中的配置文件图像:

    j2re-compact1-image
    j2re-compact2-image
    j2re-compact3-image
    

它是如何工作的...

需要 OpenJDK 7 的预包装二进制文件,因为一些构建步骤是使用外部 Java 运行时运行的。

build-dep命令用于安装构建指定包所需的所有依赖项。由于 Ubuntu 打包的 OpenJDK 6 与官方 OpenJDK 6 非常接近,此命令将安装几乎所有必需的依赖项。

Ubuntu 12.04 默认包仓库中没有 GCC 4.7 编译器,因此需要额外的仓库配置。

还有更多...

默认的 GCC 4.6 编译器也可以用来构建 OpenJDK 8。

OpenJDK 8 支持在 x86_64 宿主平台上交叉编译 x86 二进制文件。但build-dep -a i386 openjdk-7命令来安装所有必需的 x86 依赖项将不会工作(一些 x86 依赖项在 x86_64 OS 上不可安装),手动依赖项安装可能相当复杂。在单独的 x86 Ubuntu 12.04 实例上使用与此配方中完全相同的步骤构建 x86 二进制文件可能更容易。

参见

使用 ccache 加速 OpenJDK 8 构建过程

除了 Java 源代码外,OpenJDK 源代码库还包含大量的原生 C 和 C++ 源代码。原生代码编译比 Java 长得多,可能需要很长时间。在开发过程中,相同的代码可能会因为细微的变化而多次编译。代码部分的中间二进制结果可能在编译之间完全相同,但通常即使在这些部分没有添加更改,代码的部分也会被重新编译。自然地,人们会期望从现代高级编译/链接工具链中采用更聪明的代码重新编译方法。

ccache 工具为原生编译提供了这样的智能。此工具缓存 C/C++ 编译的输出,以便下次可以避免相同的编译,并从缓存中获取结果。这可以大大加快重新编译的时间。检测是通过散列不同类型的信息来完成的,这些信息对于编译应该是唯一的,然后使用散列总和来识别缓存的输出。

OpenJDK 8 支持 Linux 和 Mac OS X 上的 ccache,但在撰写本书时并未支持 Windows。

在这个配方中,我们将为 Ubuntu 12.04 上的 OpenJDK 8 构建设置 ccache。

准备工作

对于这个配方,我们需要设置 Ubuntu 12.04(服务器或桌面版本)以构建 OpenJDK 8(请参阅前面的配方)。

如何操作...

以下步骤将帮助我们启用 ccache:

  1. 安装 ccache 软件包:

    sudo apt-get install ccache
    
    
  2. 在 OpenJDK 8 源代码目录中运行以下命令重新配置项目:

    bash ./configure
    
    
  3. 检查 configure 脚本输出的这一行:

    ccache status:  installed and in use
    
    

它是如何工作的...

在 OpenJDK 8 中,configure 脚本检查缓存可用性,并在后续构建过程中自动启用其使用。

更多...

对于产品型构建,当结果二进制文件将用于生产时,可能更安全地重新配置项目,使用 --disable-ccache 选项禁用 ccache

参考信息

  • 本章中关于 在 Ubuntu Linux 12.04 LTS 上构建 OpenJDK 8 的先前配方

  • ccache 的官方网站为 ccache.samba.org/

在 Mac OS X 上构建 OpenJDK 8

OpenJDK 8 支持作为一等公民的 Mac OS X 平台,并且使用适当的工具链构建它几乎和 Linux 一样简单。

从历史上看,Java 在 Mac OS X 上一直享有第一类支持。JDK 基于 Sun 代码库,但由 Apple 构建,并与操作系统环境紧密结合。直到 Mac OS X 10.4 Tiger,使用标准 Swing 工具包编写的图形用户界面应用程序可以访问大多数 Cocoa 本地界面功能。用 Java 编写的应用程序感觉非常接近本地应用程序,同时仍然是跨平台的。

然而,对于下一个版本,Java 的支持水平下降了。从 Mac OS X 10.5 Leopard 开始,新的 Cocoa 功能不再支持 Java。Apple Java 6 的发布被推迟(与其他平台上的 Sun 发布相比)超过一年。Java 6 于 2006 年 12 月发布,但直到 2008 年 4 月才对 Mac OS X 用户可用。最后,在 2010 年 10 月,Apple 正式宣布其决定停止对 Java 的支持。Apple Java 6 仍在更新安全更新,并且可以安装在 Mac OS X 10.9 Mavericks(撰写本书时的最新版本)上,但 Apple 不会发布后续的 Java 版本。

对于 Mac OS X,存在第三方开源 Java 发行版。最著名的是基于 FreeBSD Java 1.6 补丁集的 SoyLatte X11 端口,用于 Mac OS X Intel 机器。SoyLatte 早在 OpenJDK 之前就存在,采用 Java 研究许可,并支持 Java 6 构建。现在它是 OpenJDK BSD 端口项目的一部分。

OpenJDK 7 在其他平台上添加了对 Mac OS X 的全面支持,并在 OpenJDK 8 中继续。

准备工作

对于这个配方,我们需要一个干净安装的 Mac OS X 10.7.5 Lion(或任何支持 Xcode 4 的后续版本)。

如何操作...

以下步骤将帮助我们构建 OpenJDK 8:

  1. developer.apple.com/xcode/ 下载 Xcode 4.6.2 for Lion(2013 年 4 月 15 日),需要 Apple 开发者账户(注册免费)并安装它。

  2. 使用与前面提到的相同下载链接下载 Xcode 命令行工具,版本为 2013 年 4 月(2013 年 4 月 15 日),并安装它。

  3. 从终端运行此命令以设置命令行工具:

    sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer/
    
    
  4. 导航到 应用程序 | 实用工具 并运行 X11.app 以设置 X 服务器。

  5. 安装 JDK 7 Oracle 发行版,或者可以使用预构建的 OpenJDK 二进制文件。

  6. 下载并解压缩官方 OpenJDK 8 源代码存档(撰写本书时此存档不可用)。

  7. 运行 autotools 配置脚本:

    bash ./configure –-with-boot-jdk=path/to/jdk7
    
    
  8. 开始构建:

    make all 2>&1 | tee make_all.log
    
    
  9. 运行构建的二进制文件:

    cd build/macosx-x86_64-normal-server-release/images/j2sdk-image
    ./bin/java -version
    openjdk version "1.8.0-internal"
    OpenJDK Runtime Environment (build 1.8.0-internal-obf_2014_02_20_19_08-b00)
    OpenJDK 64-Bit Server VM (build 25.0-b69, mixed mode)
    
    

它是如何工作的...

尽管用于原生源主要部分的 Xcode 命令行工具,但还需要 Xcode 本身来构建特定平台的代码。

Mac OS X 上的 OpenJDK 正在从使用 X11 服务器迁移,但对于版本 8 的构建仍然需要它。Mac OS X 10.7 Lion 预装了 X11,只需运行一次即可配置用于构建。

还有更多...

此菜谱使用官方的 Apple GCC (和 G++) 版本 4.2 构建。在此版本之后,由于许可原因,官方 Apple 对 GCC 的支持已停止。Clang——最初由 Apple 开发的开源编译器——是较新版本 Mac OS X 的默认和首选编译器。

尽管最初的开发者计划如此,但 OpenJDK 8 仍然需要 GCC,不能使用 Clang 构建。这就是为什么使用 Xcode 的第 4 版而不是第 5 版,因为第 5 版根本不包含 GCC。在撰写本书时,Xcode 5 和 Clang 支持正在添加到 OpenJDK 9 项目中。这种支持可能会后来回滚到 OpenJDK 8;如果您想使用 Clang 构建 8 版本,最好检查 OpenJDK 邮件列表以获取最新信息。

在 Mac OS X 10.8 Mountain Lion 上,应单独安装 10.9 Mavericks X11 服务器,使用 XQuartz 项目。

在 Windows 平台上构建 OpenJDK 8 的过程与版本 7 相比有重大改进。尽管如此,构建环境设置仍然比 Linux 和 Mac OS X 复杂得多。构建的大部分复杂性来自于它通过 Cygwin 工具使用类似 Unix 的构建环境。

在 Windows 7 SP1 上构建 OpenJDK 8

与之相关的还有

对于 i586 构建官方的编译器要求是 Microsoft Visual Studio C++ 2010 专业版。Visual Studio 2010 的社区版也可以用于 x86 构建。对于 x86_64 构建,我们将使用 Microsoft Windows SDK 版本 7.1,适用于 Windows 7。此 SDK 可从 Microsoft 网站免费获取。它使用与 Visual Studio 2010 Express 相同的编译器。它仅包含命令行工具(没有 GUI),但如果需要 GUI,也可以作为 Visual Studio 2010 的外部工具集使用。

准备工作

对于这个菜谱,我们应该有一个没有安装任何防病毒软件的 Windows 7 SP1 amd64 系统。不允许使用防病毒软件,因为它可能会干扰 Cygwin 运行时。

如何操作...

以下步骤将帮助我们构建 OpenJDK 8:

  1. 从 Microsoft 网站下载 Microsoft .NET Framework 4 并安装它。

  2. 从微软网站下载 Windows 7 的 amd64 版本 Microsoft Windows SDK(GRMSDKX_EN_DVD.iso 文件)并将其安装到默认位置。不需要 .NET 开发通用工具 组件。

  3. 从微软网站下载 Visual Studio 2010 Express 版本并将其安装到默认位置。只需要 Visual C++ 组件。

  4. 将 Microsoft DirectX 9.0 SDK(2004 年夏季)下载并安装到默认安装路径。请注意,此分发在微软网站上已不再可用。它可能可以从其他在线位置下载,文件详细信息如下:

    name: dxsdk_sum2004.exe
    size: 239008008 bytes
    sha1sum: 73d875b97591f48707c38ec0dbc63982ff45c661
    
  5. 将预安装的 Cygwin 版本安装或复制到 c:\cygwin

  6. 创建一个 c:\path_prepend 目录,并将 Cygwin 安装中的 find.exesort.exe 文件复制到该目录。

  7. 使用 www.cmake.org/ 网站上的 www.cmake.org/files/cygwin/make.exe-cygwin1.7 下载 GNU make 工具二进制文件,将其重命名为 make.exe,并将其放入 c:\make 目录。

  8. openjdk-unofficial-builds GitHub 项目(目录 7_64)下载预构建的 FreeType 库,并将二进制文件放入 c:\freetype\lib 目录,将头文件放入 c:\freetype\include 目录。

  9. 将 OpenJDK 7 二进制文件或 Oracle Java 7 安装到 c:\jdk7

  10. 下载官方 OpenJDK 8 源代码存档(此存档在本书编写时不可用)并将其解压缩到 c:\sources 目录。

  11. 将以下命令写入 build.bat 文本文件:

    @echo off
    set PATH=C:/path_prepend;C:/WINDOWS/system32;C:/WINDOWS;C:/make;C:/cygwin/bin;C:/jdk7/bin
    bash
    echo Press any key to close window ...
    pause > nul
    
    
  12. 从 Windows 资源管理器运行 build.bat。应该会弹出一个带有 bash 启动的 cmd.exe 窗口。

  13. 在 bash 命令提示符下运行以下命令:

    cd /cygdrive/c/sources
    bash ./configure \
     --with-boot-jdk=c:/jdk7 \
     --with-freetype=c:/freetype \
    chmod –R 777 .
    make > make.log 2>&1
    
    
  14. 启动另一个 Cygwin 控制台并运行以下命令:

    cd /cygdrive/c/sources
    tail -f make.log
    
    
  15. 等待构建完成。

  16. 运行以下构建二进制文件:

    cd build/windows-x86_64-normal-server-release/images/j2sdk-image
    ./bin/java -version
     openjdk version "1.8.0-internal"
     OpenJDK Runtime Environment (build 1.8.0-internal-obf_2014_02_22_17_13-b00)
     OpenJDK 64-Bit Server VM (build 25.0-b69, mixed mode)
    
    

它是如何工作的...

Cygwin 安装在 第二章 的配方 为 Windows 构建安装 Cygwin 中有详细说明,构建 OpenJDK 6。这里使用磁盘 C 的根目录是为了简洁。通常,可以使用由 ASCII 字母或数字组成的任意路径,路径中不能有空格。也可以使用较新的 DirectX SDK 版本。

不同版本的 GNU make 在 Windows 上可能存在不同的问题。这个来自 cmake 项目的特定版本在不同的 Windows 版本上进行了测试,并且运行良好。

此配方使用来自 openjdk-unofficial-builds GitHub 项目的预构建 FreeType 2.4.10 库。FreeType 可以使用相同的 Windows SDK 7.1 工具链从源代码构建。

make 输出将被重定向到 make.log 文件。2>&1 语句确保 stdoutstderr 都将被重定向。

tail -f 命令允许我们在构建过程中查看 make.log 文件的内容。

在批处理文件末尾添加的 pause > nul 命令可以防止在运行时错误的情况下 cmd.exe 窗口消失。

更多内容...

可以使用 --with-target-bits=32 配置选项在 amd64 操作系统上构建 i586 二进制文件。在这种情况下,应通过 --with-freetype 选项指定 FreeType 库的 x86 版本,并且应安装 Visual Studio 2010 的 Express 版本。

可以使用 tee 命令代替 >tail,将构建日志同时写入文件和控制台。

参见

  • 本章的 使用 GNU Autoconf 菜谱

  • 来自 第二章 的 准备 CA 证书 菜谱,构建 OpenJDK 6

  • 来自 第二章 的 为 Windows 构建安装 Cygwin 菜谱,构建 OpenJDK 6

  • 来自 第三章 的 在 Windows 上构建 64 位 FreeType 库 菜谱,构建 OpenJDK 7

  • 关于在 hg.openjdk.java.net/jdk8u/jdk8u/raw-file/2f40422f564b/README-builds.html 构建 OpenJDK 8 的官方说明

第五章:构建 IcedTea

在本章中,我们将涵盖以下主题:

  • 构建 IcedTea 6

  • 构建 IcedTea 7

  • 使用 IcedTea 补丁构建 OpenJDK 7

  • 使用 NSS 安全提供者构建 IcedTea 7

  • 使用 SystemTap 支持构建 IcedTea 6

简介

IcedTea 是 Red Hat 于 2007 年 6 月启动的针对 OpenJDK 的构建和集成项目。它允许用户使用免费软件工具构建 OpenJDK。它还提供了 Java 浏览器插件和 Java Web Start 的替代开源实现,作为 IcedTea-Web 子项目的一部分。

考虑到 OpenJDK 项目本身当前的状态,理解 IcedTea 项目的全部价值并不容易。OpenJDK 的某些功能,如现在看似标准的没有二进制组件和高级构建系统,是在 IcedTea 项目中首创的。

IcedTea 项目始于 2007 年,由 Red Hat 和 GNU classpath 启动,在 Sun Java 源代码在开源许可下发布后不久。主要目标是创建一个免费的 Java 软件包,用于包含在 GNU/Linux 分发中。流行的分发(如 Fedora 或 Arch)对包含的软件有严格的要求。一般来说,完整的源代码必须在开源许可下可用,并且二进制文件必须使用免费软件构建工具构建。在发布时,一些 OpenJDK 模块(例如,字体和声音相关的模块)仅以二进制形式提供,并且只能使用专有的 Sun Java 编译器进行构建。

IcedTea 集成了 GNU Autoconf 构建系统。这使得可以使用 GNU 编译器为 Java 编译 OpenJDK 代码,并用开源实现替换二进制模块。在 2008 年,Fedora 9 分发中的 IcedTea 6 二进制文件成功通过了 Java 技术兼容性工具包测试套件,成为完全兼容的 Java 6 实现。

IcedTea 项目的显著特征是 引导构建。引导模式意味着构建过程运行两次。首先,使用 GNU 编译器为 JavaGCJ)构建带有一些补丁的 OpenJDK,然后,使用新构建的二进制文件作为最终生产构建的编译器。在 IcedTea 的早期,这个过程是确保最终二进制文件中不包含(或意外泄漏)非自由二进制部分的唯一方法(来自非自由的 Sun Java 编译器)。使用另一个编译器的这种引导过程是自由软件的常见程序。除了防止二进制泄漏外,这样的程序还可以用来防止可能的恶意编译器书签。对于最近的 IcedTea 版本,构建和预构建的 OpenJDK 二进制文件不需要 GCJ,并且可以在没有完整引导过程的情况下用作引导 JDK。

IcedTea 比 OpenJDK 更开放于新和实验性功能。它允许您使用替代虚拟机实现(如 Zero、Shark 和 JamVM),同时也支持为额外的架构(如 ARM、MIPS)进行交叉编译,这些架构不受 OpenJDK 支持。

IcedTea 还提供了与操作系统的更紧密集成。最初,Sun Java 的 Linux 发行版被设计得尽可能自包含。许多标准功能(如图像处理支持(PNG、JPG、GIF)、压缩(ZIP)和加密算法(基于椭圆曲线的))的实现都包含在 OpenJDK 代码库中。这些允许更好的可移植性,但限制了使用较旧和可能不太安全的开源库的用户。现代 Linux 发行版具有先进的包管理工具,用于控制和管理包之间的常见依赖关系。IcedTea 引入了使用宿主操作系统中某些库的支持,而不是使用 OpenJDK 中包含的版本。

构建 IcedTea 6

与原始的 OpenJDK 6 源代码相比,IcedTea 项目在历史上经历了许多变化。变化数量最终减少,因为其中一些被包含在主要的 OpenJDK 6 源代码树中。其中一些变化,如 NIO2 API 支持(来自 OpenJDK 7),是实验性的,已被移除。

这些变化以补丁(不同的文件)的形式存储在 IcedTea 源代码树中,而不是作为单独分支中的 changesets。这可能会让不熟悉类 Unix/Linux 补丁技术和工具的用户感到困惑。不同的文件存储在 patches 目录中,并在 configure 构建步骤期间应用于 OpenJDK 源代码树。

与 OpenJDK 8 不同,GNU Autoconf 构建系统并未紧密集成到 IcedTea 构建过程中。Autoconf 的 configure 步骤为构建 OpenJDK 源代码和标准(略有修补)的 OpenJDK makefiles 准备环境,并在 Autoconf 的 make 步骤上运行。

在这个配方中,我们将使用 Ubuntu 12.04 LTS amd64 构建 IcedTea 6。

准备工作

对于这个配方,我们需要一个干净的 Ubuntu 12.04(服务器或桌面版本)正在运行。

如何操作...

以下步骤将帮助您构建 IcedTea 6:

  1. 安装 OpenJDK 6 的预包装二进制文件:

    sudo apt-get install openjdk-6-jdk
    
    
  2. 安装 GCC 工具链和构建依赖项:

    sudo apt-get build-dep openjdk-6
    sudo apt-get install libmotif-dev
    
    
  3. icedtea.wildebeest.org/download/source/ 下载并解压适当的 icedtea6* 源代码 tarball:

    wget http://icedtea.wildebeest.org/download/source/icedtea6-1.13.1.tar.xz
    tar xJvf icedtea6-1.13.1.tar.xz
    
    
  4. 配置构建,指定已预装的 OpenJDK 路径并禁用系统的 LCMS2 支持:

    ./configure \
    --with-jdk-home=/usr/lib/jvm/java-6-openjdk-amd64 \
    --disable-system-lcms
    
    
  5. 开始构建:

    make
    
    
  6. 等待构建完成。

它是如何工作的...

需要预包装的 OpenJDK 6 二进制文件,因为一些构建步骤是使用外部 Java 运行时运行的。

build-dep 命令用于安装构建指定包所需的所有依赖项。由于 Ubuntu 打包的 OpenJDK 6 与官方 OpenJDK 6 非常接近,此命令将安装几乎所有所需的依赖项。

libmotif-dev 包是所需的附加包,包含 Motif GUI 工具包的头文件。

在配置步骤中,IcedTea 检查环境并为在构建步骤中应用的对 OpenJDK 源的调整做准备。

在构建步骤中,官方 OpenJDK 6 源被下载并按照构建配置进行调整。然后,运行通常的 OpenJDK 6 构建。然后,使用第一次构建的结果作为引导 JDK 进行第二次运行。由第二次构建产生的二进制文件。

IcedTea 的最新版本需要颜色管理库 LCMS 版本 2.5 或更高。由于 Ubuntu 12.04 仓库中没有这样的版本,我们使用与 OpenJDK 源一起捆绑的 LCMS。

更多...

与 OpenJDK 相比,IcedTea 具有更加灵活的配置。本章后面的食谱中将会探索一些选项。

参见

构建 IcedTea 7

在 OpenJDK 7 的漫长开发过程中,构建过程得到了清理和显著改进。许多更改(例如用开源组件替换二进制插件)在 IcedTea 中的其他实验性更改“浸泡”之后被纳入了主要的 OpenJDK 源树中。

由于这些更改,IcedTea 7 源树与前辈相比,包含的 OpenJDK 7 补丁要少得多。类似于 IcedTea 6,Autoconf configure 步骤应用补丁并准备环境,而 make 步骤运行原始的 OpenJDK 7 makefiles。

源补丁主要用于与平台 Linux 库(zliblibpng 等)的兼容性,而不是源树中包含的库。Makefile 补丁除了这个任务外,还用于更改 Java 启动器——版本切换输出。许多应用程序使用这些输出以确定它们运行的 JRE 版本。除了版本号格式外,一个值得注意的更改是,在 Oracle 中,Java 版本行以 java version <version number> 字符串开头,而 vanilla OpenJDK 版本行以 openjdk version <version number> 开头。虽然可以使用环境变量修改版本号,但这个前缀不能修改。为了更好地与现有应用程序兼容,IcedTea 使用 makefile 补丁将此前缀从 openjdk 更改为 java

在这个菜谱中,我们将使用 Ubuntu 12.04 LTS 构建 IcedTea 7。

准备工作

对于这个菜谱,我们需要一个干净运行的 Ubuntu 12.04(服务器或桌面版本)。

如何操作...

以下步骤将帮助您构建 Iced Tea 7:

  1. 安装预包装的 OpenJDK 6 二进制文件:

    sudo apt-get install openjdk-6-jdk
    
    
  2. 安装 GCC 工具链和构建依赖项:

    sudo apt-get build-dep openjdk-6
    
    
  3. icedtea.wildebeest.org/download/source/ 下载并解压缩适当的 icedtea-2* 源代码 tarball:

    http://icedtea.wildebeest.org/download/source/icedtea-2.4.5.tar.xz
    tar xJvf icedtea-2.4.5.tar.xz
    
    
  4. 配置构建,指定预安装的 OpenJDK 路径,并禁用系统的 LCMS2 支持:

    ./configure \
    --with-jdk-home=/usr/lib/jvm/java-6-openjdk-amd64 \
    --disable-system-lcms
    
    
  5. 开始构建:

    make
    
    
  6. 等待构建完成。

它是如何工作的...

需要预包装的 OpenJDK 6 二进制文件,因为一些构建步骤是使用外部 Java 运行时运行的。

使用 build-dep 命令安装构建指定包所需的所有依赖项。

在构建步骤中,下载官方 OpenJDK 6 源,然后根据构建配置进行调整。之后,运行通常的 OpenJDK 6 构建。然后,使用第一次构建的结果作为引导 JDK 运行第二次 OpenJDK 6 构建。由第二次构建产生的二进制文件。

IcedTea 的最新版本需要颜色管理库 LCMS 版本 2.5 或更高。由于 Ubuntu 12.04 存储库中没有这样的版本,我们使用了与 OpenJDK 源一起捆绑的 LCMS。

更多内容...

与 OpenJDK 相比,IcedTea 具有更灵活的配置。本章后面的菜谱中将会探索一些选项。

参见

使用 IcedTea 补丁构建 OpenJDK 7

与 OpenJDK 7 相比,IcedTea 7 的发布周期要短得多。IcedTea 发布大约对应于 Oracle Java 安全发布,并包含适用的相同安全补丁,因此,IcedTea 7 可以用于在 Windows 和 Mac OS X 平台上构建最新的 OpenJDK 7 构建。

在非 Linux 平台上执行完整的 IcedTea 7 构建可能会很困难,因为构建配置步骤期望 Linux 作为主机平台。相反,可以使用 IcedTea 项目在 Linux 上修复 OpenJDK 源代码,然后可以使用常规的 OpenJDK 7 构建步骤在其他平台上构建修复后的源代码。

准备工作

对于这个方法,我们需要一个干净的 Ubuntu 12.04(服务器或桌面版本)正在运行。

如何做到这一点...

以下步骤将帮助您使用 IcedTea 补丁准备 OpenJDK 7 源代码:

  1. 安装预包装的 OpenJDK 6 二进制文件:

    sudo apt-get install openjdk-6-jdk
    
    
  2. 安装 GCC 工具链和构建依赖项:

    sudo apt-get build-dep openjdk-6
    
    
  3. icedtea.wildebeest.org/download/source/ 下载并解压缩适当的 icedtea-2* 源代码 tarball:

    http://icedtea.wildebeest.org/download/source/icedtea-2.4.5.tar.xz
    tar xJvf icedtea-2.4.5.tar.xz
    
    
  4. 运行配置脚本以启用使用与 OpenJDK 源代码捆绑的库:

    ./configure \
    --with-jdk-home=path/to/openjdk7 \
    --with-rhino=path/to/rhino-1.7.4.jar \
    --disable-bootstrap \
    --disable-system-zlib \
    --disable-system-jpeg \
    --disable-system-png \
    --disable-system-gif \
    --disable-system-lcms \
    --disable-system-pcsc \
    --disable-compile-against-syscalls \
    --disable-nss
    
  5. 开始构建过程:

    make
    
    
  6. 在构建过程中,修复后的 OpenJDK 7 源代码将被放入 openjdk 目录。将此目录复制到 Windows 或 Mac OS X 机器上。

  7. 使用常规的 OpenJDK 7 构建过程构建修复后的源代码,并附加以下环境变量:

    export FT2_CFLAGS='-I$(FREETYPE_HEADERS_PATH) -I$(FREETYPE_HEADERS_PATH)/freetype2'
    export DISABLE_INTREE_EC=true
    
    

它是如何工作的...

IcedTea 7 包含一组补丁,这些补丁在构建过程的开始时应用(取决于配置选项)。在这个方法中,我们不想构建 IcedTea 本身,但想收集修复后的 IcedTea 源代码以在另一个平台上稍后使用 OpenJDK 7 的常规构建过程构建,如 第三章 中所述,构建 OpenJDK 7

还有更多...

补丁是在构建的早期阶段应用的。不需要等待 IcedTea 构建完成:在应用补丁后,构建过程可以被中断。

参见

  • 本章中的 构建 IcedTea 7 方法

  • 第三章,构建 OpenJDK 7

  • 来自 第四章 的 使用 GNU Autoconf 方法,构建 OpenJDK 8

  • IcedTea 项目网站在 icedtea.classpath.org/wiki/Main_Page

使用 NSS 安全提供程序构建 IcedTea 7

OpenJDK 支持用于加密服务实现的插件模块。广泛使用的现代加密算法(如加密、填充等)通常在多个平台上可用。不同的平台可能有不同的本地算法实现。这些实现可能比 OpenJDK 内部的实现性能更好,因为它们可以依赖于它们运行的平台的细节。此外,平台实现可以用作连接到硬件加密设备(如智能卡或 eTokens)的桥梁。OpenJDK 插件加密提供者允许您通过单个 Java API 接口使用广泛的加密实现。

网络安全服务NSS)是一个开源加密库,最初是为 Netscape 网络浏览器开发的,目前由 Mozilla 基金会维护。NSS 提供了一系列加密算法的实现,支持传输层安全性TLS)和 S/MIME,并在 Firefox 网络浏览器以及其他软件中使用。NSS 还支持硬件加密设备(智能卡/eTokens)。

IcedTea 7 可以构建为支持作为加密提供者的 NSS。

准备工作

对于这个配方,我们需要一个干净的 Ubuntu 12.04(服务器或桌面版本)运行。

如何做...

以下步骤将帮助您构建带有 NSS 的 IcedTea 7:

  1. 安装 OpenJDK 6 的预包装二进制文件:

    sudo apt-get install openjdk-6-jdk
    
    
  2. 安装 GCC 工具链和构建依赖项:

    sudo apt-get build-dep openjdk-6
    
    
  3. icedtea.wildebeest.org/download/source/下载并解压缩适当的icedtea-2*源代码 tarball:

    http://icedtea.wildebeest.org/download/source/icedtea-2.4.5.tar.xz
    tar xJvf icedtea-2.4.5.tar.xz
    
    
  4. 运行配置脚本并启用 NSS 加密提供者:

    ./configure --with-jdk-home=/usr/lib/jvm/java-6-openjdk-amd64
    --disable-system-lcms
    --enable-nss
    
    
  5. 开始构建:

    make
    
    
  6. 等待构建完成。

它是如何工作的...

OpenJDK 支持使用统一的服务提供者接口的插件加密实现。IcedTea 7 允许您将加密任务委托给平台 NSS 库。

参见

使用 SystemTap 支持构建 IcedTea 6

在现代操作系统上运行的应用程序通常通过系统调用syscalls)或通过中间平台 API 与底层操作系统内核进行通信。一些操作系统提供高级跟踪工具来监控运行中的应用程序,以及用于故障排除。这些工具(如 Oracle Solaris 上的dtrace)允许用户拦截所有应用程序活动、系统调用、Unix 信号处理等,以诊断性能和功能问题。

为了正确支持跟踪工具,应用程序应该在其二进制文件中编译了 探针点(或 跟踪点)。

SystemTap 是 Linux 操作系统的跟踪工具和脚本语言。IcedTea 6 可以在 SystemTap 支持下构建。

准备工作

对于这个脚本,我们需要一个干净安装的 Ubuntu 12.04(服务器或桌面版本)运行。

如何操作...

以下步骤将帮助您使用 SystemTap 构建 IcedTea 6:

  1. 安装预包装的 OpenJDK 6 二进制文件:

    sudo apt-get install openjdk-6-jdk
    
    
  2. 安装 GCC 工具链和构建依赖项:

    sudo apt-get build-dep openjdk-6
    sudo apt-get install libmotif-dev
    
    
  3. icedtea.wildebeest.org/download/source/ 下载并解压适当的 icedtea6* 源代码 tarball:

    wget http://icedtea.wildebeest.org/download/source/icedtea6-1.13.1.tar.xz
    tar xJvf icedtea6-1.13.1.tar.xz
    
    
  4. 配置构建,指定预安装的 OpenJDK 路径并禁用系统的 LCMS2 支持:

    ./configure \
    --with-jdk-home=/usr/lib/jvm/java-6-openjdk-amd64 \
    --disable-system-lcms
    --enable-systemtap
    
    
  5. 开始构建:

    make
    
    
  6. 等待构建完成。

还有更多...

我们在 JVM 用户空间标记的支持下构建了 IcedTea 6。这些标记允许 SystemTap 探测各种 JVM 事件,包括类的加载、方法的即时翻译和垃圾回收。

参见

  • 本章的 构建 IcedTea 6 脚本

  • 第二章,构建 OpenJDK 6

  • 第四章 的 使用 GNU Autoconf 脚本

  • SystemTap 项目网站在 sourceware.org/systemtap/

第六章:使用其他 VM 实现构建 IcedTea

在本章中,我们将涵盖以下主题:

  • 配置 ARM 和 x86 之间的交叉编译

  • 使用集成 CACAO VM 为 ARM 构建 IcedTea

  • 将 JamVM 移植到使用 OpenJDK

  • 使用 Shark 编译器配置 Zero 汇编器以使用 OpenJDK

  • 使用 OpenEmbedded 食谱构建 MIPS 和其他架构

简介

虽然各种 x86 兼容平台广泛使用且常见,但还有其他架构需要考虑。Java 语言本身被设计成尽可能的跨平台。在 x86 兼容的世界里,这意味着 Java 将在大多数操作系统上以相同的方式和可预测性工作。对于更多架构而言,它应该能在其中许多架构上运行。

ARM 架构是最受欢迎的架构中的第二位。它在省电和性能之间提供了很好的平衡,主要用于嵌入式和便携式设备。有几个操作系统支持 ARM,例如各种 Linux 发行版、Android、Symbian、MeeGo 以及许多 Windows 版本。

在本章中,我们将讨论在 ARM 上构建 IcedTea,使用非官方的虚拟 Java 机器,它们不是 OpenJDK 社区的一部分。它们是为了不同的目的而构建的——学术、性能等。

不幸的是,如今,唯一同时支持 ARM 和 OpenJDK 的操作系统是 Linux。在 Linux 上构建 IcedTea 的细微差别因发行版而异,尽管主要思想和哲学是相同的。

在本章中,我们将尽可能多地使用基于不同发行版和设备细微差别的不同示例,尽管最彻底测试的将是以下几对:

  • 树莓派和 Raspbian

  • Nexus 7 和 Ubuntu touch

本章还将涵盖使用基于 x86 的系统进行交叉编译的主题,因为可能需要比 ARM 处理器能够提供的更快的编译速度,尤其是在树莓派这类小型计算机的情况下。

本章中我们将介绍三种虚拟机:ZeroVM、CACAO VM 和 Jam VM。然而,只有 Zero 汇编器是 OpenJDK 项目的一员。Zero 汇编器是一个旨在消除平台特定汇编语言并构建仅包含解释器的虚拟机的努力。它将使任何 Java 可执行文件移植到任何操作系统变得异常简单,但由于缺乏 JIT,性能会有显著影响。然而,有一个名为 Shark 的 JIT 编译器,它使用 LLVM 将 Zero 汇编器编译成特定平台的代码。但很明显,Shark 只适用于 LLVM 本身支持的架构。

JamVM 也因为其实际上非常小的尺寸而闻名,这个尺寸取决于架构,在 200 到 250 千字节之间。在这个空间内,它实现了 Oracle 发布的完整 JVM 规范,被称为蓝皮书

配置 ARM 和 x86 之间的交叉编译

尽管许多最近的 ARM 设备具有出色的性能和与桌面设备相同的核心数量,但总有一些设备的性能不足以在合理的时间内完成复杂的工作。不想被无休止的编译时间无聊吗?使用 OpenEmbedded 构建。OpenEmbedded是一个构建框架,用于创建 Linux 发行版,并且可以为大量架构进行交叉编译。

准备工作

我们需要一个真实或模拟的 ARM 设备,上面安装了 Linux。以下配方主要用于基于 deb 的发行版,但对于任何运行基于 GNU 工具的 Linux 内核的 ARM 设备,基本思想是相同的。

此外,我们还需要另一台装有 Linux 或安装了 Cygwin 的 Windows 设备,以执行主要的编译工作。

此配方还假设您将使用基于 deb 的发行版。如果您使用的是基于 ebuild 的发行版,请使用它们自己的交叉编译工具。在基于 rpm 的发行版中,过程相当类似。

如何做...

首先,我们需要利用 OpenEmbedded 项目。OpenEmbedded 是一个旨在构建特定平台环境并构建各种已知包的项目。请查看以下步骤,了解如何使用 OpenEmbedded 项目构建各种包:

  1. 为了使用它,我们将安装一些包:

    sudo apt-get install git g++ gawk zip diffstat texi2html texinfo subversion chrpath libgl1-mesa-dev libglu1-mesa-dev libsdl1.2-dev
    
    
  2. 之后,我们需要获取一个 OpenEmbedded 环境:

    git clone git://git.openembedded.org/openembedded-core oe-core
    
    
  3. 然后,我们需要在oe-core目录中创建三个更多项目:

    cd oe-core
    
    
  4. 接下来,让我们将一些层克隆到我们的根目录中:

    git clone git://git.openembedded.org/bitbake bitbake
    
    git clone git://git.openembedded.org/meta-openembedded
    
    git clone https://github.com/woglinde/meta-java.git
    
    
  5. 最后,初始化一个 OpenEmbedded 环境:

    . ./oe-init-build-env
    
    

现在我们已经拥有了一个完全可操作的 OpenEmbedded 环境。我们只需配置我们的构建。有两种方法可以做到这一点:手动编辑配置文件或通过 Hob GUI。

手动配置 OpenEmbedded 构建

您需要编辑两个配置文件:第一个配置文件是 Java 层,第二个是通用的,易于将其包含在构建中,如下面的步骤所示:

  1. 首先,编辑conf/local.conf文件。

  2. 我们将设置 JamVM 为首选虚拟机:

    PREFERRED_PROVIDER_virtual/java-native = "jamvm-native"
    
    
  3. 根据需要设置其他版本(例如版本 1.8.11):

    PREFERRED_PROVIDER_virtual/javac-native = "ecj-bootstrap-native"
    PREFERRED_VERSION_cacaoh-native = "<version_number>"
    PREFERRED_VERSION_icedtea6-native = "<version_number>"
    
    
  4. 我们需要一个 ARM 构建版本,因此我们将选择 qemu-arm 作为目标机器:

    MACHINE ?= "qemuarm"
    
    
  5. 我们还需要提到可以同时进行多少个编译过程:

    PARALLEL_MAKE = "-j 2"
    
    
  6. 此外,我们可以为单个编译包重新配置多个线程。对于四核计算机,它将是2

    BB_NUMBER_THREADS = "2"
    
    
  7. 然后,我们将编辑conf/bblayers.conf文件。

  8. 以下参数是文件格式的版本。这意味着在此之前有 3 个不兼容的格式:

    LCONF_VERSION = "4"
    
    
  9. 然后,我们将设置层以命令 OpenEmbedded 系统确切地构建什么:

    BBFILES ?= ""
    BBLAYERS = " \
     /home/user/oe-core/meta \
     /home/user/oe-core/meta-openembedded/meta-oe \
     /home/user/oe-core/meta-java \
    "
    
    

使用 Hob 工具配置 OpenEmbedded 构建

Hob 是 BitBake 的图形用户界面。请查看以下步骤,了解如何使用 Hob 工具配置 OpenEmbedded 构建:

  1. 让我们从构建目录中运行一个hob可执行文件:

    hob
    
    

    这只能在环境初始化之后完成。否则,您将收到找不到命令的消息。

    No command 'hob' found, did you mean:
     Command 'bob' from package 'python-sponge' (universe)
     Command 'hoz' from package 'hoz' (universe)
     Command 'tob' from package 'tob' (universe)
     Command 'hnb' from package 'hnb' (universe)
    hob: command not found
    
    
  2. 将出现一个启动屏幕,如图所示:使用 Hob 工具配置 OpenEmbedded 构建

  3. 首先,我们将选择一个虚拟机。对于 ARM 处理器,最方便的是 qemu-arm 机器。

  4. 之后,我们需要选择两个额外的层。一个是meta-oe层,另一个是meta-java

    小贴士

    虽然meta-java文件夹位于 OpenEmbedded 的根目录中,但meta-oe的路径是meta-openembedded/meta-oe/

  5. 当选择了一台机器时,工具将解析所有配方并计算所有所需的依赖项。然后,窗口应如下所示:使用 Hob 工具配置 OpenEmbedded 构建

  6. 然后选择一个配方。这里有一个与 Java 相关的配方:java-test-image。尽管如此,最好不选择它,而是选择core-image-basic

    小贴士

    您可以尝试从头开始组装您的镜像。您可以选择您想要的包,依赖项将自动计算。

在高级配置部分,您可以选择输出的确切内容。您甚至可以以.deb格式获得它,如图所示:

  1. 您需要编辑配方并将openjdk-7-jre添加到其中,如图所示:使用 Hob 工具配置 OpenEmbedded 构建

    此外,您还可以添加一些必要的依赖项以进行构建。

  2. 当一切准备就绪时,请按构建包按钮,将出现以下屏幕:使用 Hob 工具配置 OpenEmbedded 构建

如果出现问题,问题标签页将不会为空。

构建之后

您需要将构建输出从tmp-eglibc文件夹复制到您的 ARM 设备上。

它是如何工作的...

OpenEmbedded 是一系列针对各种工具的交叉编译配方,编译需要花费很多时间。OpenEmbedded 将从头开始创建根文件系统。然后,它将构建您想要构建的包。OpenEmbedded 提供了一组抽象层,从开发者到核心层。这些层包括构建环境和目标项目的配方和工具。

因此,在完成配置后,该工具将构建一个环境,并在其中构建一些项目。

更多...

构建 OpenEmbedded 配方不是一项简单任务。它通常需要一些问题解决技能。当您尝试构建一个标准的 Java-test 镜像时,即使花费数小时构建的构建过程被一些错误中断,这也是不寻常的。

虽然配方构建和纠正的基本知识将在以下配方中给出,但我们可以在本节中提供处理一些问题的路线图。

小贴士

一些作者发现并克服的情况将在第八章 黑客 OpenJDK 中找到。

配置问题 - 当找不到头文件时

虽然构建过程与从头开始创建一个维护良好的 Linux 仓库相似,但它并不简单。这是一个从源代码构建整个系统的任务,某些小包中的头文件移动可能是一个重大的问题,这对您的构建是致命的,如下所示:

  1. 因此,这是一个依赖问题。首先,尝试找出,您是否需要该包,是否找不到头文件。

  2. 如果是这样,找出您是否可以通过标准的 ./configure 属性(在 make 之前指定的)来禁用它。

  3. 如果您能这样做,那您很幸运。只需找到您包的配方并编辑 .bb.inc 文件。对于 OpenJDK,它是 openjdk-7-common.inc

  4. 将所需的 ./configure 属性添加(或删除)到 EXTRA_OECONF 中。不要忘记筛选 \。如果您需要该依赖项或无法禁用它,那又是另一回事。

  5. 首先,考虑切换到 .bb 文件的较旧/较新版本。这可能会解决一些问题,但也可能轻易地增加问题。

  6. 如果这不可能或不会有所帮助,尝试找到源代码的补丁并应用它。

获取问题 - 您的包无法通过任何可用的镜像访问

首先,尝试检查是否所有使用的镜像都永久离线。如果不是,只需等待一会儿,直到其中一个变得可访问。

编译问题 - 发生编译错误的地方

总是有解决这类问题的方法,这可以通过互联网搜索找到。

这可能是一个错误的依赖版本,甚至是一个编译问题,这个问题从维护者的眼中溜走了。解决方案是使用一些已知的补丁或甚至您自己的补丁来修补您的构建,或者更改包的源参数。

解析问题 - 您的配方无法解析

这不是一个常见问题,但确实遇到了,例如在 meta-java 仓库的 openjdk-7 分支中。通常这并不意味着某些东西完全损坏。检查配方文件中指示的行,以查找缺失的、部分注释和其他格式问题。如果问题仍然存在,考虑更改您当前的分支。

构建 ARM 版本的 IcedTea,集成了 CACAO 虚拟机

这个配方承诺相当简单,因为 CACAO 虚拟机的集成已经是 IcedTea 本身的一部分。它不需要任何修补或其他复杂的事情。

准备工作

我们可能需要一个真实或模拟的 ARM 设备,该设备已安装 Linux 和任何 Java 环境。我们需要 Java 来执行 javac 编译,这是 IcedTea 构建过程的一个必要部分。

如何做...

我们将下载 IcedTea,解压它,并使用指定的参数构建它,仅此而已。

  1. 首先,让我们从服务器获取最新的 IcedTea 源代码:

    wget http://icedtea.wildebeest.org/download/source/icedtea-<icedtea_version>.tar.gz
    
    

    它将下载一个存档。

  2. 如有必要,执行校验和检查。

  3. 让我们解压存档并将其复制到 icedtea 目录:

    tar -xzf icedtea-XXXX
    cd icedtea-XXXX
    
    
  4. 然后,您可能需要安装一些制作 IcedTea 所需的依赖包。

  5. 给定的包是在作者第一次构建时在机器上缺失的包:

    sudo apt-get install zip gawk xsltproc libjpeg8-dev libgif-dev libpng-dev liblcms2-dev libgtk2-dev cups libcups2-dev libc6-dev libattr1-dev alsa-ocaml-dev
    
    
  6. 然后,我们将为构建配置创建一个链接以找到 Java 主目录:

    sudo ln -s /usr/lib/jvm/java-7-openjdk-armhf/ /usr/lib/jvm/java-1.7.0-openjdk
    
    

    提示

    如果您有推荐的方式来更改默认的 Java 主目录,请按照它来做。

  7. 然后,我们将在配置中启用 CACAO 虚拟机。此外,我们将配置新构建的 IcedTea 使用 CACAO 虚拟机,不仅作为构建配置,而且作为默认配置:

     ./configure --enable-cacao --with-icedtea —with-icedtea-home=<your_openjdk_home>
    
    
  8. 当运行 configure 时,您可能会遇到一些错误,错误信息表明某些程序或库缺失。没关系,您只需使用您的包管理器安装它们即可。

  9. 在完成这些后,我们将使用一个简单的命令构建 IcedTea:

    make
    
    

这是一个漫长的过程,即使使用交叉编译,所以最好喝杯茶或类似的东西。

它是如何工作的…

IcedTea 现在支持 CACAO 虚拟机作为默认的构建配置。我们只需要启用此选项来配置并添加缺失的依赖项。通常,IcedTea 在 ARM 上使用 ZeroVM,但没有 Shark JIT 编译。然而,在底层,它实际上应用了数百个补丁,并使用了在嵌入式设备上不可接受的内存量。

更多内容...

由于您可能在某些设备上构建 IcedTea 时遇到一些困难,您可能需要使用我们之前使用过的交叉编译方式。

您可能只需要设置一个变量到本地配置文件:

 PREFERRED_PROVIDER_virtual/java-native = "cacao-native"

将 JamVM 移植到使用 OpenJDK

对于不支持 JIT 的平台,另一个 Zero HotSpot 的替代方案是 JamVM——这是一个极小的虚拟机,在支持蓝皮书规范(以书籍形式发布的)的虚拟机中是最小的。

准备中

我们可能需要一个真实或模拟的具有 Linux 和任何 Java 环境安装的 ARM 设备。我们需要 Java 来执行 javac 编译,这是 IcedTea 构建过程中的必要部分。

如何做到这一点...

尽管 JamVM 补丁可以手动应用,但我们将使用更简单的方法:

  1. 首先,让我们从仓库克隆源代码:

    git clone git://git.berlios.de/jamvm
    
    
  2. 然后,我们将其配置为使用 OpenJDK 作为 Java 运行时库:

    ./autogen.sh –with-java-runtime-library=openjdk
    
    
  3. 然后,我们将使用 make 命令从源代码实际构建它:

    make && make install
    
    
  4. 接下来,我们需要将 libjvm 复制到 lib 目录下:

    cp libjvm.so  /usr/local/jamvm/lib.
    
    
  5. 然后,让我们复制 OpenJDK 内容:

    cd /usr/lib/jvm
    cp -r java-7-openjdk jamvm-openjdk
    cp /usr/local/jamvm/lib/libjvm.so jamvm-openjdk/jre/lib/armv6/server
    
    
  6. 然后,让我们运行我们的编译后的 Java:

    /usr/lib/jvm/jamvm-openjdk/jre/bin/java -version
    
    
  7. 我们将看到如下输出:

    java version "1.7.0_20"
    OpenJDK Runtime Environment (IcedTea7 1.9.5) (7b20-1.9.5-0ubuntu1)
    JamVM (build 1.7.0-devel, inline-threaded interpreter)
    
    

更多内容

此外,Jam M 支持 是 OpenEmbedded Java 层的一部分。为了添加它,您需要移除 ZeroVM 支持,并添加 JamVM 支持。虽然这可能是一个简单的任务,但在配置和构建过程中您可能会遇到错误。

此外,您可以使用 JamVM 运行任何选择的 Java 程序,即使您的 IcedTea 构建未配置为使用它。

只需输入以下命令:

java  -jamvm <other parameters and program name>

使用 Shark 编译器配置 Zero-assembler 以使用 OpenJDK

零汇编器 HotSpot 端口是 Java 8 之前所有在 ARM 上新建的 OpenJDK 实例的默认 Java 虚拟机。它是许多 JIT 不支持平台的默认 Java 虚拟机。然而,有人正在努力将其 JIT 功能提升,名为 Shark。

准备工作

我们可能需要一个真实的或模拟的 ARM 设备,该设备已安装 Linux 和任何 Java 环境。我们需要 Java 来执行javac编译,这是 IcedTea 构建过程的必要部分。

如何做到这一点...

  1. 让我们下载 IcedTea 源代码。

    wget http://icedtea.wildebeest.org/download/source/icedtea-<icedtea_version>.tar.gz
    
    

    它将下载一个存档。

  2. 然后,你可以检查校验和。

  3. 让我们解压它并将其复制到icedtea目录中:

    tar -xzf icedtea-XXXX
    cd icedtea-XXXX
    
    
  4. 然后,你可能需要安装一些制作 IcedTea 所需的依赖包。

    以下是在我的机器上第一次构建时缺失的包。

    sudo apt-get install zip gawk xsltproc libjpeg8-dev libgif-dev libpng-dev liblcms2-dev libgtk2-dev cups libcups2-dev libc6-dev libattr1-dev alsa-ocaml-dev
    
    
  5. 然后,我们将为构建配置创建一个链接以找到 Java 主目录:

    sudo ln -s /usr/lib/jvm/java-7-openjdk-armhf/ /usr/lib/jvm/java-1.7.0-openjdk
    
    

    小贴士

    如果你有一个推荐的更改默认 Java 主目录的发行版方式,请遵循它。

  6. 然后,我们将在配置中启用 Zero-Shark。此外,我们将配置新构建的 IcedTea,不仅用于构建,还作为默认使用 Shark 虚拟机:

     ./configure --enable-zero  --enable-shark --with-icedtea —with-icedtea-home=<your_openjdk_home>
    
    

    当运行configure时,你可能会遇到错误,显示某些程序或库缺失。没关系,你只需要使用你的包管理器安装它们。

  7. 在完成这些之后,我们将使用一条简单的命令来构建 IcedTea:

    make
    
    

如何工作…

通常,IcedTea 在 ARM 上使用 ZeroVM,但没有 Shark JIT 编译。我们只需要命令它使用 ZeroVM 和 Shark JIT 编译器。

Shark 是一个项目,它使用一个著名的 LLVM 支持的平台列表,以在所有这些平台上启用 JIT 支持,这相当令人印象深刻。它可能不如本地 JIT 快,但总比没有好。

小贴士

在 ARM 上,有一个可以使用 HotSpot 中的 JIT 编译的 OpenJDK,但它是早期访问模式,并且由于它是 JDK 8,因此在此之前的支持将不会移植。

LLVM 项目的目标是实现尽可能多的语言和平台的跨平台编译。它使用前端和后端转换来提供工具的灵活性,如下面的图所示:

如何工作…

由于这种结构,LLVM 可以将 Java 类方法的代码编译成中间语言,然后再编译成本地代码。它将最简单的功能如反射等留给虚拟机。

还有更多

此外,你始终可以交叉编译具有 Zero-Shark 支持的 IcedTea。为了做到这一点,请参考本章的最后一条配方。

使用 OpenEmbedded 配方为 MIPS 和其他架构构建

当基于 ARM 的设备广泛流行时,总有其他架构。Java 的强大之处在于其跨平台性,所以让我们尝试为其他一些架构构建。

准备工作

您需要一个互联网连接,以及安装了 Cygwin 的 Linux 或 Windows 电脑。根据作者的实践经验,您至少需要 4GB 的 RAM。

为了舒适地构建,建议您拥有强大的硬件和快速的硬盘。

此外,您在构建过程中可能会遇到一些延迟,甚至 OOM-kills,所以请确保您的所有数据都已保存。

此外,您还需要配置一个 OpenEmbedded 项目,如本章第一道菜谱中所述。

如何操作...

首先,我们将了解如何将架构和软件添加到 OpenEmbedded 中:

  1. 打开您的浏览器,并访问layers.openembedded.org/layerindex/branch/master/layers/

    您将找到支持的架构列表和您可以安装的软件列表。

  2. 克隆包含您需要的代码的仓库(例如meta-asusmeta-htc)。

  3. 然后,准备您的构建环境,并打开如图所示的 Hob GUI:如何操作...

  4. 从下拉列表中选择机器。

  5. 然后,您可能需要设置一些特定于架构的./configure选项。为了做到这一点,您需要更改一个bitbake文件,添加或纠正EXTRA_OECONF变量。别忘了添加尾随的\s。

它是如何工作的…

这个项目的目标是从头开始创建仓库和发行版,包括预编译的镜像和依赖项。它由称为.bb文件的元数据组成,以及它们之间众多的依赖关系。

如果这个项目运行得不是很顺利,您可能需要做一些更改才能顺利通过。.bb文件是一个具有以下结构的文件:

#A description of this recipe.
 DESCRIPTION = ""
#A homepage, if any
 HOMEPAGE = ""
#A license
 LICENSE = ""
#Dependecies
 DEPENDS = ""
#URI of the sources
 SRC_URI = " \
 "
 # SRC_URI could also point to a git repository, eg:
 # SRC_URI = "git://host:port/path/to/repo.git;branch=win;protocol=ssh;user=username"

 # any .patch files included here will be auto-magically applied, increasing the -p level until it sticks.
 # SRC_URI = "file://some.patch"

#package revision. This is a really important thing, you will change it each time when you update the recipe.
 PR = "r0"

#checksums for tarball packages
 SRC_URI[md5sum] = ""
 SRC_URI[sha256sum] = ""
 S = "${WORKDIR}/CHANGEME-${PV}"

#Action which will be performed on oe_configure stage
 do_configure () {
   ./configure --prefix=${prefix}
 }

#Action which will be performed on oe_make stage
 do_compile () {
   make
 }

 #Action which will be performed on install stage
do_install () {
   DESTDIR=${D} oe_runmake install
 }

这是一个普通的配方模板,您可以通过它添加任何想要的依赖项。此外,您还可以将补丁添加到配方目录中,甚至将其添加到源代码中。

还有更多...

您始终可以通过使用标准的 HotSpot Zero 端口在其他架构上构建 OpenJDK。它是完全可解释的,可以使您的程序运行得非常慢,但总比没有好。只需遵循标准程序:

./configure
make

这种方法有其缺点,因为除了英特尔和 ARM 之外的大多数架构都是嵌入式架构,所以您可能无法制作出快速的产品,甚至可能根本无法制作出来。例如,在搭载新构建的 Raspbian 和 512 Mb RAM 的树莓派上,由于内存不足,构建 IcedTea 会随机崩溃,即使有大的交换文件。

第七章. 使用 WebStart 和浏览器插件

在本章中,我们将涵盖以下主题:

  • 在 Linux 上构建 IcedTea 浏览器插件

  • 在 Linux 上使用 IcedTea Java WebStart 实现

  • 准备 IcedTea Java WebStart 实现以供 Mac OS X 使用

  • 准备 IcedTea Java WebStart 实现以供 Windows 使用

简介

很长一段时间内,对于最终用户来说,Java 小程序技术是整个 Java 世界的面孔。对于许多非开发者来说,Java 这个词本身就是一个同义词,指的是允许在网页浏览器中运行 Java 小程序的 Java 浏览器插件。Java WebStart技术与 Java 浏览器插件类似,但它在远程加载的 Java 应用程序上作为独立的应用程序运行,位于网页浏览器之外。

OpenJDK 开源项目不包含浏览器插件和 WebStart 技术的实现。Oracle Java 发行版,尽管与 OpenJDK 代码库紧密匹配,但为这些技术提供了自己的闭源实现。

IcedTea-Web 项目包含浏览器插件和 WebStart 技术的免费和开源实现。IcedTea-Web 浏览器插件仅支持 GNU/Linux 操作系统,而 WebStart 实现是跨平台的。

虽然 IcedTea 对 WebStart 的实现经过充分测试且适用于生产环境,但它与 Oracle WebStart 实现存在许多不兼容性。这些差异可以被视为边缘情况;其中一些包括:

  • 解析格式不正确的 JNLP 描述符文件时的不同行为:Oracle 实现通常对格式不正确的描述符更为宽容。

  • JAR(重新)下载和缓存行为差异:Oracle 实现使用缓存更为积极。

  • 声音支持差异:这是由于 Oracle Java 和 IcedTea 在 Linux 上声音支持之间的差异。Linux 历史上有多达不同的声音提供者(ALSA、PulseAudio 等),而 IcedTea 对不同提供者的支持更广泛,这可能导致声音配置错误。

IcedTea-Web 浏览器插件(因为它基于 WebStart 构建)也存在这些不兼容性。除此之外,它还可能在浏览器集成方面存在更多不兼容性。用户界面表单和一般与浏览器相关的操作,如从 JavaScript 代码中访问,应与两种实现都能良好工作。但历史上,浏览器插件被广泛用于安全关键的应用程序,如在线银行客户端。这类应用程序通常需要来自浏览器的安全功能,例如访问证书存储或硬件加密设备,这些设备可能因操作系统(例如,仅支持 Windows)、浏览器版本、Java 版本等因素而有所不同。因此,许多实际应用程序在 Linux 上运行 IcedTea-Web 浏览器插件时可能会遇到问题。

WebStart 和浏览器插件都是基于从远程位置下载(可能是不可信的)代码的想法构建的,并且对代码进行适当的权限检查和沙箱执行是一个臭名昭著的复杂任务。通常在 Oracle 浏览器插件中报告的安全问题(最广为人知的是 2012 年期间的问题)也在 IcedTea-Web 中单独修复。

在 Linux 上构建 IcedTea 浏览器插件

IcedTea-Web 项目本身不是跨平台的;它是为 Linux 开发的,因此可以在流行的 Linux 发行版上非常容易地构建。它的两个主要部分(存储在源代码库中的相应目录中)是netxplugin

NetX 是 WebStart 技术的纯 Java 实现。我们将在本章后续的配方中更详细地探讨它。

插件是使用支持多个浏览器的NPAPI插件架构实现的浏览器插件。插件部分用 Java 编写,部分用本地代码(C++)编写,并且官方仅支持基于 Linux 的操作系统。关于NPAPI存在一种观点,即这种架构过时、过于复杂且不安全,而现代网络浏览器已经内置了足够的特性,无需外部插件。浏览器已经逐渐减少对NPAPI的支持。尽管如此,在撰写本书时,IcedTea-Web 浏览器插件在所有主要的 Linux 浏览器(Firefox 及其衍生品、Chromium 及其衍生品和 Konqueror)上都能正常工作。

我们将使用 Ubuntu 12.04 LTS amd64 从源代码构建 IcedTea-Web 浏览器插件。

准备工作

对于这个配方,我们需要一个干净的 Ubuntu 12.04 系统,并且安装了 Firefox 网络浏览器。

如何操作...

以下步骤将帮助您构建 IcedTea-Web 浏览器插件:

  1. 安装 OpenJDK 7 的预包装二进制文件:

    sudo apt-get install openjdk-7-jdk
    
    
  2. 安装 GCC 工具链和构建依赖项:

    sudo apt-get build-dep openjdk-7
    
    
  3. 安装浏览器插件的特定依赖项:

    sudo apt-get install firefox-dev
    
    
  4. 下载并解压 IcedTea-Web 源代码 tar 包:

    wget http://icedtea.wildebeest.org/download/source/icedtea-web-1.4.2.tar.gz
    tar xzvf icedtea-web-1.4.2.tar.gz
    
    
  5. 运行configure脚本以设置构建环境:

    ./configure
    
    
  6. 运行构建过程:

    make
    
    
  7. 将新构建的插件安装到/usr/local目录中:

    sudo make install
    
    
  8. 配置 Firefox 网络浏览器以使用新构建的插件库:

    mkdir ~/.mozilla/plugins
    cd ~/.mozilla/plugins
    ln -s /usr/local/IcedTeaPlugin.so libjavaplugin.so
    
    
  9. 检查 IcedTea-Web 插件是否出现在工具 | 附加组件 | 插件下。

  10. 打开java.com/en/download/installed.jsp网页以验证浏览器插件是否工作。

它是如何工作的...

IcedTea 浏览器插件需要编译成功,这需要 IcedTea Java 实现。Ubuntu 12.04 中的预包装 OpenJDK 7 二进制文件基于 IcedTea,因此我们首先安装了它们。插件使用的是 GNU Autconf 构建系统,这是免费软件工具之间常见的。需要xulrunner-dev包来访问NPAPI头文件。

构建的插件可能仅对当前用户安装到 Firefox 中,无需管理员权限。为此,我们在 Firefox 期望找到libjavaplugin.so插件库的位置创建了一个到我们插件的符号链接。

更多内容...

该插件也可以安装到具有NPAPI支持的其它浏览器中,但不同浏览器和不同 Linux 发行版的安装说明可能不同。

由于NPAPI架构不依赖于操作系统,理论上可以为非 Linux 操作系统构建插件。但目前没有这样的端口计划。

参见

在 Linux 上使用 IcedTea Java WebStart 实现

在 Java 平台上,JVM 需要为它想要使用的每个类执行类加载过程。这个过程对 JVM 来说是透明的,加载的类的实际字节码可能来自许多来源之一。例如,此方法允许 Java Applet 类从远程服务器加载到浏览器内的 Java 进程中。远程类加载还可以用于在不与浏览器集成的情况下以独立模式运行远程加载的 Java 应用程序。这种技术称为 Java WebStart,是在Java 规范请求JSR)编号 56 下开发的。

要远程运行 Java 应用程序,WebStart 需要一个应用程序描述符文件,该文件应使用Java 网络启动协议JNLP)语法编写。该文件用于定义远程服务器以加载应用程序以及一些元信息。WebStart 应用程序可以通过点击网页上的 JNLP 链接或在事先获取的 JNLP 文件上运行,而不使用浏览器。在任何情况下,运行应用程序都与浏览器完全分离,但使用与 Java Applets 类似的沙箱安全模型。

OpenJDK 项目不包含 WebStart 实现;Oracle Java 发行版提供了自己的闭源 WebStart 实现。开源的 WebStart 实现作为 IcedTea-Web 项目的一部分存在。它最初基于网络执行NetX)项目。与 Applet 技术不同,WebStart 不需要任何网络浏览器集成。这允许开发者使用纯 Java 而不需要本地代码来实现 NetX 模块。为了与基于 Linux 的操作系统集成,IcedTea-Web 实现了javaws命令作为 shell 脚本,用于启动带有正确参数的netx.jar文件。

在这个菜谱中,我们将从官方 IcedTea-Web 源代码 tarball 构建 NetX 模块。

准备工作

对于这个菜谱,我们需要一个干净安装了 Firefox 浏览器的 Ubuntu 12.04。

如何做到这一点...

以下步骤将帮助您构建 NetX 模块:

  1. 安装预包装的 OpenJDK 7 二进制文件:

    sudo apt-get install openjdk-7-jdk
    
    
  2. 安装 GCC 工具链和构建依赖项:

    sudo apt-get build-dep openjdk-7
    
    
  3. 下载并解压 IcedTea-Web 源代码 tarball:

    wget http://icedtea.wildebeest.org/download/source/icedtea-web-1.4.2.tar.gz
    tar xzvf icedtea-web-1.4.2.tar.gz
    
    
  4. 运行configure脚本来设置构建环境,排除构建中的浏览器插件:

    ./configure –disable-plugin
    
    
  5. 运行构建过程:

    make
    
    
  6. 安装新构建的插件到/usr/local目录:

    sudo make install
    
    
  7. 从 Java 教程中运行 WebStart 应用程序示例:

    javaws http://docs.oracle.com/javase/tutorialJWS/samples/
    deployment/dynamictree_webstartJWSProject/dynamictree_webstart.jnlp
    
    

它是如何工作的...

javaws shell 脚本安装到/usr/local/*目录。当使用路径或 JNLP 文件的链接启动时,javaws将启动netx.jar文件,将其添加到引导类路径(出于安全原因)并作为参数提供 JNLP 链接。

相关阅读

为 Mac OS X 准备 IcedTea Java WebStart 实现

IcedTea-Web 项目中的 NetX WebStart 实现是用纯 Java 编写的,因此它也可以在 Mac OS X 上使用。IcedTea-Web 只为基于 Linux 的操作系统提供javaws启动器实现。在这个菜谱中,我们将为 Mac OS X 创建一个简单的 WebStart 启动脚本实现。

准备工作

对于这个菜谱,我们需要安装了 Java 7 的 Mac OS X Lion(预构建的 OpenJDK 或 Oracle 版本)。我们还需要从 IcedTea-Web 项目获取netx.jar模块,可以使用之前菜谱中的说明来构建。

如何做到这一点...

以下步骤将帮助您在 Mac OS X 上运行 WebStart 应用程序:

  1. 从 Java 教程 docs.oracle.com/javase/tutorialJWS/samples/deployment/dynamictree_webstartJWSProject/dynamictree_webstart.jnlp 下载 JNLP 描述符示例。

  2. 测试此应用程序是否可以通过 netx.jar 从终端运行:

    java -Xbootclasspath/a:netx.jar net.sourceforge.jnlp.runtime.Boot dynamictree_webstart.jnlp
    
    
  3. 使用以下内容创建 wslauncher.sh bash 脚本:

    #!/bin/bash
    if [ "x$JAVA_HOME" = "x" ] ; then
     JAVA="$( which java 2>/dev/null )"
    else
     JAVA="$JAVA_HOME"/bin/java
    fi
    if [ "x$JAVA" = "x" ] ; then
     echo "Java executable not found"
     exit 1
    fi
    if [ "x$1" = "x" ] ; then
     echo "Please provide JNLP file as first argument"
     exit 1
    fi
    $JAVA -Xbootclasspath/a:netx.jar net.sourceforge.jnlp.runtime.Boot $1
    
    
  4. 将启动脚本标记为可执行:

    chmod 755 wslauncher.sh
    
    
  5. 使用启动脚本运行应用程序:

    ./wslauncher.sh dynamictree_webstart.jnlp
    
    

它是如何工作的...

next.jar 文件包含一个 Java 应用程序,可以读取 JNLP 文件并下载和运行 JNLP 中描述的类。但由于安全原因,next.jar 不能直接作为应用程序启动(使用 java -jar netx.jar 语法)。相反,netx.jar 被添加到受保护的启动类路径中,并直接指定主类来运行。这允许我们在沙盒模式下下载应用程序。

wslauncher.sh 脚本尝试使用 PATHJAVA_HOME 环境变量查找 Java 可执行文件,然后通过 netx.jar 启动指定的 JNLP。

更多内容...

wslauncher.sh 脚本为从终端运行 WebStart 应用程序提供了一个基本解决方案。为了正确地将 netx.jar 集成到操作系统环境中(以便能够使用来自网络浏览器的 JNLP 链接启动 WebStart 应用程序),可以使用本地启动器或自定义平台脚本解决方案。这些解决方案超出了本书的范围。

相关内容

准备 Windows 上的 IcedTea Java WebStart 实现

IcedTea-Web 项目的 NetX WebStart 实现是用纯 Java 编写的,因此它也可以在 Windows 上使用;我们还在本章前面的配方中在 Linux 和 Mac OS X 上使用了它。在这个配方中,我们将为 Windows 创建一个简单的 WebStart 启动脚本实现。

准备工作

对于这个配方,我们需要一个运行 Java 7(预构建的 OpenJDK 或 Oracle 版本)的 Windows 版本。我们还需要来自 IcedTea-Web 项目的 netx.jar 模块,可以使用本章前面的配方中的说明来构建。

如何做到这一点...

以下步骤将帮助您在 Windows 上运行 WebStart 应用程序:

  1. 从 Java 教程中下载 JNLP 描述符示例:

    http://docs.oracle.com/javase/tutorialJWS/samples/deployment/dynamictree_webstartJWSProject/dynamictree_webstart.jnlp
    
    
  2. 使用netx.jar测试此应用程序是否可以从命令提示符中运行:

    java -Xbootclasspath/a:netx.jar net.sourceforge.jnlp.runtime.Boot dynamictree_webstart.jnlp
    
    
  3. 创建一个批处理文件,名为wslauncher.bat,内容如下:

    @echo off
    if "%JAVA_HOME%" == "" goto noJavaHome
    if not exist "%JAVA_HOME%\bin\javaw.exe" goto noJavaHome
    set "JAVA=%JAVA_HOME%\bin\javaw.exe"
    if "%1" == "" goto noJnlp
    start %JAVA% -Xbootclasspath/a:netx.jar net.sourceforge.jnlp.runtime.Boot %1
    exit /b 0
    :noJavaHome
    echo The JAVA_HOME environment variable is not defined correctly
    exit /b 1
    :noJnlp
    echo Please provide JNLP file as first argument
    exit /b 1
    
    
  4. 使用启动脚本运行应用程序:

    wslauncher.bat dynamictree_webstart.jnlp
    
    

它是如何工作的...

由于安全原因,netx.jar 模块必须添加到启动类路径中,因为它不能直接运行。

wslauncher.bat 脚本尝试使用JAVA_HOME环境变量查找 Java 可执行文件,然后通过netx.jar启动指定的 JNLP。

更多内容...

wslauncher.bat 脚本可以被注册为默认应用程序以运行 JNLP 文件。这将允许您从网页浏览器中运行 WebStart 应用程序。但当前的脚本在启动应用程序之前会短暂显示批处理窗口。它也不支持在 Windows 注册表中查找 Java 可执行文件。可以使用 Visual Basic 脚本(或任何其他本地脚本解决方案)或作为本地可执行启动器编写一个没有这些问题的更高级的脚本。这些解决方案超出了本书的范围。

参考以下内容

第八章. 破解 OpenJDK

在本章中,我们将涵盖以下内容:

  • 使用 NetBeans 设置开发环境

  • 与 Mercurial 森林协同工作

  • 理解 OpenJDK 6 和 7 增量构建

  • 使用 NetBeans 调试 Java 代码

  • 使用 NetBeans 调试 C++代码

  • 使用 NetBeans 编译 HotSpot

  • 使用 HotSpot 开发参数

  • 向 HotSpot 添加新的内建函数

  • 从源代码构建 VisualVM

  • 为 VisualVM 创建插件

  • 从 AdoptOpenJDK 项目中获得收益

简介

OpenJDK 真正的美在于其开放性,这意味着开发者不仅可以使用它来运行应用程序,还可以根据需要对其进行更改或为其开发做出贡献。源代码的可用性和易于访问为有特殊要求的人或只是想了解更多关于 JVM 内部工作方式的人提供了巨大的机会,他们希望将其适应任何特殊要求。本章将帮助您进入这一领域,并提供一些食谱,使设置所需开发环境的过程尽可能简单。

首先,它将涵盖如何设置开发环境以及启动所需的工具。它将涵盖 IDE 设置,以及启动 JVM 和开始调试所需的调整。下一步是更改代码并重新构建,后者将略不同于第五章中描述的正常构建,即第五章,构建 IcedTea,第六章,使用其他虚拟机实现构建 IcedTea,和第七章,使用 WebStart 和浏览器插件。该部分剩余的内容将包含一些有用的技术,可用于调试更改。

本章假设读者对 C++和 Java 有合理的了解。对 JVM 的了解是理想的,因为读者应该知道 JIT 是什么以及它是如何工作的。

JIT 部分的大多数食谱都是独立的,可以单独执行,因此读者只需选择他们需要的部分并继续即可。

使用 NetBeans 设置开发环境

本食谱将涵盖在 NetBeans IDE 中安装、运行和设置项目的步骤。NetBeans 是一个开源 IDE,主要用于 Java 开发。它还提供了对 C++的丰富支持,这使得它成为 OpenJDK 开发的良好工具,因为 OpenJDK 使用这两种语言。本食谱使用 NetBeans IDE v.7。

准备工作

www.netbeans.org/downloads下载适用于您平台的最新 NetBeans All捆绑包。All捆绑包必须在同一 IDE 中具有 C/C++和 Java 支持。还必须在机器上检出并可用 OpenJDK 代码。

确保 OpenJDK 构建的设置已经完成,并且可以无错误地执行。如何做到这一点在第二章、构建 OpenJDK 6、第三章、构建 OpenJDK 7和第四章、构建 OpenJDK 8中有描述。

如何操作...

我们将安装并配置 OpenJDK 项目中使用的 NetBeans IDE 作为标准 IDE。

  1. 首先,我们需要安装 NetBeans IDE。这是一个非常简单的过程,包括几个简单的步骤。运行下载的可执行文件,在第一个屏幕的底部,选择自定义按钮。这将显示以下窗口:如何操作...

    确保已选择基本 IDEJava SEC/C++需求功能。其余的都是可选的,不是运行和调试 OpenJDK 所必需的,但安装该功能并无害处。

  2. 设置完成后,您应该更新所有插件到最新版本。更新可通过帮助/检查更新菜单项进行。

  3. 当 NetBeans 设置完成后,需要对其配置进行轻微的修改。

    OpenJDK 是一个大型项目,其内存需求比默认设置中定义的要大。为了增加 IDE 可用的内存:

    1. 前往$HOME/.netbeans/NETBEANS_VERSION/etc文件夹(在 Windows 中$HOME%HOMEPATH%)。

    2. 如果文件夹不存在,请创建它。

    3. 然后,如果该文件夹中没有netbeans.conf文件,请从 Netbeans 安装目录中复制它,该目录位于etc文件夹中。

    4. 使用任何文本编辑器打开文件,并定位看起来类似于以下的netbeans_default_options参数:

      netbeans_default_options="-J-client -J-Xss2m -J-Xms32m -J-XX:PermSize=32m -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.graphics.UseQuartz=true -J-Dsun.java2d.noddraw=true -J-Dsun.java2d.dpiaware=true -J-Dsun.zip.disableMemoryMapping=true"
      
      
  4. 当找到参数时,添加-J-Xmx2g,或者如果该选项已经存在,更新它到一个不小于2G(2 千兆字节)的值。这将增加 JDK 可用的内存到2G。如果 IDE 之前正在运行,请重新启动以应用该更改。

    小贴士

    值得注意的是,由于 Netbeans IDE 对内存需求较大,建议在能够为进程提供至少 2GB 内存的系统上运行。基本上,这意味着它应该是一个 64 位操作系统,并拥有大约 4 到 6GB 的 RAM。

  5. 现在,Netbeans IDE 已准备好设置项目。运行它,并在对话框中转到文件 | 新建项目,选择使用现有源文件的 C/C++项目如何操作...

  6. 然后按下一步,在随后的对话框中,选择 OpenJDK 源根目录所在的文件夹:如何操作...

    小贴士

    您可以通过输入hg clone http://hg.openjdk.java.net/jdk6/jdk6 && ./get_source.sh来获取您的 OpenJDK 代码。

  7. 完成 按钮,Netbeans 将尝试清理和构建项目。清理,很可能会无问题执行,但构建将不会工作,因为它需要一些环境设置,我们将在稍后进行。

    在尝试构建项目后,Netbeans 将花费相当多的时间(分钟)扫描源代码和构建索引。这时喝杯咖啡是个不错的选择。

  8. 下一步是配置 Netbeans 以构建项目。如前所述,构建脚本需要一些环境设置。以下是一个简单的 bash 脚本,可以用来创建适当的环境:

    #!/bin/sh
    export LANG=C
    export ALT_BOOTDIR=/usr/lib/jvm/java
    ./jdk/make/jdk_generic_profile.sh
    make $*
    
    
  9. 在 OpenJDK 源树的根目录下创建一个名为 build.sh 的文件,并将此脚本保存在该文件夹中。

  10. 然后导航到 运行 | 设置项目配置 | 自定义 菜单项,在左侧的树状结构中,选择 构建 | 构建。在那里,您将看到以下对话框:如何操作...

  11. 如截图所示,将 构建命令清理命令 变量设置为执行您的 ./build.sh,分别使用 debug_buildclean 命令。

  12. 如果需要 OpenJDK 的 product 版本,那么只需创建另一个配置,包含 product_buildbuild.sh 的参数。

参见

Netbeans 不是唯一支持 Java 和 C++ 的 IDE。还有其他类似功能的 IDE。一个例子是 Eclipse IDE,它也是一个用 Java 编写的强大多平台 IDE,并且具有类似的功能。

使用 Mercurial forest 进行工作

Mercurial 是一个跨平台版本控制系统。它被设计用于处理大型项目和大量代码,这在 OpenJDK 项目中无疑是存在的。OpenJDK 的官方仓库是一个 Mercurial 仓库。

Forest 插件是用于各种 OpenJDK 子项目合并和共存的插件。它与嵌套的 Mercurial 仓库一起工作,这些仓库通常被视为独立的。主要思想是从根仓库传播更改到嵌套的仓库。

它的主要目的是允许开发者在不需要对整个仓库(例如更改修订号)进行任何更改的情况下,与代码一起工作,这仅仅是完整 OpenJDK 项目仓库的一个小部分。

准备工作

首先,我们需要安装 Mercurial 本身。在 Windows 上,可以通过访问官方 Mercurial 网站并从 mercurial.selenic.com/wiki/Download 下载它。

对于 Linux 发行版,通常在它们的官方仓库中有 Mercurial 版本。

例如,在 Debian 和 Debian 衍生发行版中,Mercurial 的安装方式如下:

sudo apt-get install mercurial

如果您在 Mercurial 安装过程中遇到任何问题,请参考官方网站或您的 Linux 发行版资源。

如何操作...

让我们用一个简单、非 Java 相关的例子来解释它。我们将假设在 OS 中已经存在一个 Mercurial 实例。由于 Mercurial 有一个命令行工具,我们将使用命令行做所有事情。

  1. 让我们创建两个仓库:

    mkdir repo-one
    cd repo-one
    hg init
    echo "hello" > hello.txt
    hg ci -m"init one"
    cd ..
    
    mkdir repo-two
    cd repo-two
    hg init
    echo "hello" > hello.txt
    hg ci -m"init two"
    cd ..
    
    
  2. 我们需要找到 .hgrc 文件:

    locate hgrc
    
    
  3. 让我们从 bitbucket.org/gxti/hgforest/src 复制 forest.py 文件。

  4. 然后,让我们编辑你的 .hgrc 文件:

    [extensions]
    forest = patch/to/forest.py
    
    

我们现在在我们的仓库中有一个全新的 fclone 命令。

  1. 让我们将第一个仓库复制到第二个仓库中:

    hg clone repo-one repo-two/one
    
    

    repo-two 仓库不是 repo-one 的一个整体部分,它只是位于其中。

  2. 让我们使用 fclone 命令克隆一个 repo-two 仓库并将 repo-one 附接到它上:

    hg fclone repo-two wc-two
    
    

    我们刚刚复制了 repo-two,包括 repo-two/one

  3. 让我们对 repo-two/hello.txtrepo-two/one/hello.txt 进行一些修改:

    echo some >> repo-two/hello.txt  &&  echo some1 >> repo-two/hello.txt
    
    
  4. 我们将使用单独的命令提交每个更改:

    cd wc-two/ && ls
    hg ci -m"edited hello.txt"
    cd one/
    hg ci -m"edited hello.txt"
    cd ..
    
    
  5. 让我们将结果推回 repo-two

     hg fpush
    
    

    我们将在 repo-one 中有两个更改的文件。

  6. 让我们将其中一个推送到 repo-two

    cd ../repo-two
    hg fpush
    
    
  7. 现在,repo-two/one 的更改已传播到 repo-one

它是如何工作的…

Mercurial 是一个相对简单的控制系统。它可以通过不同的插件进行大量扩展,这些插件通过 .hgrc 文件进行配置。

Mercurial 的 forest 插件将嵌套仓库中的更改传播到根仓库,并将父仓库的内容与嵌套仓库同步。

理解 OpenJDK 6 和 7 的增量构建

OpenJDK 的编译过程非常耗时。这非常无聊,尤其是在开发整个项目的一个小部分时,为了测试目的需要完全重新编译。为了简单地进行编译,并且只编译必要的部分,存在增量构建。

准备工作

我们需要下载 OpenJDK(6 或 7)的源代码。你可能需要安装 libmotif。Windows 用户可能需要安装 Cygwin。

如何做…

我们将看到 OpenJDK 是如何进行增量构建的,以避免添加任何讨厌的虫子。

  1. 首先,让我们第一次构建 OpenJDK:

    make all
    
    
  2. 这将花费一些时间,所以喝杯茶吧。

  3. 然后,我们将第二次构建它:

    make all
    
    
  4. 从输入中你可以看到实际上并没有构建任何东西。

  5. 然后,让我们对一些源文件(例如,cardTableModRefBS.cpp)进行微小的更改。

  6. 让我们再次 make OpenJDK,但这次我们将 grep 输出:

    make all | grep -i .cpp
    
    
  7. 我们看到,在输出中,实际上只编译了两个文件,hotspot/src/share/vm/memory/cardTableModRefBS.cpphotspot/src/share/vm/runtime/vm_version.cpp

它是如何工作的…

构建程序会检查更新的文件,并只编译那些在最后一次编译运行之后更新的文件。但是,如果任何 .hpp 文件被修改,构建将以干净模式执行,例如,不会进行优化。

还有更多…

在使用增量构建时,往往会发生奇怪的事情。这种事情的概率与构建时间成比例增加。

基本上有两种执行清理构建的方法:

  • 清理所有文件,从头开始重新编译成为必要:

    make clean && make all
    
    
  • 第二种方法是指定参数,这将强制构建的清理模式。

使用 NetBeans 调试 Java 代码

显然,当有人编写任何代码时,都需要进行一些调试。NetBeans 作为一款高标准的 IDE,提供了一些工具来完成这项工作。本食谱将展示如何使用 NetBeans 调试 Java 代码。

准备工作

您需要安装 NetBeans 并设置一个开发环境,如本章先前所述。

如何操作...

我们将使用 NetBeans 调试我们自己的 OpenJDK Java 代码。我们需要重新构建 OpenJDK 并带有调试符号,并配置 NetBeans 以便进行调试:

  1. 首先,让我们创建一个带有调试符号的 OpenJDK 实例:

    bash ./configure --enable-debug
    make all CONF=linux-x86_64-normal-server-fastdebug
    
    
  2. 让我们确保构建了一个可调试的版本:

    ./build/linux-x86_64-normal-server-fastdebug/jdk/bin/java -version
    openjdk version "1.8.0-internal-fastdebug"
    OpenJDK Runtime Environment (build 1.8.0-internal-fastdebug-dsmd_2014_03_27_05_34-b00)
    OpenJDK 64-Bit Server VM (build 25.0-b70-fastdebug, mixed mode)
    
    
  3. 现在我们有了可调试的 OpenJDK。让我们将其设置为 NetBeans 的默认版本。

  4. 让我们在 NetBeans 安装路径中打开 etc/netbeans.conf 文件。

  5. 我们将更改一行:

    netbeans_jdkhome="<path_to_jdkhome>"
    
    
  6. 之后,我们将启动 NetBeans 并确保我们的 JDK 已正确加载。

  7. 我们将选择 工具 | Java 平台,随后会出现以下屏幕:如何操作...

  8. 让我们尝试调试 java.lang.String 类。我们将断点设置在这个类的不可避免部分——其中一个构造函数,如以下截图所示:如何操作...

  9. 这组断点足以连接到迄今为止启动的几乎所有 Java 可执行文件。但如果我们决定更进一步并附加调试器,我们将得到一个错误消息:

    Not able to submit breakpoint LineBreakpoint String.java : 138, reason: No source root found for URL 'file:/home/dsmd/00experimental/java_build/jdk8intr/jdk8/jdk/src/share/classes/java/lang/String.java'. Verify the setup of project sources.
    Invalid LineBreakpoint String.java : 138
    
    
  10. 为了避免这种情况,我们需要直接将我们的 Java 源代码指定给 NetBeans。我们的项目是一个 C++ 项目,它倾向于忽略 Java 文件。

  11. 对于 String 类的结果将如以下截图所示:如何操作...

  12. 然后,只需启动一些使用字符串的 Java 可执行文件:

    build/linux-x86_64-normal-server-fastdebug/jdk/bin/java -Xdebug -Xrunjdwp:transport=dt_socket,address=8998,server=y -jar /path/to/jar.jar
    
    
  13. 按照以下步骤连接 Java 调试器:如何操作...

  14. 享受吧,您现在可以从内部看到 OpenJDK 的动态:如何操作...

工作原理…

这只是一个带有几个简单细微之处的调试器。

更多内容...

在某些 Linux 发行版中,您可以使用 ZIP 文件中提供的源代码安装 OpenJDK 的调试版本。这些源代码会被 NetBeans 自动获取。

使用 NetBeans 调试 C++ 代码

如果您计划修改 HotSpot 或 OpenJDK 的任何其他 C++ 部分,那么您肯定需要逐步调试代码。本食谱将解释如何设置 NetBeans IDE 以实现此目的。

准备工作

要开始,只需要几样东西——下载的 Open JDK 源代码和安装的 NetBeans IDE。假设 OpenJDK 项目已经设置好并且可以构建源代码。

如何操作...

  1. 第一步是设置一个可运行的执行文件。转到运行 | 设置项目配置 / 自定义,然后构建 | Make,并将build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/gamma设置为构建结果,如下面的截图所示:如何操作...

  2. 然后在左侧树形结构中选择运行选项,并将运行命令设置为"${OUTPUT_PATH}" –version如何操作...

  3. 这里的–version标志只是你可以运行的 simplest thing——获取 Java 版本。你可以稍后将其更改为你想要的任何内容,例如,运行 Java 程序。

  4. 下一步是设置一些 Java 运行所需的环境变量。这可以通过在以下对话框的环境部分设置它们来完成。将LD_LIBRARY_PATH设置为build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg,将JAVA_HOME设置为build/linux-amd64-debug/j2sdk-server-image

  5. 现在一切准备就绪,可以开始调试。为了检查它是否工作,在hotspot/src/share/tools/launcher/java.cmain函数的开始处设置一个断点,然后转到调试 | 调试主项目或使用快捷键Ctrl + F5

它是如何工作的…

仔细的读者可能已经注意到,调试使用了gamma JVM 启动器,而不是通常运行 Java 时使用的java。这是为了简化事情;gammajava的轻量级版本,它不会执行调试目的不必要的检查。

使用 NetBeans 编译 HotSpot

在进行 HotSpot 开发时,等待完整的 OpenJDK 构建执行是非常令人烦恼的。因此,排除其他部分并仅编译我们感兴趣的部分,即 HotSpot 部分,是有意义的。这个配方将解释如何做到这一点。

准备工作

这个配方的唯一先决条件是机器上可用的源代码,已安装 Netbeans,并已创建 OpenJDK 项目。

如何操作...

这是一个非常简单的配方,可以遵循。如果你已经完成了使用 NetBeans 设置开发环境,唯一需要做的事情是更改hotspot_build参数并添加另一个参数DEBUG_NAME=debug,整个构建命令行应该看起来像这样:

build.sh hotspot-build DEBUG_NAME=debug

在这种情况下,项目属性对话框的构建/Make屏幕将看起来像这样:

如何操作...

它是如何工作的…

幸运的是,在make配置中已经创建了仅构建 HotSpot 位的目标。这些目标可以在./make/hotspot-rules.gmk文件中找到。

创建非优化版本或 HotSpot 的debug命令不是DEBUG_NAME变量的唯一选项。fastdebug命令是另一个选项,其中构建将创建一个带有断言的优化版本。当DEBUG_NAME未设置时,将构建 HotSpot 的产品版本。

使用 HotSpot 开发参数

HotSpot 有其他选项,这些选项可能会显著改变其行为。在这里,我们将使用其中一些选项,这些选项仅在 OpenJDK 的开发版本中使用。

准备工作

为了使用这些选项,我们需要编译一个开发 OpenJDK 版本。

如何操作...

我们将使用 OpenJDK 开发版本中可用的参数。在生产构建中,它们被禁用或设置为常量值。

要使用这些参数,我们将按照以下方式运行 Java:

java - -XX:<optionName>

这里是一些可用的开发选项列表:

  • InlineUnsafeOps:如果启用此选项,将内联来自 sun.misc.Unsafe 的原生内存操作。在某些情况下,它可能提供一些性能改进。

  • DieOnSafepointTimeout:此选项如果安全点未达到但超时,则会终止进程。默认情况下是禁用的。

  • ZapResourceArea:此选项将使用 0xABABABAB zaps 释放的资源/区域空间。在调试模式下是真实的,但在生产 VM 中被取消选中。尽管它有一些性能影响,但它可能用于真正的偏执安全原因。

  • ZapJNIHandleArea:此选项将使用 0xFEFEFEFE zaps 释放的 JNI 处理空间。它仅具有调试值。

  • ZapUnusedHeapArea:此选项将使用 0xBAADBABE zaps 未使用的堆空间。它可能用于安全原因。

  • Verbose:此选项从其他模式打印额外的调试信息。它是开发 HotSpot 的主要日志选项。

  • UsePrivilegedStack:此选项启用安全 JVM 功能。默认情况下是 true,但在开发模式下,您仍然有机会以禁用安全的方式运行 HotSpot。

  • MemProfiling:此选项将内存使用分析写入日志文件。默认情况下是 false,可以用于一些内存分析问题。

  • VerifyParallelOldWithMarkSweep:此选项将使用MarkSweep GC 代码来验证并行老代的阶段。在更改 JVM 内存处理机制时,此选项可能用于调试目的。

  • ScavengeWithObjectsInToSpace:此选项非常有趣。Java 使用两空间 GC,在幸存空间方面,此选项允许在 to_space 包含对象时进行清除。在这样做的时候,如果启用了 ZapUnusedHeapArea,它还会清除一个未使用的区域。

  • FullGCALot:此选项强制在从运行时系统退出时进行完全 GC(N=FullGCALotInterval)。这可能是一个非常昂贵的操作,但一些开发者可能将其构建到用于桌面应用的 JDK 中。它可能比使用交换空间吸收不断增长的堆中的兆字节更便宜。

  • AdaptiveSizePolicyReadyThreshold:此选项是在开始自适应大小调整之前的收集次数。默认值是 5,但在桌面系统中,将其设置为 1 可能更有意义,因为这些系统的最大瓶颈是交换空间,特别是如果在一台机器上同时运行多个 Java 程序的话。

  • EagerInitialization: 这个选项如果可能的话会积极初始化类。默认值是false,所以可能不安全将其打开。但这个想法似乎很好,尤其是在服务器机器上。

  • GuaranteedSafepointInterval: 这个选项保证每隔几毫秒(0表示没有)至少有一个安全点。默认值是1000。它可以用来调整停止世界状态问题。选项值越大,这些停止的时间越长;如果我们把值设置得太小,将会出现过多的不必要的停止。

  • MaxTrivialSize: 这个选项是内联平凡方法的最大字节码大小。默认值是6。它与 C++编译器的内联选项类似,但针对字节码编译器。

  • MinInliningThreshold: 这个选项是方法需要的最小调用次数才能进行内联。默认值是250

  • SharedOptimizeColdStartPolicy: 这个选项是SharedOptimizeColdStart的重排序策略。0值优先考虑类加载时的局部性,1使用平衡策略,而2则优先考虑运行时局部性。

默认值是2,很少需要更改它,但在某些情况下,如果你的应用程序启动后有太多可能加载的类,将其设置为1可能是有意义的。

将新的内联函数添加到 HotSpot

内联函数是一个由编译器特别处理的函数。通常这意味着函数调用被自动生成的指令所替换。这与内联函数非常相似,但编译器对内联函数了解得更多,因为它们是编译器本身的一部分,所以它可以更明智地使用它们。

内联函数通常比原生函数更注重性能,因为没有 JNI 开销。

准备工作

要开始,我们只需要安装 NetBeans IDE 用于代码编辑和 OpenJDK 源代码。用户应该能够阅读 C++代码,并且对汇编语言有一点了解会有所帮助。

值得检查一下 CPU 是否支持 SSE4.2(一个包含六个新命令的扩展指令集,主要用于字符搜索和比较)。这个指令集在 2009 年随着 Core i7 英特尔芯片的推出而引入,所以如果你使用的是英特尔 CPU,它应该存在。AMD 第一次引入是在 2011 年的 Bulldozer 芯片上,所以你应该有相对较新的芯片来支持它。如果你的 CPU 不兼容该指令,请不要担心。这个方法适用于你可能想要引入的任何内联函数;除了你想要内联的代码的实际实现之外,没有区别。

如何做...

添加新的内联函数不是一个简单的过程。仔细遵循这些说明。确保在每一步之后编译代码;这样做可能会节省一些时间。

我们将要内联的指令是 CRC32 计算,它由java.util.zip.CRC32实现。

首先,让我们对负责 CRC32 计算的 Java 类进行一个小修改。我们将添加一个将被 HotSpot 内建的方法。打开jdk/src/share/classes/java/util/zip/CRC32.java文件,并添加一个新的方法doUpdateBytes

private static int doUpdateBytes(int crc, byte[] b, int off, int len) {
    return updateBytes(crc, b, off, len);
}

该实现只是调用当前使用的updateBytes本地方法。这是 Java 中唯一的更改。其余的将是 HotSpot 的 C++内部实现。

打开hotspot/src/share/vm/precompiled/precompiled.hpp文件,并在其中添加以下行:

#include "smmintrin.h"

smmintrin.h文件包含 GCC 内建函数,这些函数将用于我们 CRC32 函数的实现。

然后,因为我们使用 SSE4.2 指令,我们需要通知编译器。为此,打开hotspot/make/linux/makefiles/gcc.make文件(假设你在 Linux 上构建),找到包含CFLAGS += -fno-rtti的行。就在该行之后,添加-msse4.2标志,使其看起来像这样:

CFLAGS += -fno-rtti # locate that line
CFLAGS += -msse4.2  # add this new line here

现在我们准备在 C++中实现我们的 CRC32 函数。在hotspot/src/cpu/x86/vm/文件夹中,创建CRC32Calc类和static_calcCrc32静态方法。以下是包含类声明的CRC32Calc.hpp文件:

#ifndef CRC32CALC_HPP
#define  CRC32CALC_HPP

class CRC32Calc {
public:
    CRC32Calc() {};
    virtual ~CRC32Calc() {};

    static int static_calcCrc32(int crc, const char* data, int dataOffset, int dataLen);
};
#endif  /* CRC32CALC_HPP */

CRC32Calc.cpp文件及其实现如下所示:

#include "CRC32Calc.hpp"
#include "precompiled.hpp"
int CRC32Calc::static_calcCrc32(int crc, const char* data, int dataOffset, int dataLen) {
    const int dataSize = dataLen - dataOffset;
    int result = crc;
    int uints32 = (int)(dataSize / sizeof(int));
    int units8 = dataSize % sizeof(int);

    const int* pUint32 = (const int*)data;
    while (uints32--) {
        result = ::_mm_crc32_u32(result, *pUint32);
        pUint32++;
    }

    const char* pUnit8 = (const char*)pUint32;
    while (units8--) {
        result = ::_mm_crc32_u8(result, *pUnit8);
        pUnit8++;
    }

    return result;
}

以下指令告诉 HotSpot 如何内建我们的方法。

定位到hotspot/src/share/vm/classfile/vmSymbols.hpp文件。这是包含所有内建函数声明的文件,并向其中添加以下定义:

do_class(java_util_zip_crc32,      "java/util/zip/CRC32")                                                        \
do_intrinsic(_crc32_doUpdateBytes, java_util_zip_crc32, doUpdateBytes_name, int_byteArray_int_int_signature, F_R)  \
do_name(     doUpdateBytes_name,                                 "doUpdateBytes")                                  \
do_signature(int_byteArray_int_int_signature,             "(I[BII)I")                                              \

这是将 Java 方法与将在运行时替换它的代码映射的内建函数声明。添加时要小心。它基于宏,这意味着,如果存在拼写错误或其他错误,将很难找出问题所在。

下一步是定义我们将为内建函数生成哪种代码。在这里我们不会特别聪明,因为这只是一个练习,看看功能是如何工作的。所以我们的汇编器将要做的只是生成对 C 函数的调用。将以下内容添加到hotspot/src/cpu/x86/vm/stubGenerator_x86_64.cpphotspot/src/cpu/x86/vm/stubGenerator_x86_32.cpp文件中:

#include "CRC32Calc.hpp"

现在有点棘手,需要一些底层代码。我们将告诉 HotSpot 如何为我们的方法生成汇编代码。要做到这一点,将generator方法添加到在hotspot/src/cpu/x86/vm/stubGenerator_x86_64.cpphotspot/src/cpu/x86/vm/stubGenerator_x86_32.cpp中声明的StubGenerator类中,分别对应 x86_64 和 x86 架构。该方法的代码如下:

  // Arguments:
  //
  // Inputs:
  //   c_rarg0   - input crc
  //   c_rarg1   - byte array with data for calculation
  //   c_rarg2   - offset in the input array
  //   c_rarg3   - number of data bytes after offset
  //
  // Output:
  //    eax - result crc
  address generate_crc32_doUpdateBytes() {
    __ align(CodeEntryAlignment);
    StubCodeMark mark(this, "StubRoutines", "crc32_doUpdateBytes");
    address start = __ pc();

    __ enter(); // required for proper stackwalking of RuntimeStub frame
    __ pusha();
    // no need to put params in regr - they are already there
    // after this call rax should already have required return value
    __ call_VM_leaf(CAST_FROM_FN_PTR(address, CRC32Calc::static_calcCrc32), 4);
    __ popa();

    __ leave(); // required for proper stackwalking of RuntimeStub frame
    return start;
  }

现在我们需要一个变量来包含生成方法的地址。为此,将以下静态成员声明添加到hotspot/src/share/vm/runtime/stubRoutines.hpp文件中:

static address _crc32_doUpdateBytes;

向同一文件添加以下方法,该方法仅返回声明的变量值:

static address crc32_doUpdateBytes() { return _crc32_doUpdateBytes; }

然后,在hotspot/src/share/vm/runtime/stubRoutines.cpp中,为_crc32_doUpdateBytes分配一个默认值:

address StubRoutines::_crc32_doUpdateBytes = NULL;

然后,在hotspot/src/cpu/x86/vm/stubGenerator_x86_64.cpphotspot/src/cpu/x86/vm/stubGenerator_x86_32.cpp中,找到generate_all方法,并将以下值赋给变量_crc32_doUpdateBytes

StubRoutines::_crc32_doUpdateBytes = generate_crc32_doUpdateBytes();

下一步是添加创建描述符的方法。描述符是我们函数的定义——它需要多少个参数,它接受哪些类型的参数,等等。第一步是将方法声明添加到hotspot/src/share/vm/opto/runtime.hpp文件中的OptoRuntime类中:

static const TypeFunc* crc32_Type();

这将是创建我们方法调用类型信息的函数——它描述了参数并返回参数。实现后,它创建了一个输入参数的类型数组以及返回值的类型。将其放置在hotspot/src/share/vm/opto/runtime.cpp文件中:

const TypeFunc* OptoRuntime::crc32_Type() {
  // create input type (domain): int, pointer, int, int
  int num_args      = 4;
  int argcnt = num_args;
  const Type** fields = TypeTuple::fields(argcnt);
  int argp = TypeFunc::Parms;
  fields[argp++] = TypeInt::INT;      // crc
  fields[argp++] = TypePtr::NOTNULL;  // data
  fields[argp++] = TypeInt::INT;      // offset
  fields[argp++] = TypeInt::INT;      // len
  const TypeTuple* domain = TypeTuple::make(TypeFunc::Parms+argcnt, fields);

  // create return value
  fields = TypeTuple::fields(1);
  fields[TypeFunc::Parms+0] = TypeInt::INT;

  const TypeTuple* range = TypeTuple::make(TypeFunc::Parms+1, fields);

  return TypeFunc::make(domain, range);
}

现在我们将实现内联代码的方法。在hotspot/src/share/vm/opto/library_call.cpp文件中,找到LibraryCallKit类的定义,并添加以下方法声明:

bool inline_crc32();

同样,在同一个文件中,添加实现:

bool LibraryCallKit::inline_crc32() {
  address stubAddr = StubRoutines::crc32_doUpdateBytes();
  const char *stubName = "crc32_doUpdateBytes";
  Node* inputCrc = argument(0);
  Node* in_data  = argument(1);
  Node* offset   = argument(2);
  Node* len      = argument(3);

  // Call the stub.
  make_runtime_call(RC_LEAF|RC_NO_FP, OptoRuntime::crc32_Type(),
                    stubAddr, stubName, TypePtr::BOTTOM,
                    inputCrc, in_data, offset, len);

  return true;
}

最后告诉 HotSpot 我们确实想要内联我们的方法调用,并调用内联方法inline_crc32

要告诉 HotSpot 我们想要内联该方法,hotspot/src/share/vm/opto/library_call.cpp文件中的Compile::make_vm_intrinsic方法必须返回一个非空指针到CallGenerator。要做到这一点,在该方法的switch(id)选择语句中添加以下行:

  case vmIntrinsics::_crc32_doUpdateBytes:
    break;

并非严格要求有那个 case 和 break,默认设置也完全可以;但它使得我们使用内建方法进行 CRC32 计算方法更为明确。

然后,为了调用内联方法,在相同的hotspot/src/share/vm/opto/library_call.cpp文件中,定位LibraryCallKit::try_to_inline,找到switch (intrinsic_id()),并添加以下代码行:

case vmIntrinsics:: _crc32_doUpdateBytes: return inline_crc32();
new line.

它是如何工作的…

要检查方法是否内联,使用 Java 参数-XX:+PrintCompilation-XX:+PrintInlining。要查看内建函数编译成了什么,使用-XX:+PrintAssembly(当在产品构建上运行时,应在前面加上-XX:+UnlockDiagnosticsVMOptions)。

还有更多…

要检查是否支持 SSE4.2,只需编译并运行以下代码:

// This is Linux version
#include <cpuid.h>T
#include <stdio.h>
void main () {
    unsigned int eax, ebx, ecx, edx;
    __get_cpuid(1, &eax, &ebx, &ecx, &edx);
    if (ecx & bit_SSE4_2)
        printf ("SSE4.2 is supported\n");
    return;
}

// And this is the version for windows
#include <intrin.h>
int _tmain(int argc, _TCHAR* argv[])
{
  int cpuInfo[4] = { -1 };
  __cpuid(cpuInfo, 1);
  bool bSSE42Extensions = (cpuInfo[2] & 0x100000) || false;
  if (bSSE42Extensions) {
    printf("SSE4.2 is supported\n");
  }
  return 0;
}

有很多内建方法。参见library_call.cppvmSymbols.hpp

  • Object.getClass给出一个或两个指令。

  • 当操作数是常量时,Class.isInstanceClass.isAssignableFrom与字节码实例一样便宜,否则不会比 aastore 类型检查更昂贵。

  • 大多数单比特类查询都很便宜,甚至可以常数折叠。

  • 反射数组创建与newarrayanewarray指令一样便宜。

  • Object.clone在 Java6 之后与Arrays.copyOf共享代码,且成本较低。

Java 不是唯一使用内建的编程语言,它们在 C++的 SSE 操作中也广泛使用。

有趣的是,_mm_crc32_u32_mm_crc32_u8 本身就是内联函数,由 GCC 或 MS 编译器所知,在编译后的代码中直接被汇编指令替换。

从源代码构建 VisualVM

VisualVM 是一个开源项目,它不是 OpenJDK 的一部分。它是一个强大的工具,对任何使用基于 JDK 的应用程序的人来说都很有帮助。它允许我们监控系统参数,浏览堆转储,创建线程转储等等。由于该工具是开源的,因此可以获取源代码并根据需要对其进行定制,或者只是简单地看看它是如何工作的。本食谱将介绍下载源代码并从中构建 VisualVM 所需的步骤。

准备工作

本食谱需要安装了 Subversion 和 Ant 的机器。此外,由于 VisualVM 是一个图形应用程序,需要一个图形环境来运行它。可以在不启动应用程序的情况下执行构建。

如何操作...

第一步是获取源代码:

  1. 为源文件创建一个文件夹,例如,/home/user/visualvm

  2. 前往新创建的文件夹,假设您需要从trunk获取源代码,请运行以下命令:

    svn checkout https://svn.java.net/svn/visualvm~svn/trunk
    
    
  3. 这将在当前目录中创建一个包含源代码的trunk文件夹。

首先,我们需要下载 NetBeans 平台的二进制文件。所需版本取决于我们将要构建的 VisualVM 版本。在这个例子中,我们将使用trunk,当前的开发版本,它需要 NetBeans 平台 v.8;但鉴于这可能会改变,建议使用链接visualvm.java.net/build.html咨询适当的版本页面。这些二进制文件可以直接从 VisualVM 网站获取,而不是从 NetBeans 网站获取。在这个例子中,URL 是java.net/projects/visualvm/downloads/download/dev/nb80_visualvm_27062014.zip。当文件下载完成后,将其解压到trunk/visualvm文件夹中,如下所示:

  1. 现在,执行 Ant 以运行构建。

    ant build-zip
    
    
  2. 构建完成后,我们应该在命令提示符中看到以下类似的输出:

    BUILD SUCCESSFUL
    Total time: 34 seconds
    
    

    这表示构建成功。如果我们只需要运行 VisualVM,这一步不是必需的,因为 Ant 也会运行构建目标;但如果不需要运行,只需要构建,这一步可能很有用。

  3. 要运行 VisualVM,请运行以下命令:

    ant run
    
    
  4. 如果应用程序尚未构建,那么 Ant 将首先构建它,然后运行它。由于 VisualVM 是一个 GUI 应用程序,我们将看到以下屏幕:如何操作...

这是 VisualVM 的登录屏幕。我们能看到它意味着应用程序已构建并正常工作。包含分发的压缩存档文件可以在visualvm/dist文件夹中找到。

参见

  • 关于 VisualVM 构建的更多信息可在主页visualvm.java.net/build.html上找到。每个版本都有一个不同的页面,因为构建说明因版本而异。例如,每个构建可能需要 NetBeans 平台的不同版本。

为 VisualVM 创建插件

VisualVM 只是一个具有预定义和有限功能的应用程序。它是一个框架,这意味着可以扩展它。VisualVM 提供扩展的方式是通过插件 API,这允许我们创建新的插件,然后通过应用程序访问它们。这样的插件可以执行各种操作,但主要用于提供监控或控制 JVM 应用程序的新方法。

准备工作

当前(在撰写本文时)VisualVM 的分支版本需要 NetBeans 平台和 IDE v.8。因此,我们需要确保当前版本的平台在机器上可用。如果有任何疑问,请检查 VisualVM 分支构建说明页面visualvm.java.net/build/build.html

如何操作...

让我们从我们要监控的内容开始。看起来我们能做的最简单的事情就是构建一个组件,它会更新我们可以读取的数据。例如,看看下面的类:

package org.openjdk.cookbook;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;

public class SleepProbe implements SleepProbeMBean {
    private volatile long lastSleepSampleMs = 100;

    public static void main(String[] args) throws Exception {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        ObjectName name = new ObjectName("org.openjdk.cookbook:type=SleepProbe");
        SleepProbe mbean = new SleepProbe();
        mbean.start();
        mbs.registerMBean(mbean, name);
        System.out.println("Started MBean");
        Thread.sleep(Long.MAX_VALUE);
    }

    @Override
    public long getActualSleepTime() {
        return lastSleepSample;
    }

    public void start() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while ( !Thread.currentThread().isInterrupted() ) {
                    try {
                        final long start = System.nanoTime();
                        Thread.sleep(100);
                        final long end = System.nanoTime();
                        lastSleepSampleMs = (long)((double)(end-start))/1000000;
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        }).start();
    }
}

此代码休眠 100 毫秒并测量实际休眠的时间。这个值不会非常精确,但大约是100。它通过lastSleepSample变量发布最后一次休眠时间的测量结果,该变量可通过SleepProbeMBean接口访问:

package org.openjdk.cookbook;
public interface SleepProbeMBean {
    public long getActualSleepTime();
}

这个类和接口应该放入一个单独的项目中,这样你就可以独立于 VirtualVM 插件项目运行它们:

  1. 要开始,我们需要在 IDE 中创建一个插件项目。启动 IDE,转到文件 | 新建项目,从项目类型中选择NetBeans 平台应用程序如何操作...

  2. 在下一屏上,选择VisualVM作为NetBeans 平台(如果不可用,请参阅后续说明),项目名称和位置,如图所示:如何操作...

  3. 如果VisualVM不在平台列表中,请点击管理图标,在显示的对话框中,通过指向 VisualVM 发行版的文件夹添加一个新的平台,如图所示:如何操作...

  4. 在这个例子中,发行版是从源代码构建的(请参阅从源代码构建 VisualVM配方)。按下一步然后按完成

  5. 现在只需完成向导,你将拥有一个带有一些属性和构建脚本的全新空项目。

  6. 注意,NetBeans 的一些版本中存在一个已知错误(netbeans.org/bugzilla/show_bug.cgi?id=242564),这会导致依赖项问题,并且不允许我们稍后添加所需的依赖项。为了解决这个问题,右键单击项目,然后点击属性。在项目属性对话框中,选择如何操作...

  7. 在平台组件下取消选中JavaFX 包装器。确保所有其他复选框都已选中,包括配置文件visualvm节点。

  8. 现在我们需要为我们的插件创建一个模块。在项目树中的模块项上右键单击并选择添加新…如何操作...

  9. 将其命名为SamplingModule并点击下一步。在下一屏幕上,将net.cookbook.openjdk作为代码名称基础并点击完成。这将创建一个空模块,我们需要在其中添加一些组件。

  10. 下一步是向模块添加依赖项。在模块上右键单击并选择属性,然后转到 | 模块依赖项,并点击添加依赖项。在添加模块依赖项对话框中,将VisualVM放入过滤器字段中,如图所示:如何操作...

  11. 选择VisualVM-ApplicationVisualVM-CoreVisualVM-Tools(在截图上不可见),然后点击确定

  12. 下一步是添加安装程序和一些源代码。为此,在新创建的模块上右键单击并转到新建 | 其他。这将显示一个对话框,可以选择文件类型。点击安装程序/激活器并点击下一步,如图所示:如何操作...

  13. 然后只需通过点击完成来完成向导。这将创建一个名为Installer的类,位于net.cookbook.openjdk包中。目前,请保持该类不变,我们将在后面的步骤中更新它。

  14. 下一步是创建一个为我们绘制图表的组件。为此,我们将创建一个简单的面板,该面板将每半秒刷新一次,使用来自sampler MBean的新样本。在net.cookbook.openjdk包中创建一个新类,并将其命名为SamplingGraphPanel

    package net.cookbook.openjdk;
    
    import com.sun.tools.visualvm.application.Application;
    import com.sun.tools.visualvm.tools.jmx.*;
    import java.awt.*;
    import java.util.LinkedList;
    import javax.management.*;
    import javax.swing.JPanel;
    import org.openide.util.Exceptions;
    
    public class SamplingGraphPanel extends JPanel implements Runnable {
        private static final int MAX_DATA_POINTS = 20;
        private static final int MAX_VALUE = 110;
        private static final int GAP = 30;
    
        private final LinkedList<Long> samples = new LinkedList<Long>();
        private final Application application;
        private Thread refreshThread;
    
        public SamplingGraphPanel(Application application) {
            this.application = application;
            this.setBackground(Color.black);
        }
    
        @Override
        public void paintComponent(Graphics gr) {
            super.paintComponent(gr);
    
            Graphics2D g2 = (Graphics2D)gr;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    
            final double xScale = ((double) getWidth()-2*GAP)/(samples.size()-1);
            final double yScale = ((double) getHeight()-2*GAP)/(MAX_VALUE-1);
    
            Stroke oldStroke = g2.getStroke();
            g2.setColor(Color.green);
            g2.setStroke(new BasicStroke(3f));
            for (int i = 0; i < samples.size()-1; ++i) {
                final int x1 = (int) (i * xScale + GAP);
                final int y1 = (int) ((MAX_VALUE-samples.get(i))*yScale+GAP);
                final int x2 = (int) ((i+1) * xScale + GAP);
                final int y2 = (int) ((MAX_VALUE - samples.get(i+1)) * yScale + GAP);
                g2.drawLine(x1, y1, x2, y2);         
            }
        }
    
    public void start() {
           refreshThread = new Thread(this);
           refreshThread.start();
        }
    
        public void stop() {
            if ( refreshThread != null ) {
                refreshThread.interrupt();
                refreshThread = null;
            }
        }
    
        @Override
        public void run() {
            JmxModel jmx = JmxModelFactory.getJmxModelFor(application);
            MBeanServerConnection mbsc = null;
            if (jmx != null && jmx.getConnectionState() == JmxModel.ConnectionState.CONNECTED) {
                mbsc = jmx.getMBeanServerConnection();
            }
    
            try {
                while ( mbsc != null && !Thread.currentThread().isInterrupted() ) {
                    if ( samples.size() == MAX_DATA_POINTS ) {
                        samples.remove();
                    }
                    Long val = (Long)mbsc.getAttribute(new ObjectName("org.openjdk.cookbook:type=SleepProbe"), "ActualSleepTime");
                    samples.add(val);
                    repaint();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) { break; }
                }
            } catch (Exception e) {
                Exceptions.printStackTrace(e);
            }
        }
    }
    
  15. 此类将每隔 500 毫秒从本食谱第一步中实现的MBean读取一个值,并将其添加到包含样本的列表中。然后它刷新图表,进行重绘。基本上,这段代码只是一个 Java Swing 代码,可以在任何应用程序中运行。这里 VisualVM 特有的部分是一些辅助类,用于从Application对象中获取MBean

  16. 现在,在SamplingModule中创建一个负责显示数据的类。将其命名为SamplingView并将其放入net.cookbook.openjdk包中,如下所示:

    package net.cookbook.openjdk;
    
    import com.sun.tools.visualvm.application.Application;
    import com.sun.tools.visualvm.core.ui.DataSourceView;
    import com.sun.tools.visualvm.core.ui.components.DataViewComponent;
    import javax.swing.*;
    import org.openide.util.Utilities;
    
    public class SamplingView extends DataSourceView {
        private DataViewComponent dvc;
        private SamplingGraphPanel panel;
        public static final String IMAGE_PATH = "net/cookbook/openjdk/icon.png";
    
        public SamplingView(Application application) {
            super(application,"Sampling Application", new ImageIcon(Utilities.loadImage(IMAGE_PATH, true)).getImage(), 60, false);
        }
    
        protected DataViewComponent createComponent() {
            //Data area for master view:
            JEditorPane generalDataArea = new JEditorPane();
            generalDataArea.setBorder(BorderFactory.createEmptyBorder(14, 8, 14, 8));
    
            panel = new SamplingGraphPanel(SamplingProvider.getSleepProbeInstance((Application)getDataSource()));        DataViewComponent.MasterView masterView = new DataViewComponent.MasterView("Sampling Overview", null, generalDataArea);
            DataViewComponent.MasterViewConfiguration masterConfiguration = new DataViewComponent.MasterViewConfiguration(false);
            dvc = new DataViewComponent(masterView, masterConfiguration);
            //Add detail views to the component:
            dvc.addDetailsView(new DataViewComponent.DetailsView("Sampling Graph", null, 10, panel, null), DataViewComponent.TOP_LEFT);
    
            return dvc;
        }
    
        @Override
        protected void removed() {
            super.removed();
            panel.stop();
        }
    }
    
  17. 重要的一点是,由 IMAGE_PATH 引用的文件实际上必须存在,否则插件将无法启动并会抛出异常。您可以做的最简单的事情是从互联网上下载任何可用的免费图标,例如在 www.iconfinder.com/icons/131715/download/png/32 上,并将其放入与 SamplingView 类相同的包文件夹中。

  18. 下一步是创建一个提供者,该提供者将创建视图实例并识别我们连接的应用程序是否由插件支持。在 net.cookbook.openjdk 包中,创建一个名为 SamplingProvider 的类,并具有以下实现:

    package net.cookbook.openjdk;
    
    import com.sun.tools.visualvm.application.Application;
    import com.sun.tools.visualvm.core.ui.*;
    import com.sun.tools.visualvm.tools.jmx.*;
    import javax.management.*;
    import org.openide.util.Exceptions;
    
    public class SamplingProvider extends DataSourceViewProvider<Application> {
        private static DataSourceViewProvider instance = new SamplingProvider();
        @Override
        public boolean supportsViewFor(Application application) {
            boolean result = false;
            JmxModel jmx = JmxModelFactory.getJmxModelFor(application);
            if (jmx != null && jmx.getConnectionState() == JmxModel.ConnectionState.CONNECTED) {
                MBeanServerConnection mbsc = jmx.getMBeanServerConnection();
                if (mbsc != null) {
                    try {
                        mbsc.getObjectInstance(new ObjectName("org.openjdk.cookbook:type=SleepProbe"));
                        result = true; // no exception - bean found
                    }catch (InstanceNotFoundException e) {
                        // bean not found, ignore
                    } catch (Exception e1) {
                        Exceptions.printStackTrace(e1);
                    }
                }
            }
            return result;
        }
    
        @Override
        protected DataSourceView createView(Application application) {
            return new SamplingView(application);
        }
    
        static void initialize() {
            DataSourceViewsManager.sharedInstance().addViewProvider(instance, Application.class);
        }
    
        static void unregister() {
            DataSourceViewsManager.sharedInstance()
                         .removeViewProvider(instance);
        }
    
        public static Object getSleepProbeInstance(Application application) {
            ObjectInstance instance = null;
            JmxModel jmx = JmxModelFactory.getJmxModelFor(application);
            if (jmx != null && jmx.getConnectionState() == JmxModel.ConnectionState.CONNECTED) {
                MBeanServerConnection mbsc = jmx.getMBeanServerConnection();
                if (mbsc != null) {
                    try {
                        instance = mbsc.getObjectInstance(new ObjectName("org.openjdk.cookbook:type=SleepProbe"));
                    } catch (InstanceNotFoundException e) {
                        // bean not found, ignore
                    } catch (Exception e) {
                        Exceptions.printStackTrace(e);
                    }
                }
            }
            return instance;
        }
    }
    
  19. 该类的主要方法为 supportsViewForcreateViewcreateView 方法很小且简单,它只是创建一个视图实例,并通过应用程序传递,以便视图可以从其中获取数据。supportsViewFor 类稍微大一些,但它并不真正匹配。它通过 JMX 连接到指定的应用程序,并尝试获取我们插件感兴趣的 MBean 实例。如果 MBean 不存在,则表示该应用程序不受支持,方法返回 false

  20. 现在是时候看看插件是如何工作的了。为此,首先启动本食谱第一步创建的应用程序。然后右键单击 SamplingModule 并选择 运行。这将启动带有我们插件的 VisualVM。从 VisualVM 中的进程列表中选择我们的进程,并点击 Sampling Application 选项卡。在那里,您将看到我们的图表,显示睡眠时间略有变化,如下面的截图所示:如何操作...

在遵循这些步骤之后,读者应该能够扩展此示例,并在 VisualVM 中使用任何他们想要的监控应用程序。

参考信息

VisualVM 网站上有一些文档可供参考,这些文档有助于创建 VisualVM 插件,并详细介绍了在此食谱中使用的一些类,请参阅 visualvm.java.net/api-quickstart.html

值得一看的是现有的插件源代码和一些示例。这些可以在 <code_root>/plugins<code_root>/samples 分别找到。有关如何下载源代码的说明,请参阅 从源代码构建 VisualVM 的食谱。

从 AdoptOpenJDK 项目中获得收益

AdoptOpenJDK 是一个项目,最初由一小群爱好者开发,但后来成为官方 OpenJDK 社区的一部分。其中一些目的是澄清和简化 OpenJDK 的构建、安装和使用,但还有更多。它提供构建自动化、报告生成器、构建测试等。我们将介绍一些对每个人都有用的基本功能。

准备工作

要遵循这个食谱,我们需要安装一个 OpenJDK 实例,并建立互联网连接。

如何操作...

AdoptOpenJDK 是一个非常有帮助且非常复杂的项目集,包含许多不同的子项目。其中许多与测试和宣传有关,所以我们无法在这里作为食谱包含它们。

可视化 JIT 日志

在 AdoptOpenJDK 中有一个名为 Jitwatch 的项目。它的目的是可视化 JIT 编译器的日志。它有助于发现我们项目中的一些性能缺陷,并检查原生汇编输出,因为毕竟这很有趣。以下是一些要点:

  1. 首先,让我们下载一个可执行的 jar 文件www.chrisnewland.com/images/jitwatch.jar

  2. 为了分析一个可执行文件,我们需要用以下开关运行它:

    -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly
    

    UnlockDiagnosticVMOptions参数提供了访问其他诊断选项的权限,例如SharedOptimizeColdStartPauseAtStartup等等。

    LogCompilation详细记录编译活动到hotspot.logLogFile,这是另一个 VM 选项。

    TraceClassLoading参数让 JVM 确保所有加载的类都是可见的,即使是没有与之相关的任何 JIT 编译代码的类。

    PrintAssembly参数让我们看到 JIT 编译的汇编输出。它使用hsdis,这是一个 HotSpot 反汇编器,它是 OpenJDK 的一部分。

  3. 启动分析器:

     java -jar ./jitwatch.jar
    
    

    你将看到以下屏幕:

    可视化 JIT 日志

  4. 打开hotspot.log,然后按开始

享受体验。

保护你的 javadoc

  1. 我们将尝试消除似乎存在于 Java 7u22 之前版本中的 javadoc 漏洞(CVE-2013-1571)。

  2. 要做到这一点,我们需要克隆一个仓库,例如:

    git clone https://github.com/AdoptOpenJDK/JavadocUpdaterTool.git && cd JavadocUpdaterTool
    
    

    构建项目

    mvn clean install
    
    
  3. 我们将从包含JavadocPatchTool.jar文件的目录中运行以下命令来扫描潜在漏洞:

    java -jar JavadocPatchTool.jar -R -C <directory>
    
    

    如果工具找到任何适用 HTML 文件,它将打印出此类文件的列表。

  4. 要修复单个适用文件,我们将运行以下命令:

    java -jar JavadocPatchTool.jar

    这里,<path>是包含适用文件的目录的路径。

  5. 要修复树中所有适用文件,请运行以下命令:

    java -jar JavadocPatchTool.jar -R <path_to_tree>
    
    

    小贴士

    要了解 AdoptOpenJDK 项目带来的更多好处,请访问java.net/projects/adoptopenjdk/pages/Benefits的相关页面。

它是如何工作的...

JIT 日志可视化器只是一个处理由 OpenJDK 生成的日志的工具。但它是一个非常有用的工具,可能会显著提高性能。

Java 被用于各种需要安全性的项目中,因此 javadoc 中的漏洞对仍然使用 Java 6 且无法将其更改为 Java 7 的用户产生了影响。因此,无需技术转换即可修复它的工具确实非常宝贵。

还有更多...

AdoptOpenJDK 中有更多项目。其中大多数是传教士或测试性质的项目;因此,它们略超出了本书的范围。

然而,你总可以在各种 AdoptOpenJDK 网站上找到它们。本书无需提及所有这些项目,它们之间紧密相连。为了找到它们,只需四处看看就足够了。

第九章:测试 OpenJDK

在本章中,我们将涵盖以下主题:

  • 使用下载或构建的 jtreg 版本运行测试

  • 从源代码构建 jtreg

  • 运行标准套件的 OpenJDK 测试

  • 为 jtreg 编写自己的测试

  • 在 GUI 模式下使用 jtreg

  • 为 jtreg 编写 TestNG 测试

  • 从源代码编译 JT Harness

  • 构建 和 运行 jcstress

  • 为 jcstress 编写测试

  • 使用 JMH 创建基准测试项目

  • 下载源代码并编译 JMH

简介

回到 1997 年,当 JDK 1.1 完成,JDK 1.2 刚刚开始时,有一个明显的问题——JDK 必须以某种方式被测试,并且必须有一个工具来完成这项工作。这就是 jtreg 作为回归测试工具出现的时刻。当时,Java 的测试框架并不多。事实上,甚至没有多少软件是用 Java 编写的。所以选择非常有限,唯一合理的选项是当时用于Java 兼容性工具包JCK)的框架。它的名字叫JavaTest。但是,由于 JCK 测试与 JDK 回归测试应该完成的任务非常不同,该框架需要进行一些调整,这就是 jtreg 出现的时候。到目前为止,尽管自 1997 年以来已经过去了许多年,jtreg 仍然仍然是运行 OpenJDK 中单元和回归测试的主要工具。自从它在 OpenJDK 中引入以来,已经创建了超过 10,000 个使用 jtreg 框架运行的测试。

作为测试工具,jtreg 对于习惯了 JUnit 和 TestNG 等工具的现代开发者来说可能看起来有些不寻常。jtreg 看起来不同的主要原因是因为它有着悠久的历史。它在 1997 年出现,而 jUnit 大约在 2000 年左右出现。在当时,尤其是在技术如此年轻的时候,三年是一个很长的时间。似乎还有一个可能的原因是,jtreg 长期以来一直是一个专有工具,并且由于它一直在履行其职责,因此没有必要对其进行更改。此外,它并没有向广大的开源社区开放,这些社区本可以在一段时间内改变其形态。与当前经典工具相比,导致其差异的另一个原因是它提供了一些通常不在测试框架中可用,但在进行 JDK 测试时必需的特殊功能。这些功能包括在具有特定参数的单独 JVM 实例(或对于某些测试,甚至几个实例)中运行测试,测试 Java 小程序(还记得它们吗?),将 shell 脚本作为测试运行,运行需要用户交互的 GUI 测试等等。这是一套相当大的附加功能,足以证明为它构建一个单独的框架是合理的。

话虽如此,说 jtreg 是某种过时的工具,它停滞在 20 世纪 90 年代,并且没有尝试改变自己以更接近现代软件测试框架构建方式,这并不公平。它已经与测试框架如TestNG集成,并提供了一种基于该框架创建测试的方法。然而,JDK 中的大多数测试仍然是只有 main 方法的类,由框架执行。尽管如此,公平地说,这种做法有其优点,因为它允许一个人在没有框架的情况下运行单个测试。还有一些测试只是批处理文件,并且正在进行努力以消除它们。

作为从 JavaTest 演变而来的工具,jtreg 继承了与其框架的兼容性。这种兼容性现在已被隔离到一个名为Java Test HarnessJT Harness)的独立项目中。这是一个用于运行、构建和部署测试套件的框架。它还提供了一个 GUI 来管理和执行测试套件。

在本章中,你将学习到足够的内容,成为一个自信的 jtreg 用户,并了解如何使用 JT Harness。你将了解到如何从源代码构建 jtreg 和 JT Harness,如何运行测试,以及如何编写自己的测试。本章仅涵盖纯 Java 和 TestNG 测试,因为它们对于 OpenJDK 开发者来说最有用。本章不涵盖 shell 测试的使用,因为它们的用法不被认为是良好的实践,并且它们是为了解决几年前存在的 JVM 限制而创建的。在当前时刻,所有 OpenJDK 贡献者都被鼓励尽可能地将 shell 测试替换为 Java 版本。

使用下载或构建的 jtreg 版本运行测试

开始使用 jtreg 的最简单方法是下载它,解压它,并运行一些测试。在这个食谱中,我们将做的是确切地这样做,而不会做任何额外的事情,比如从源代码构建它或尝试创建我们自己的测试。

准备工作

对于这个食谱,实际上不需要太多东西——只需要互联网连接、安装或构建了 OpenJDK 的机器,以及 OpenJDK 源代码。在 Windows 环境中,必须在您的机器上安装 Cygwin。

如何操作...

以下是一些简单的步骤,以便通过 jtreg 执行一组测试:

  1. 如果 jtreg 尚未在机器上可用,请访问 jtreg 的官方网站(adopt-openjdk.ci.cloudbees.com/job/jtreg/lastSuccessfulBuild/artifact/)并下载 jtreg 的最新可用版本。另一种选择是从源代码构建它。为此,请遵循本章后面将涵盖的“从源代码构建 Jtreg”食谱中的说明。在你下载了 jtreg 或使用源代码构建了它之后,继续下一步。

  2. 将下载的存档解压到一个文件夹中。

  3. 在 OpenJDK 源代码树的根目录下,创建一个名为run_test.sh的 shell 脚本。这个脚本将用于运行 jtreg:

    #!/bin/sh
    export JT_JAVA=/etc/alternatives/java_sdk_1.7.0_openjdk/
    /home/user/openjdk/jtreg/bin/jtreg -jdk:$JT_JAVA -agentvm -automatic -verbose:summary -w build/jtreg/work -r build/jtreg/report hotspot/test/compiler/5091921
    
    

    这里唯一需要更改的是JT_JAVA环境变量,它必须指向高于或等于 1.5 版本的 OpenJDK。

  4. 运行脚本后,您将看到如下输出:

    [user@localhost jdk7u_clean]$ ./run_test.sh 
    Passed: compiler/5091921/Test5091921.java
    Passed: compiler/5091921/Test6186134.java
    Passed: compiler/5091921/Test6196102.java
    Passed: compiler/5091921/Test6357214.java
    Passed: compiler/5091921/Test6559156.java
    Passed: compiler/5091921/Test6753639.java
    Passed: compiler/5091921/Test6850611.java
    Passed: compiler/5091921/Test6890943.java
    Passed: compiler/5091921/Test6897150.java
    Passed: compiler/5091921/Test6905845.java
    Passed: compiler/5091921/Test6931567.java
    Passed: compiler/5091921/Test6935022.java
    Passed: compiler/5091921/Test6959129.java
    Passed: compiler/5091921/Test6985295.java
    Passed: compiler/5091921/Test6992759.java
    Passed: compiler/5091921/Test7005594.java
    Passed: compiler/5091921/Test7020614.java
    Test results: passed: 17
    
    
  5. 测试完成后,运行由 jtreg 生成的 HTML 报告。这个报告可以在由-r参数指定的文件夹中找到。

它是如何工作的…

正如您所看到的,用于运行 jtreg 的 shell 脚本很简单,只需要澄清几个要点。这些是JT_JAVA环境变量和 jtreg 的命令行参数。

JT_JAVA是 jtreg shell 脚本使用的两个环境变量之一。JT_JAVA指定了将用于运行框架的 Java 版本,但不包括测试。在这个菜谱中,为了简单起见,我们使用了与 jtreg 和测试相同的 Java 版本。

jtreg 的命令行参数在 jtreg 网页上都有详细描述(openjdk.java.net/jtreg/command-help.html),所以我们将只介绍在这个菜谱中使用的一些参数:

  • -jdk:这个参数将生成用于运行测试的 JDK。基本上,这是测试运行时被测试的 Java 版本。在我们的例子中,我们使用了安装在机器上的版本。如果您想使用从源代码构建的版本,变量应该相应地更改以指向构建的输出。

  • -agentvm:这是 jtreg 使用可重用 JVM 池来运行测试的模式。当一个测试需要一个单独的 JVM 来运行时,不会创建新的 JVM,而是从可重用实例池中借用。如果没有指定任何参数,jtreg 将为每个测试重新创建一个 JVM,这将显著减慢测试运行速度。

  • -verbose:summary:这个参数指定了输出模式。summary参数意味着它将只打印状态和测试名称。

  • -automatic:这个参数意味着只有不需要用户干预的自动测试将被运行。

  • -w:这个参数提供了工作目录的位置。这将用于存储类文件等。

  • -r:这个参数提供了报告目录,报告将存储在这个目录中。要查看报告,请在任何浏览器中打开<reporting directory>/html/report.html文件。

参见

  • 完整的命令行选项列表可在openjdk.java.net/jtreg/command-help.html找到。

  • 有些人可能发现只运行特定错误的测试很有用,为此,可以使用 bug:<bug_id> 命令行选项。测试的日志文件可以在工作目录中找到(由 -w 参数指定或在未定义该参数时在 JTwork 中),日志文件以 .jtr 扩展名的文本格式存在。这些文件包含测试输出以及命令行和抛出的异常,对于故障排除非常有用。

从源代码构建 jtreg

除了下载 jtreg 作为二进制包之外,还可以选择下载源代码并从源代码构建 jtreg。这对于可能想要修改源代码或获取尚未作为二进制包发布的最新修复的开发者来说可能是有益的。

准备工作

你需要一个互联网连接,一台能够运行 make 和批处理文件(Linux 或 Cygwin)的机器,以及已安装的 Mercurial。

如何做...

以下简单步骤将展示如何获取 jtreg 源代码并构建:

  1. hg.openjdk.java.net/code-tools/jtreg 下载源代码。为此,只需执行以下命令,该命令将在本地文件夹中克隆 jtreg 源代码树,jtreg

    [user@localhost tmp]$ hg clone http://hg.openjdk.java.net/code-tools/jtreg
    destination directory: jtreg
    requesting all changes
    adding changesets
    adding manifests
    adding file changes
    added 85 changesets with 1239 changes to 602 files
    updating to branch default
    586 files updated, 0 files merged, 0 files removed, 0 files unresolved
    
    

    命令执行后,当前目录可以在新的 jtreg 文件夹中找到,其中包含所有 jtreg 源代码。

  2. 如果机器上没有安装 Ant 软件,可以使用 yum(或任何其他打包工具)安装它,或者简单地从 ant.apache.org/ 下载它,然后解压。如果你使用的是 Linux 机器,你也可以选择通过运行 yum 或任何其他可以在 ant.apache.org/ 找到的类似工具来安装它,然后解压。

  3. 下载 JT Harness 的最新可用版本,它可在 jtharness.java.net/ 找到。将其解压到 jtreg/lib 文件夹中。

  4. jtreg 需要 JUnit,但不一定是最新版本。版本必须早于 4.11。最简单的方法是从 Maven central 在 mvnrepository.com/artifact/junit/junit/4.5 下载该版本。jtreg 构建只需要 JAR 文件。将此文件放入 jtreg/lib 文件夹中。

  5. 为了让事情变得更有趣,jtreg 构建还需要另一个测试框架——TestNG v.6.8。可以从 testng.org/doc/download.html 下载。

    注意,所需的版本是针对 Ant 用户版本的。为了简化操作,只需使用链接,testng.org/testng-6.8.zip。将下载的存档解压到 jtreg/lib 文件夹中。

  6. 下一个依赖项是 JavaHelp。这似乎只能通过直接链接在 download.java.net/javadesktop/javahelp/javahelp2_0_05.zip 获取。将其解压缩到 jtreg/lib 文件夹中。

  7. 然后,最后,最后一个依赖项是 Xalan,XML 转换库。所需版本是 2.7.1,可以从以下网站之一下载:www.apache.org/dyn/closer.cgi/xml/xalan-j。按照您对其他库所做的相同程序,将其解压缩到 jtreg/lib 文件夹中。

  8. 现在,是时候编写执行构建的脚本了。将以下脚本代码放入在第一步中创建的 jtreg 文件夹的 make.sh 文件中:

    #!/bin/sh
    export JDK15HOME= /etc/alternatives/java_sdk_1.7.0_openjdk/
    export JAVAHELP_HOME=/home/user/openjdk/jtreg/lib/jh2.0/javahelp
    export ANTHOME=/usr/share/ant
    export JTHARNESS_HOME=/home/user/openjdk/jtreg/lib/jharness4.4.1
    export JUNIT_JAR=/home/user/openjdk/jtreg/lib/junit4.5/junit-4.5.jar
    export TESTNG_HOME=/home/user/openjdk/jtreg/lib/testng-6.8
    export TESTNG_JAR=/home/user/openjdk/jtreg/lib/testng-6.8/testng-6.8.jar
    export XALANHOME=/home/user/openjdk/jtreg/lib/xalan-j_2_7_1
    make -C make
    
    

    如您所见,脚本很简单,只需设置环境变量。不需要太多解释,因为所有变量名都很直观。所以,只需为您的机器设置分配适当的值。所有变量都是强制性的,必须定义才能运行框架。

  9. 最后一步就是运行那个脚本:

    [user@localhost jtreg]$ ./make.sh
    
    

    构建(应该只需几秒钟)完成后,生成的 JAR 文件可以在 build/images/jtreg/ 文件夹中找到。这个文件夹将包含一个完全可工作和自给自足的 jtreg 发行版:

    [user@localhost jtreg]$ ls -l build/images/jtreg/
    total 60
    drwxrwxr-x. 2 user user 4096 May  3 21:27 bin
    -rw-rw-r--. 1 user user 994 May  3 21:23 COPYRIGHT
    drwxrwxr-x. 3 user user 4096 May  3 21:23 doc
    drwxrwxr-x. 4 user user 4096 May  3 21:27 legal
    drwxrwxr-x. 2 user user 4096 May  3 21:27 lib
    -rw-rw-r--. 1 user user 19241 May  3 21:27 LICENSE
    drwxrwxr-x. 3 user user 4096 May  3 21:23 linux
    -rw-rw-r--. 1 user user 3790 May  3 21:27 README
    -rw-rw-r--. 1 user user 72 May  3 21:27 release
    drwxrwxr-x. 3 user user 4096 May  3 21:23 solaris
    drwxrwxr-x. 3 user user 4096 May  3 21:23 win32
    
    

运行 OpenJDK 的标准测试集

这个菜谱与描述简单测试执行的菜谱没有太大区别。然而,它将重点介绍如何运行 JDK 测试。如果有人正在修改 HotSpot 或 OpenJDK 的其他部分,可能需要这些知识。

标准测试仅在 JDK7 的三个根文件夹和 JDK8 的四个文件夹中可用。这些是 hotspotjdklangtoolsnashorn(仅限 JDK8)。尽管其他区域如 CORBA、JDBC、JAXP 等没有测试可用,但这并不意味着它们完全没有经过测试。这只意味着它们的测试不是 OpenJDK 的一部分,也就是说,它们不是由供应商提供的。

测试的组织方式因测试区域的依赖性而异,例如,hotspotlangtools 主要按它们测试的功能区域分组,然后按错误(编号)分组。jdk 文件夹主要按包名组织,因为这个测试集涵盖了 Java API。

请记住,有些测试可能会失败,但这并不意味着 OpenJDK 有什么特别的问题。这只意味着在某些情况下,很难创建在任何环境中都能通过测试的测试。例如,可能有需要特殊网络配置或某些其他特殊环境的测试,而这些可能没有在机器上设置。

有一些已知的测试会失败,通常有很好的理由。最明显的例子是覆盖了一些已知问题的测试,但修复这些问题的方案不会很快出现。这些测试列在jdk/test/ProblemList.txt中,或者用@ignore标签标记。通常,这些测试应该从标准测试运行中排除。

准备中

由于我们将运行 OpenJDK 的测试,机器上需要有相关的源代码。机器还应设置好 OpenJDK 构建环境,因为我们将会使用 OpenJDK 的 make 文件来执行测试运行。

jtreg 应该在机器上的一个文件夹中下载并解压,以便为测试运行做好准备。

一些 OpenJDK 测试是 shell 脚本,因此你需要使用 Linux 机器,或者在 Windows 机器上使用支持 Bourne shell 的 Cygwin,尽管不推荐使用 Cygwin,因为有可能某些 shell 测试无法在它上面正确运行。

如何操作...

运行测试最方便的方式之一是为每个区域单独运行,例如hotspotjdk等。由于本菜谱的目的是仅解释概念,我们将使用jdk测试,这些测试只是 OpenJDK 中所有测试的一个子集,但相同的模式也可以应用于所有其他区域。按照以下步骤操作:

  1. 在 OpenJDK 源根目录下,创建一个名为run_jdk_lang_tests.sh的文件,并包含以下内容:

    #!/bin/sh
    export JTREG_HOME=/home/user/openjdk/jtreg/build/images/jtreg/
    export JT_HOME=/home/user/openjdk/jtreg/build/images/jtreg/
    export PRODUCT_HOME=/home/stas/openjdk/jdk7u_clean/build/linux-amd64/j2sdk-image/
    cd jdk/test
    make TESTDIRS=java/lang
    
    

    JTREG_HOMEJT_HOME环境变量都是相同的,应该指向包含 jtreg 的文件夹。不幸的是,在 make 文件中有地方同时使用了这两个变量。

    PRODUCT_HOME指向测试中的 JDK。并不严格要求它指向从源代码构建的 JDK 版本,但也没有必要在无法更改的版本上执行测试。

    TESTDIRS指向要运行的测试子集。显然,这个子集越广,要执行的测试就越多,运行时间也就越长。所以通常来说,将这个子集限制在合理范围内是有意义的,除非在做出重大更改后需要进行回归测试。

  2. 现在让我们运行脚本。它将在jdk文件夹中执行测试,并输出数百行类似的内容:

    TEST: java/lang/StringBuilder/Insert.java
     build: 1.112 seconds
     compile: 1.112 seconds
     main: 0.161 seconds
    TEST RESULT: Passed. Execution successful
    --------------------------------------------------
    
    
  3. 当一切完成后,make 脚本将报告如下:

    Summary:
    TEST STATS: name=  run=383  pass=383  fail=0  excluded=4
    EXIT CODE: 0
    EXIT CODE: 0
    Testing completed successfully
    
    

前面的输出告诉我们执行了多少测试,有多少失败了,有多少通过了,等等。现在,当我们运行所有感兴趣的测试后,结果可以在jdk/build/linux-amd64/testoutput/JTreport文件夹中找到。将会有标准的 jtreg 文本和 HTML 报告文件,可以使用任何网络浏览器查看。

还有更多...

如果有人到了需要运行 OpenJDK 测试的地步,很可能会有这样的情况,即测试需要更新或扩展。在这种情况下,了解这些测试内部发生的事情以及每个测试存在的确切原因是很重要的。大多数情况下,这些信息都包含在@bug@summary标签中。强烈建议关注它们的内容,并投入一些努力来了解它们如何与实际的测试代码相关联。

大多数测试在@bug@summary标签中包含额外的信息。参考这些标签来理解测试的原因是很重要的。例如,当你运行测试时,看到以下类似的输出并不罕见:

--------------------------------------------------
TEST: java/lang/invoke/7157574/Test7157574.java
 build: 1.194 seconds
 compile: 1.193 seconds
 main: 0.199 seconds
TEST RESULT: Passed. Execution successful

这意味着这是一个针对 ID 为7157574的 bug 的测试,实际上可以在 JDK 错误跟踪系统中找到bugs.openjdk.java.net/browse/JDK-7157574。当查看测试时,以下信息将在标题中:

/* @test
 * @bug 7157574
 * @summary method handles returned by reflective lookup API sometimes have wrong receiver type
 *
 * @run main Test7157574
 */

这个标题引用了这个测试正在测试的 bug,在摘要部分,它详细说明了这个测试正在做什么。此外,当你查看测试的源代码时,通常可以看到它包含了一个非常详细的关于问题的解释以及测试问题的方法。

为 jtreg 编写自己的测试

如果你打算向 OpenJDK 添加新功能或修复一个错误,有一个测试用例来覆盖功能的变化并确保实现更改不会破坏任何东西,这确实是一个好主意。这个方法将帮助你了解这个过程并创建一个简单的测试。你会发现为 jtreg 编写自己的测试用例并不是一个复杂的工作,但在某些方面可能有些不寻常。

准备中

这个方法只需要安装 jtreg 和 OpenJDK 源代码。后者是必需的,因为这种方法假设新创建的测试是针对 OpenJDK 的。

如何操作...

为 jtreg 编写测试可能有些不寻常,但当你习惯了这种模式,实际上相当简单。开始时,只需遵循以下步骤。记住,所有路径都是相对于 OpenJDK 源根给出的:

  1. 前往 OpenJDK 的根目录,首先创建jdk/test/demo/SampleTest.java文件:

    /* @test
     * @summary Test to ensure that computer wasn't moved to the past
     * @compile SampleTimeProvider.java
     * @run main SampleTest
     * @run main/othervm SampleTest
     */
    public class SampleTest {
        public static void main(String[] args) {
            long currentTime = new SampleTimeProvider().getCurrentTime();
            if ( currentTime < 0 ) {
                throw new RuntimeException("It can't be 1969!");
            }
        }
    }
    
  2. 然后,按照以下方式在jdk/test/demo/SampleTimeProvider.java文件中创建文件:

    public class SampleTimeProvider {
        public long getCurrentTime() {
            return System.currentTimeMillis();
        }
    }
    
  3. 现在,在 JDK 源根目录下创建一个名为run_jtreg.sh的脚本文件并运行它:

    #!/bin/sh
    export JT_JAVA=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.60-2.4.7.0.fc20.x86_64
    /home/user/openjdk/jtreg/build/images/jtreg/bin/jtreg -jdk:$JT_JAVA -agentvm -automatic -verbose:summary -w build/jtreg/work -r build/jtreg/report -exclude:./jdk/test/ProblemList.txt jdk/test/demo/SampleTest.java
    
    
  4. 输出应该是这样的:

    [user@localhost jdk7u]$ ./run_jtreg.sh 
    Directory "build/jtreg/work" not found: creating
    Directory "build/jtreg/report" not found: creating 
    Passed: demo/SampleTest.java
    Test results: passed: 1
    Report written to /home/user/openjdk/jdk7u/build/jtreg/report/html/report.html
    Results written to /home/user/openjdk/jdk7u/build/jtreg/work
    
    

    如您所见,只运行了一个测试,并且运行成功。因此,编写一个简单的测试用例是一个非常简单的任务。

  5. 现在,最后一步。在所有测试都运行完毕后,让我们看看 jtreg 输出中提供的测试结果路径。让我们在网页浏览器中打开report/html/repost.html并查看其中的内容:如何操作...

    在这里,我们可以看到只有一个测试,demo/SampleTest.java被执行,并且它通过了。

它是如何工作的...

需要一些解释来找出实际发生了什么。测试本身位于SampleTest.java文件中。jtreg 通过类头部注释中存在的@test标签知道该文件包含测试。如果没有这个标签,jtreg 不会将其视为测试。

@summary标签的唯一目的是为测试提供一个总结描述。这个描述也将用于日志和报告中。为该标签提供一个良好、易读的描述非常重要。此外,如果测试是为了一个错误,那么有必要将@bug标签填充适当的错误编号。

下一个标签@compile引用了另一个文件,该文件需要编译后才能运行测试。SampleTimeProvider.java的存在只是为了展示如何使用@compile标签。Java 做这样的事情非常不寻常。通常,所有内容都会编译,然后从类路径中选取,但这是 Java 的工作方式。

@run标签告诉测试框架如何运行测试。从测试类中可以看出,这个参数可以定义多次,这意味着测试也将执行多次,每次都会使用关联的运行标签定义的配置来运行。在我们的例子中,有两个运行,一个在同一虚拟机中,另一个在由othervm参数指定的新虚拟机实例中。如果没有定义此标签,则 jtreg 默认假设它是@run main ClassName

注意,如果测试运行时间超过 2 分钟(120 秒),则测试失败,并且可以被@run main/timeout=xxx覆盖。

通常,测试通过抛出异常来指示其失败。如果条件不满足,这个测试用例将抛出RuntimeException

jtreg 要求在测试的根目录中创建TEST.ROOT文件。没有这个文件,它将不会执行任何测试。幸运的是,对于 JDK 来说,它已经有了包含适当内容的所需文件,所以我们不需要担心这个问题。

另请参阅

jtreg 测试用例由 Javadoc 标签定义,熟悉所有这些标签是有用的。标签的完整列表和每个标签的信息可以在通过运行 jtreg 命令–onlineHelp访问的帮助文件中找到,或者在网上openjdk.java.net/jtreg/tag-spec.html找到。

在 GUI 模式下使用 jtreg

jtreg 不仅是一个命令行工具,还提供了一个相对复杂的图形界面,允许你运行单个测试集、准备测试运行、查看运行结果等等。此配方将涵盖一些基本的 UI 功能,足以让用户开始使用此工具。

准备工作

此配方所需的所有内容只是一个已安装的 jtreg 和 OpenJDK 源代码。

如何操作...

  1. 创建以下脚本以启动具有图形用户界面的 jtreg:

    #!/bin/sh
    export JT_JAVA=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.60-2.4.7.0.fc20.x86_64
    /home/user/openjdk/jtreg/build/images/jtreg/bin/jtreg –g -jdk:$JT_JAVA -agentvm -automatic -verbose:summary -w build/jtreg/work -r build/jtreg/report -exclude:./jdk/test/ProblemList.txt jdk/test/
    
    

    告诉 jtreg 启动 JT Harness UI 的参数是-g。启动后,jtreg 显示一个类似于以下窗口:

    如何操作...

  2. 在前面的截图右侧,你可以看到最新测试运行的结果。它知道从–r参数中提取它们的位置。在此截图中,你还可以看到成功和失败的测试数量、执行的总测试数量和一些其他统计数据。

  3. 在前面的截图左侧,有一个包含所有可用测试的树。这显示了从测试包的根目录开始的全部测试,该目录包含TEST.ROOT配置文件。绿色文件夹图标表示成功运行的测试,红色图标表示失败的测试。

  4. 要从一组测试中运行特定的测试,右键单击单个测试文件夹,并从弹出菜单中选择执行这些测试项。这将触发测试运行,并生成一个新的报告。对于长时间运行的测试,有一个状态窗口,可通过运行测试|监控进度菜单项访问:如何操作...

工作原理...

jtreg 使用的 UI 实际上并不属于 jtreg。它由 JT Harness 提供,jtreg 只是通过提供的插件系统将其集成。

JT Harness 不仅提供了一个丰富的界面来运行测试,还提供了一套向导来创建测试配置、各种报告转换工具、代理监控工具等等。要获取有关所有这些功能的更多信息,请参阅 JT Harness 在线帮助,它可通过帮助/在线帮助菜单项访问。

为 jtreg 编写 TestNG 测试

jtreg 还提供了运行 TestNG 测试用例的支持,这可能对许多开发者来说是一个更熟悉的技巧。需要执行一些步骤才能实现这一点,此配方将介绍这些步骤。

准备工作

此配方需要 OpenJDK 源代码和已安装的 jtreg 版本。在 Windows 机器上,还需要安装 Cygwin。

如何操作...

以下过程对 Java 开发者来说可能比编写本机 jtreg 测试更熟悉,因为 TestNG 是大多数开发者所听说并使用的。现在,让我们进入实际部分并创建一个测试。路径相对于 OpenJDK 源根目录。

  1. 相对于 OpenJDK 根文件夹,创建一个名为jdk/test/testng/org/demo的文件夹。这是我们将要创建测试的文件夹。

  2. 在相对于 OpenJDK 源根的jdk/test/testng/TEST.properties文件中创建文件,添加以下行:

    TestNG.dirs=.
    
    

    创建该文件不是严格必要的,也可以在jdk/test/TEST.ROOT中定义属性,这将以相同的方式工作,就像我们之前的属性一样。然而,在大多数情况下,有一个这样的文件是实用的,因为它可以包含一些针对 TestNG 测试集的特定配置,例如,lib.dirs属性。

  3. jdk/test/testng/org/mydemo文件夹中,创建以下名称的文件,MyTestNGTest.java

    package org.mydemo;
    
    import org.testng.Assert;
    import org.testng.annotations.Test;
    
    public class MyTestNGTest {
     @Test
     public void testMoreNanosThanMillis() {
     final long nanos = System.nanoTime();
     final long millis = System.currentTimeMillis();
     Assert.assertTrue(nanos > millis);
     }
    }
    
    
  4. 现在,在 OpenJDK 的源根文件夹中,创建以下 bash 脚本(根据机器环境需要调整路径):

    #!/bin/sh
    export JT_JAVA=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.60-2.4.7.0.fc20.x86_64
    /home/user/openjdk/jtreg/build/images/jtreg/bin/jtreg -jdk:$JT_JAVA -agentvm -automatic -verbose:summary -w build/jtreg/work -r build/jtreg/report -exclude:./jdk/test/ProblemList.txt jdk/test/testng
    
    
  5. 然后,给它一个名字,./run_jtreg.sh,并使其可执行(运行chmod +x ./run_jterg.sh即可完成此操作)并运行它。脚本的输出结果应类似于以下内容:

    [user@localhost jdk7u]$ ./run_jtreg.sh 
    Passed: testng/org/mydemo/MyTestNGTest.java
    Test results: passed: 1
    Report written to /home/user/openjdk/jdk7u/build/jtreg/report/html/report.html
    Results written to /home/user/openjdk/jdk7u/build/jtreg/work
    
    

就这样。测试已经运行,并且从输出中可以看出,它已经通过。运行的结果可以通过在浏览器应用程序中打开/home/user/openjdk/jdk7u/build/jtreg/report/html/report.html来查看。

它是如何工作的…

在测试根包文件夹中创建的名为TEST.properties的文件需要一些解释。这是包含特定于 TestNG 测试的配置集的文件。例如,它可以通过lib.dirs属性引用包含库的文件夹。这是一个path类型的属性,与TestNG.dirs相同,此类属性是路径到某些文件夹或文件的空格分隔列表。如果列表中的路径以/开头,则相对于TEST.ROOT文件夹进行评估,否则从包含TEST.properties的目录进行评估。

参见

由于可以在测试类中使用 TestNG 注解,因此查看 TestNG 网站是值得的,该网站包含一些关于此主题的文档,链接为testng.org/doc/documentation-main.html

还可以使用 jtreg 样式标签和 TestNG 样式测试一起使用。在这种情况下,测试应创建为正常的 jtreg 测试(参见为 jtreg 编写自己的测试配方),使用testng作为@run的参数,例如,@run testng SampleTest。在这种情况下,不需要TEST.properties,并且像lib.dirs这样的东西通过 jtreg 标签在测试源文件中定义,而不是在单独的配置文件中定义。

从源代码编译 JT Harness

JT Harness 是一个框架,允许您执行不同的测试集。您不必仅使用 jtreg。其他测试框架也可以与之集成。这意味着拥有其源代码以便从源代码构建它可能很有用。这正是本食谱将要解释的内容。

准备工作

您需要互联网访问来下载源代码和安装构建所需的附加软件。

确保机器上安装了 Ant 版本 1.6.1 或更高版本。如果没有,请安装它。如何做...这一部分取决于您使用的操作系统。例如,在 Fedora 上,它将是:

yum install ant

在 Windows 上,最简单的方法是下载发行版并将 Ant 的bin文件夹添加到PATH环境变量中。Ant 发行版可以在ant.apache.org/找到。请注意,为了使其工作,JAVA_HOME环境变量必须包含 Java 发行版的正确路径。

确保机器上已安装 Subversion。与其它工具不同,JT Harness 不使用 Mercurial 仓库来存储其源代码。在 Fedora 上,可以通过运行yum来安装 Subversion:

yum install svn

在任何其他操作系统上,这完全取决于操作系统。请检查subversion.apache.org/以了解可用的选项。

如何做...

运行以下命令以检查源代码:

svn checkout https://svn.java.net/svn/jtharness~svn/trunk jtharness

此命令将创建一个名为jtharness的文件夹,并下载源代码。在该文件夹中创建另一个文件夹,并将其命名为lib。在这里,我们将放置构建 JT Harness 所需的库。

现在下载以下软件(所有路径都是以jtharness文件夹为相对路径):

  1. JavaHelp 似乎只能通过download.java.net/javadesktop/javahelp/javahelp2_0_05.zip的直接链接获得。将其解压到lib/jh2.0

  2. 前往asm.ow2.org/下载 ASM Java 字节码操作库的二进制版本 3.1。将其解压到lib/asm-3.1。这仅用于编译。

  3. 接下来,所需的库是Java Communications API。前往www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-misc-419423.html下载Java Communications API 3.0u1或任何其他可用的更高版本。将其解压到lib/commapi。这仅用于编译。

  4. Java Servlet API 仅用于编译。最简单的方法是直接从 Maven Central 获取 JAR 文件。访问search.maven.org/并搜索javax.servlet servlet-api。下载版本 3.1.0 的 JAR 文件并将其直接放入lib文件夹。

  5. 最后一个是 JUnit,它也仅用于编译。推荐版本是 4.4。访问junit.org/并点击下载链接以下载适当的 JAR 文件版本。将其直接放入lib文件夹。

  6. 下一步是获取源代码。为此,运行以下命令,将从主干获取最新源代码:

    svn checkout https://svn.java.net/svn/jtharness~svn/trunk jtharness
    
    

    这将创建一个名为jtharness的文件夹,其中包含两个子文件夹:wwwcodewww文件夹包含指向文档和其他有用信息的页面链接,但我们真正感兴趣的是名为code的文件夹。实际上,根本没有必要下载www,但这也不会造成任何伤害。

  7. 前往文件夹jtharness/code/build,找到文件local.properties,并编辑它以设置以下属性以指向适当的路径:

    • jhalljar: 这提供了 JavaHelp jhalljar.jar文件的路径。

    • jhjar: 此属性提供了 JavaHelp jhjar.jar文件的路径。

    • jcommjar: 这提供了从Java Communications APIcomm.jar文件的路径。

    • servletjar: 这提供了从Java Servlet APIservlet-api.jar文件的路径。

    • bytecodelib: 这提供了冒号分隔的asm-3.1.jarasm-commons-3.1.jar文件的路径。

    • junitlib: 这提供了从 jUnit 到junit-4.4.jar文件的路径。

    编辑后,文件应类似于以下内容:

    #Please specify location of jhall.jar here - for compilation
    jhalljar = /home/user/openjdk/jtharness/lib/jh2.0/javahelp/lib/jhall.jar
    # needed only at runtime
    jhjar = /home/user/openjdk/jtharness/lib/jh2.0/javahelp/lib/jh.jar
    # location of jar with implementation of java serial communications API
    jcommjar = /home/user/openjdk/jtharness/lib/commapi/jar/comm.jar
    # location of jar with servlet API implementation
    servletjar = /home/user/openjdk/jtharness/lib/javax.servlet-api-3.1.0.jar
    # bytecode library (BCEL or ASM)
    # these are not interchangable
    bytecodelib = /home/user/openjdk/jtharness/lib/asm-3.1/lib/asm-3.1.jar:/home/stas/openjdk/jtharness/lib/asm-3.1/lib/asm-commons-3.1.jar
    # JUnit Library - Version 4 currently used to compile 3 and 4 support
    junitlib = /home/user/openjdk/jtharness/lib/junit-4.4.jar
    # Please specify location where the build distribution (output) will be created
    BUILD_DIR = ../JTHarness-build
    
    

    如果需要,可以将BUILD_DIR变量更改为不同的文件夹,但通常没有必要。

  8. 现在确保当前文件夹是jtharness/code/build,然后运行 Ant:

    [stas@localhost build]$ ant
    Buildfile: /home/user/openjdk/jtharness/code/build/build.xml
     …skipped…
    BUILD SUCCESSFUL
    Total time: 45 seconds
    When build is finished
    
    

    当构建完成后,文件夹jtharness/JTHarness-build/binaries将包含 JT Harness 的分发版本。

参见

JT Harness 构建文件中还有其他一些目标,你可能觉得它们很有用:

  • run: 这将构建并运行 JT Harness。这不是启动应用程序的唯一方法。另一种选择是在构建后从源根目录运行以下命令:

    java -jar JTHarness-build/binaries/lib/javatest.jar
    
    
  • clean: 这只是构建分发目录。

  • build: 这将构建 JT Harness 然后运行所有测试。

  • test: 这只是运行测试。

  • Javadoc: 这将生成 Javadoc API 文档。

  • build-examples: 这将构建与源代码一起打包的示例测试套件。此目标还会自动首先构建核心工具。

构建 和 运行 jcstress

Java 并发压力测试jcstress)是一组用于测试 Java 并发支持的正确性的测试。这是一个主要针对 Java 8 的新工具,这意味着并非所有测试都能在 Java 的先前版本上运行。作为一个新工具,jcstress 处于 alpha 阶段,修复和改进的更改很常见,这意味着使用它的人预计需要相对频繁地更新源代码和重新构建工具。

测试并发性不是一个容易的任务,即使代码有误,也可能很难使这样的测试失败。这是因为并发代码的性质,它可能在不同的硬件配置上以不同的方式工作。这种差异可能源于 CPU 的数量或 CPU 架构。总的来说,这意味着 jcstress 中的许多测试都是不确定的,它们可能需要很长时间才能暴露潜在的问题。

准备工作

您需要互联网访问和 Mercurial 仓库来下载源代码。由于 jcstress 需要 Java 8 进行编译和运行完整的测试集,因此它必须安装到机器上,并设置为当前版本。这意味着以下命令应显示 Java 1.8 作为主要版本,如以下所示:

[user@localhost]$ java -version
openjdk version "1.8.0_11"
OpenJDK Runtime Environment (build 1.8.0_11-b12)
OpenJDK 64-Bit Server VM (build 25.11-b02, mixed mode)
[user@localhost jcstress]$ javac -version
javac 1.8.0_11

构建还需要在机器上安装 Maven(Java 构建工具之一)。这个工具的安装取决于操作系统。例如,在 Fedora 上,可以通过以下命令作为 root 用户执行:

[root@localhost ~]# yum install maven

在其他操作系统上,最简单的方法可能是从maven.apache.org/download.cgi下载二进制文件,解压存档,将M2_HOME指向解压文件夹的根目录,并将M2_HOME/bin添加到路径中。

如何做…

现在是采取行动的时候了。这个食谱的前几个步骤将涵盖构建过程,然后它将切换到运行实际的测试:

  1. 第一步是下载源代码。转到您想要存储源代码的文件夹,并运行以下命令:

    [user@localhost ~]$ hg clone http://hg.openjdk.java.net/code-tools/jcstress/ jcstress
    
    

    当运行此命令时,Mercurial 将从远程仓库下载源代码,并存储在jcstress文件夹中。

  2. 现在,要构建这个工具,移动到jcstress文件夹并运行以下命令:

    [user@localhost jcstress]$ mvn clean install -pl tests-custom –am
    
    

    如果成功,它应该在结束时显示如下:

    [INFO] BUILD SUCCESS
    [INFO] ----------------------------------------------------
    [INFO] Total time: 1:31.300s
    [INFO] Finished at: Tue Jul 29 22:23:19 BST 2014
    [INFO] Final Memory: 37M/410M
    [INFO] ----------------------------------------------------
    
    

    这意味着构建已经完成了它应该做的事情,测试准备就绪,可以运行。

  3. 要运行所有测试,请使用以下命令:

    java -jar tests-custom/target/jcstress.jar
    
    

    这将输出数千行类似的内容:

    (ETA:   00:39:58) (R: 1.36E+07) (T:  46/898) (F: 1/1) (I: 3/5)       [OK] o.o.j.t.atomicity.primitives.plain.VolatileCharAtomicityTest
    (ETA:   00:40:05) (R: 1.36E+07) (T:  46/898) (F: 1/1) (I: 3/5)       [OK] o.o.j.t.atomicity.primitives.plain.VolatileDoubleAtomicityTest
    
    

    ETA是预计完成时间,R是运行时间(以纳秒为单位),T是测试编号,F是分支编号,I是测试迭代编号。所有这些后面都跟着结果(本例中的OK)和测试类的全名。

如您所见,完整的标准测试集运行大约需要 40 分钟,这可能太多了,所以有一个选项可以使用-t参数选择要运行的测试。这是一个为测试选择的正则表达式,例如:

[user@localhost jcstress]$ java -jar tests-custom/target/jcstress.jar -t ".*ByteBufferAtomicityTests.*"

这将只运行名称中包含ByteBufferAtomicityTests的测试。

当测试完成后,是时候查看生成的报告了,默认情况下,它们被放入 ./results/ 文件夹中。在那里您可以找到文件 index.html,可以用任何浏览器打开。结果将列出所有测试,如果您点击测试,将列出所有观察者输出。这些输出可能是预期的,不是预期的,或者预期但以某种方式对用户来说令人惊讶。这可能导致以下测试结果:

  • FAILED: 测试失败,结果并非预期

  • ERROR: 测试崩溃

  • ACCEPTABLE: 测试结果符合规范

  • ACCEPTABLE_INTERESTING: 这与 ACCEPTABLE 相同,但它有一些值得强调的内容

  • ACCEPTABLE_SPEC: 这与 ACCEPTABLE 相同,但还观察到一些可能不会预料到的有趣行为

还有更多...

建议您查看 jcstress 可用的其他命令行选项。这些信息可以通过运行以下命令获取:

[user@localhost jcstress]$ java -jar tests-custom/target/jcstress.jar –h

jcstress 在openjdk.java.net/有自己的页面,那里有一些非常有用的信息和链接到源代码和邮件列表openjdk.java.net/projects/code-tools/jcstress/

为 jcstress 编写测试

Java 并发压力测试是一个非常好的工具,被 JDK 作者用来确保他们的并发代码在并发方面工作正确。编写并发代码很困难,测试起来更难。他们的大多数测试都是概率性的,需要很多技巧来编写,可能需要很多天的时间,以及适当的硬件来展示失败行为。考虑到所有这些复杂性,拥有一个可以帮助正确执行测试的框架是一个很大的优势。此配方将介绍编写自己 jcstress 测试所需的步骤。

准备工作

要遵循此配方,唯一的要求是拥有一个能够编译和运行 jcstress 的环境(请参阅 构建和运行 jcstress 配方)。

如何操作...

以下步骤将引导您完成创建测试并使用 jcstress 运行测试的过程:

  1. 首先,我们需要一个代码来测试。让我们选择一个,可以说是并发中最常见的头痛原因,并且很容易复现的问题。数据竞争听起来是一个很好的候选者。我们将创建一个名为 CASValue 的类并实现它:

    package org.openjdk.jcstress.tests;
    
    public class CASValue {
        private int i = 0;
        public boolean cas(int expected, int newValue) {
            boolean res = false;
            if (i == expected) {
                i = newValue;
                res = true;
            }
            return res;
        }
    }
    

    此类实现了一个单一的操作,该操作应该执行比较和交换操作(请参阅en.wikipedia.org/wiki/Compare-and-swap)。在没有同步的情况下,它将无法在多线程环境中正确工作,并且应该会失败我们将在本配方的下一步中创建的测试。

  2. 在 jcstress 的源根文件夹中创建文件,tests-custom/src/main/java/org/openjdk/jcstress/tests/CASValue.java,并将CASValue类的源代码放入其中。

  3. 现在是时候编写一个测试来看看我们的实现是否错误了。测试类看起来像这样:

    package org.openjdk.jcstress.tests;
    
    import org.openjdk.jcstress.annotations.*;
    import org.openjdk.jcstress.infra.results.LongResult2;
    
    public class CASValueTests {
        @State
        public static class S extends CASValue { }
    
        @JCStressTest
        @Description("Tests correctness of CASValue CAS operations.")
        @Outcome(id = "[5,2]", expect = Expect.ACCEPTABLE, desc = "T1 -> T2 execution.")
        @Outcome(id = "[1,10]", expect = Expect.ACCEPTABLE, desc = "T1 -> T2 execution.")
        public static class ValCas_ValCas {
            @Actor public void actor1(S s, LongResult2 r) { 
                r.r1 = s.cas(0, 5) ? 5 : 1; 
            }
            @Actor public void actor2(S s, LongResult2 r) { 
                r.r2 = s.cas(0, 10) ? 10 : 2;
            }
        }
    }
    
  4. 将此文件保存在与CASValue.java相同的文件夹中,即在tests-custom/src/main/java/org/openjdk/jcstress/tests/,并命名为CASValueTests.java

  5. CASValueTests 类是测试中其他类的容器类。这并不是严格必要的,但它有助于保持代码整洁。被@JCStressTest注解的ValCas_ValCas类是包含两个演员(被@Actor注解的方法)的测试用例类。这些方法将由测试框架并行运行。

  6. 被注解为@State的子类S是在演员之间共享的状态,在这种情况下,是正在测试的类。它扩展了我们的类CASValue,并且仅为了避免在CASValue上添加@State注解而创建。

  7. @Outcome注解指定了测试的结果。结果可以是ACCEPTABLEFORBIDDENACCEPTABLE_INTERESTINGACCEPTABLE_SPEC。这些由expect属性定义。id属性提供了结果列表,而desc只是对结果的一个描述。此测试用例指定,对于我们的测试,LongResul2中的有效值只有52以及110,这是如果 CAS 按预期工作时的唯一预期结果。任何其他结果都是禁止的,并将导致测试用例失败,这正是我们想要的。

  8. 现在是编译测试的时候了。为此,请从 jcstress 的源根目录运行以下命令:

    [user@localhost jcstress] mvn clean install -pl tests-custom –am
    
    

    这将仅运行tests-custom项目的构建,编译我们刚刚创建的类。

  9. 下一步是运行我们的测试并查看它是否工作:

    [user@localhost jcstress] java -jar tests-custom/target/jcstress.jar -t ".*CASValueTests.*"
    
    

    此命令中的-t参数指定我们只想运行满足.*CASValueTests.*正则表达式的测试。

    如预期的那样,测试应该失败,因为提供的实现确实实现了 CAS 操作。输出应该有多个类似于以下结果的测试结果:

    (ETA:        n/a) (R: 5.95E+08) (T:   1/1) (F: 1/1) (I: 1/5)   [FAILED] o.o.j.t.CASValueTests$ValCas_ValCas
    Observed state  Occurrences Expectation Interpretation
    [5, 2]  (3,230,666)        ACCEPTABLE T1 -> T2 execution.
    [1, 10] (2,613,825)        ACCEPTABLE T2 -> T1 execution.
    [5, 10] (7,609,449)        FORBIDDEN Other cases are not expected.
    
    

    它显示了每种结果的输出数量。到目前为止,不正确的结果[5, 10]7,609,449次出现率领先。这表明测试工作正确,并帮助我们确定我们需要修复 CAS 类的实现。

  10. 现在让我们修复我们的类并再次运行测试。最简单但不是最有效的方法是简单地给我们的 CAS 方法添加synchronized修饰符:

    public class CASValue {
     private int i = 0;
     public synchronized boolean cas(int expected, int newValue) {
     boolean res = false;
     if (i == expected) {
     i = newValue;
     res = true;
     }
     return res;
     }
    }
    
    
  11. 在更改实现后,再次运行构建:

    mvn clean install -pl tests-custom –am
    
    
  12. 然后,重新运行测试:

    java -jar tests-custom/target/jcstress.jar -t ".*CASValueTests.*"
    
    
  13. 现在,测试不应该显示任何失败,并报告测试运行成功:

    (ETA: n/a) (R: 3.35E+08) (T:   1/1) (F: 1/1) (I: 1/5) [OK] o.o.j.t.CASValueTests$ValCas_ValCas
    (ETA: 00:00:02) (R: 1.69E+07) (T:   1/1) (F: 1/1) (I: 2/5) [OK] o.o.j.t.CASValueTests$ValCas_ValCas
    (ETA: 00:00:01) (R: 1.22E+07) (T:   1/1) (F: 1/1) (I: 3/5) [OK] o.o.j.t.CASValueTests$ValCas_ValCas
    (ETA: 00:00:00) (R: 1.07E+07) (T:   1/1) (F: 1/1) (I: 4/5) [OK] o.o.j.t.CASValueTests$ValCas_ValCas
    (ETA: now) (R: 1.00E+07) (T:   1/1) (F: 1/1) (I: 5/5) [OK] o.o.j.t.CASValueTests$ValCas_ValCas
    
    

这表明实现的更改是有效的,并且根据测试用例,实现是正确的。

通过遵循这些步骤,开发者可以确保并发代码根据规范按预期工作,只要测试和测试用例被正确实现和定义。然而,请注意,并发性是难以测试的,一个在一种硬件上工作的实现很容易在其他硬件上失败。这意味着建议在尽可能广泛的配置上运行这些测试。

参见

这个框架是由 Aleksey Shipilëv 构建和维护的,他有自己的博客,并在各种会议上发表演讲。我们建议您访问他的主页 (shipilev.net/),在 YouTube 上观看他的视频(例如,www.youtube.com/watch?v=4p4vL6EhzOk),并阅读一些他的论文。这将帮助您获得大量关于正确并发测试、一般并发、Java 中的并发支持以及其他相关主题的信息。

使用 JMH 创建基准项目

微基准测试本身不是一个容易的主题,使用像 Java 这样的语言正确地进行它是一个非常困难的任务。这些困难源于 Java 执行代码的方式和 JVM 所需的基础设施。正如 JIT 和 GC 可能会严重影响微基准测试的结果一样,确保每次运行的输出结果一致和正确可能不是一个容易完成的任务。为了帮助解决这个问题,有几个框架可以帮助确保基准测试运行正确。其中之一是 Java 微基准工具JMH),它是 OpenJDK 的一部分。这个食谱将解释开发者如何使用这个框架来对其自己的代码进行基准测试。

准备工作

这个食谱需要一个具有互联网连接的机器、Maven、Java SDK 以及支持 Maven 项目的你喜欢的 IDE。

如何做…

以下步骤将指导您创建基准项目并编写基准测试,这可以用来分析代码的性能:

  1. 在命令行中,运行以下 Maven 命令:

    [user@localhost ~] mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jmh -DarchetypeArtifactId=jmh-java-benchmark-archetype -DgroupId=org.benchmark -DartifactId=mybenchmark -Dversion=1.0
    
    
  2. 运行此命令后,在当前目录下,Maven 将创建一个名为 mybenchmark 的文件夹,其中将包含项目的框架。如果一切顺利,构建应该以类似于以下内容的输出结束:

    [INFO] project created from Archetype in dir: /home/user/openjdk/mybenchmark
    [INFO] -------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] -------------------------------------------------------
    [INFO] Total time: 9.188s
    [INFO] Finished at: Sat Aug 02 20:16:01 BST 2014
    [INFO] Final Memory: 11M/129M
    [INFO] -------------------------------------------------------
    
    
  3. 现在,当项目生成后,我们可以开始使用它并创建我们的第一个微基准测试。使用您最喜欢的 IDE 打开生成的项目文件(/home/user/openjdk/mybenchmark/pom.xml)。确保 Maven 配置正确并且所有依赖项都已正确下载。请注意,已经为基准测试创建了一个名为org.benchmark.MyBenchmark的类。一开始,它只有一个方法,我们将在其中放置稍后要测试的代码。

  4. 作为例子,让我们测试一些相对简单但仍有改进空间的东西。二分查找是这种目的的好选择。所以,让我们草拟一个简单的实现并将其放入org.benchmark.BinarySearch1类中,如下所示:

    package org.benchmark;
    public class BinarySearch1 {
        public static int search(long[] arr, long value) {
            return search(arr, value, 0, arr.length-1);
        }
    
        private static int search(long[] arr, long value, int start, int end) {
            if (end < start) 
                return -1;
    
            int imid = start + ((end - start) / 2);
            if (arr[imid] > value)
                return search(arr, value, start, imid-1);
            else if (arr[imid] < value)
                return search(arr, value, imid+1, end);
            else
                return imid;
        }
    

    这是一个非常基本的实现,对于我们的实验来说已经足够好了。如果您不熟悉二分查找或想了解更多关于这个算法的信息,请访问维基百科页面en.wikipedia.org/wiki/Binary_search_algorithm

  5. 现在,当实现的第一稿准备好后,我们将为它创建一个微基准测试。将以下代码放入org.benchmark.MyBenchmark类中:

    package org.benchmark;
    
    import org.openjdk.jmh.annotations.*;
    import java.util.Arrays;
    import java.util.concurrent.TimeUnit;
    
    @State(value = Scope.Thread)
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    public class MyBenchmark {
        private long[] data = new long[5000];
    
        @Setup
        public void setup() {
            for (int i = 0; i != data.length; ++i) {
                data[i]=(long)(Math.random()*Long.MAX_VALUE-1);
            }
            Arrays.sort(data);
        }
    
        @Benchmark
        public int testBinarySearch1 () {
            return BinarySearch1.search(data, Long.MAX_VALUE);
        }
    }
    

    这段代码需要一些解释。@State注解是必需的,以告诉 JMH 这个类包含一些由测试使用的数据,并且Scope.Thread作用域中的数据意味着它不会在多个线程之间共享。

    @BenchmarkMode(Mode.AverageTime)注解表示我们想要测量的是执行测试的平均时间,默认情况下,它测量吞吐量。@OutputTimeUnit(TimeUnit.MICROSECONDS)注解设置了timeunit。我们需要定义它,因为默认值是秒,这对于基准测试来说是一个非常大的尺度。

    设置方法被@Setup注解标记,这意味着它为测试做一些准备工作,并且它将在初始化测试数据时被调用。它与 JUnit 中的@Before注解类似。请注意,这个方法只会在 JVM 分叉运行测试之前执行一次。它不会在每个测试方法被调用之前执行。这意味着相同的测试方法将在每次迭代后使用相同的数据。

    实际的测试在带有@Benchmark注解的方法中,该方法执行我们要测试的代码。

  6. 现在一切准备就绪,运行一个测试来找出我们的代码有多快。首先,让我们用我们的代码构建项目并测试它。为此,转到项目文件夹并运行以下命令:

    [user@localhost mybenchmark] mvn clean install
    
    
  7. 然后,运行基准测试:

    [user@localhost mybenchmark] java -jar target/benchmarks.jar --wi=10 --i=5 --f=1 --jvmArgs=-server
    
    

    在这里,wi定义了warmup迭代次数,i定义了测试运行迭代次数,f表示要使用的 JVM 分叉数量,而jvmArgs是分叉 JVM 的参数。

    我们每个测试方法的输出应该看起来像这样:

    # VM invoker: C:\Usres\User\jdk1.7.0\jre\bin\java.exe
    # VM options: -server
    # Warmup: 10 iterations, 1 s each
    # Measurement: 5 iterations, 1 s each
    # Threads: 1 thread, will synchronize iterations
    # Benchmark mode: Average time, time/op
    # Benchmark: org.benchmark.MyBenchmark.testBinarySearch1
    
    # Run progress: 0.00% complete, ETA 00:00:15
    # Fork: 1 of 1
    # Warmup Iteration   1: 74.562 ns/op
    # Warmup Iteration   2: 75.657 ns/op
    # Warmup Iteration   3: 79.575 ns/op
    # Warmup Iteration   4: 75.718 ns/op
    # Warmup Iteration   5: 76.432 ns/op
    # Warmup Iteration   6: 75.965 ns/op
    # Warmup Iteration   7: 73.987 ns/op
    # Warmup Iteration   8: 75.677 ns/op
    # Warmup Iteration   9: 76.326 ns/op
    # Warmup Iteration  10: 77.050 ns/op
    Iteration   1: 77.027 ns/op
    Iteration   2: 75.870 ns/op
    Iteration   3: 77.674 ns/op
    Iteration   4: 81.460 ns/op
    Iteration   5: 73.858 ns/op
    
    Result: 77.178 ±(99.9%) 10.778 ns/op [Average]
     Statistics: (min, avg, max) = (73.858, 77.178, 81.460), stdev = 2.799
     Confidence interval (99.9%): [66.400, 87.956]
    
    # Run complete. Total time: 00:00:18
    Benchmark                            Mode  Samples   Score  Score error  Units
    o.b.MyBenchmark.testBinarySearch1    avgt        5  77.178       10.778  ns/op
    
    

    输出显示了每个分叉执行的运行以及最终结果。在这里,我们可以看到,平均而言,我们的方法运行需要77.178纳秒。

  8. 现在我们有了结果,我们应该如何处理它们?通常,这些结果只有在与其他东西比较时才有意义。让我们尝试对代码进行一些修改,看看是否有助于我们的二分查找实现运行得更快。我们可以尝试移除递归并看看效果如何。创建另一个名为org.benchmark.BinarySearch2的类,并将以下实现放在那里:

    package org.benchmark;
    public class BinarySearch2 {
        public static int search(long[] arr, long value) {
            return search(arr, value, 0, arr.length-1);
        }
        private static int search(long[] arr, long value, int start, int end) {
            while (end >= start) {
                int imid = start + ((end - start) / 2);
                if(arr[imid] == value)
                    return imid;
                else if (arr[imid] < value)
                    start = imid + 1;
                else
                    end = imid - 1;
            }
            return -1;
        }
    }
    

    这是迭代实现,它不使用递归调用。

  9. 现在,让我们更新基准测试类,以便我们可以比较递归和迭代实现:

    package org.benchmark;
    
    import org.openjdk.jmh.annotations.*;
    import java.util.Arrays;
    import java.util.concurrent.TimeUnit;
    
    @State(value = Scope.Group)
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    public class MyBenchmark {
        private long[] data = new long[500000];
    
        @Setup
        public void setup() {
            for (int i = 0; i != data.length; ++i) {
                data[i] = (long)(Math.random() * (Long.MAX_VALUE-1));
            }
            Arrays.sort(data);
        }
    
        @Benchmark
        @Group(value = "bsearch")
        public int testBinarySearch1() {
            return BinarySearch1.search(data, Long.MAX_VALUE);
        }
    
        @Benchmark
        @Group(value = "bsearch")
        public int testBinarySearch2() {
            return BinarySearch2.search(data, Long.MAX_VALUE);
        }
    }
    

    与上一个版本相比,这个基准测试使用的是提供我们实现简单比较的基准测试组。现在@State注解必须具有Group范围,否则测试将使用不同的数据实例,这不是我们想要的,因为我们希望算法在完全相同的条件下工作。

  10. 现在,重新构建项目:

    [user@localhost mybenchmark] mvn clean install
    
    
  11. 然后,再次运行测试:

    [user@localhost mybenchmark] java -jar target/benchmarks.jar --wi=10 --i=5 --f=1 --jvmArgs=-server
    
    

    输出将略不同于上一个,因为使用了分组。我们感兴趣的主要区别将在报告的末尾:

    # Run complete. Total time: 00:00:18
    Benchmark                                    Mode  Samples   Score  Score error  Units
    o.b.MyBenchmark.bsearch                      avgt        5  66.929        1.663  ns/op
    o.b.MyBenchmark.bsearch:testBinarySearch1    avgt        5  79.717        2.289  ns/op
    o.b.MyBenchmark.bsearch:testBinarySearch2    avgt        5  54.141        1.209  ns/op
    
    

    我们可以看到的是,对于这个特定的配置(包括机器规格、JDK 版本、操作系统等),通过testBinarySearch2()方法实现的迭代实现平均比通过testBinarySearch1()实现的递归实现更快(54.141 < 79.717)。

在完成这个菜谱后,你已经学会了如何运行微基准测试,如何解释结果,以及如何比较不同实现的性能。确保你正确地对每个困难任务进行微基准测试,并记住结果在不同机器、JVM 版本等上可能会有很大差异。

还有更多...

JMH 是一个灵活的框架,它提供了使用方式的灵活性。例如,如果有人想通过主方法运行测试,而不使用benchmarks.jar,这可以很容易地实现。为此,只需将以下主方法添加到MyBenchmark中并运行它:

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(".*" + MyBenchmark.class.getSimpleName() + ".*")
            .warmupIterations(3)
            .measurementIterations(3)
            .forks(2)
            .build();
    new Runner(opt).run();
}

这个例子将给出与运行以下命令相同的结果:

[user@localhost mybenchmark] java -jar target/benchmarks.jar --wi=3 --i=3 --f=2

我们还建议你下载源代码(见下载源代码和编译 JHM菜谱)。查看菜谱和 JavaDocs,因为 JavaDocs 写得很好,解释了很多关于框架的内容。

参见

与 jcstress 类似,这个框架也是由 Aleksey Shipilëv 构建和维护的,他有自己的博客,并在各种会议上发表演讲。我们建议你访问他的主页 (shipilev.net/),在 YouTube 上观看他的视频(例如,www.youtube.com/watch?v=4p4vL6EhzOk),并阅读一些他的论文。

下载源代码和编译 JHM

与所有其他 OpenJDK 工具和项目类似,有一个选项可以下载 JHM 的源代码并自行构建。如果框架需要定制和扩展修复,可能需要这样做。幸运的是,这个过程非常简单直接。

准备工作

此配方需要一个能够运行 Mercurial 和 Maven 的具有互联网连接的机器。基本上,要求与编译和运行 jcstress 的要求相同(参见 构建和运行 jcstress 配方)。

如何操作…

以下步骤将引导你通过下载源代码和构建 JHM 的过程:

  1. 首先,让我们运行以下命令以下载源文件:

    hg clone http://hg.openjdk.java.net/code-tools/jmh/ jmh
    
    

    此命令将下载源文件并将它们放入 jmh 文件夹。

  2. 下一步是构建源代码。构建过程需要机器上安装 Maven。将当前文件夹更改为 jmh 并运行以下命令:

    mvn clean install –DskipTests
    
    

    此命令应生成类似于以下内容的输出:

    [INFO] -------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] -------------------------------------------------------
    [INFO] Total time: 3:14.052s
    [INFO] Finished at: Sat Aug 02 19:43:39 BST 2014
    [INFO] Final Memory: 38M/176M
    [INFO] -------------------------------------------------------
    
    

    这意味着构建已成功完成。

  3. 现在,最后一步是将你的基准项目更改为使用刚刚构建的 JHM 版本。假设 JHM 版本的项目引用是通过属性进行的,只需将项目中 JHM 依赖的版本更改为 1.0-SNAPSHOT

    <properties>
     ...
     <jmh.version>0.9.3</jmh.version>
    </properties>
    
    

它是如何工作的…

当你使用 install 目标运行 Maven 时,它将新构建的工件版本放入本地仓库。在这种情况下,版本是 1.0-SNAPSHOT。当其他项目对该版本有依赖时,Maven 将从本地仓库选择该版本并使用它。

第十章。为 OpenJDK 贡献

在本章中,我们将涵盖以下主题:

  • 成为贡献者

  • 使用 webrev 生成补丁

  • 将 OpenJDK v9 补丁回滚到 OpenJDK v8

  • 理解 OpenJDK 小组

  • 理解 OpenJDK 项目

  • 提出新 JSR

  • 提出新 JEP

简介

OpenJDK 社区由许多人在项目中扮演不同角色和承担不同责任的人组成。这种结构是项目规模和重要性的结果,否则它将无法控制,也无法进一步发展。OpenJDK 的管理和结构可以用两个层次来描述:一个是功能性的,另一个是治理性的。这两个层次相互交叉,但交叉不多。唯一在两者中都存在的角色是JDK 领导者,这是 Oracle 指派的 OpenJDK 成员,负责管理 Java 发布项目。

功能性层次控制变更和开发过程。它定义了所有参与在 OpenJDK 中提出和实施变更的社区成员之间的关系。总的来说,它可以由以下图中所示的结构表示:

简介

参与者可以是任何参与过 OpenJDK 项目的任何人,这可以是任何类型的参与。例如,它可以是邮件列表上的讨论或创建补丁来修复错误。任何参与者都可以签署Oracle 贡献者协议OCA),成为贡献者。

贡献者可以参与任何 OpenJDK 项目,如果项目领导批准,可以成为项目作者。项目作者不能将代码推送到仓库,而必须请求项目提交者之一来做。在作者获得提交者的信任后,他或她可以被选为其中之一。所有代码更改都必须在进入仓库前获得批准。这项任务由经验丰富的提交者(项目审查员)完成,他们拥有很大的权限。有些项目可能没有项目审查员的角色。

此外,贡献者可以成为 OpenJDK 成员并参与 OpenJDK 小组。要成为 OpenJDK 成员,贡献者需要证明有显著的贡献历史,并且应由现有的 OpenJDK 成员投票选举。成为成员后,可以通过加入一个或多个小组成为 OpenJDK 小组成员。小组成员由现有小组成员提名,因此,要加入一个小组,成员必须对该小组有一些贡献历史。值得一提的是,成员资格的顺序可以颠倒——贡献者可以被选为小组成员,这可以成为成为 OpenJDK 成员的途径。

每个 OpenJDK 小组负责 JDK 的领域。例如,有一个Swing 小组安全小组等等。每个小组都有一个小组负责人和小组成员。一个小组可以赞助项目,例如,如前所述,Swing 小组是OpenJFXSwing 应用程序框架等项目的赞助商。正如我们之前提到的,任何贡献者都可以参与赞助项目。贡献者不需要是 Open JDK 成员或小组成员才能这样做。

管理结构由管理委员会表示,该委员会由几个角色组成,如下所示:

  • 主席:这个角色由 Oracle 任命。这个人是管理委员会的负责人。

  • 副主席:这个角色由 IBM 任命

  • OpenJDK 负责人:这个角色由 Oracle 任命。这个人还领导 Java 发布项目。

  • 两名非正式成员:这两名成员是通过 OpenJDK 成员的投票选择的。

管理委员会的职责是定义新的流程或改进现有的流程,并更新和支持章程。管理委员会有权在社区内解决程序性争议,但它不是一个执行机构。这意味着它对技术或发布决策没有直接权威。有趣的是,管理委员会和 OpenJDK 小组也可以赞助项目。

对于那些对 OpenJDK 角色、组、项目及其关系的层次结构细节感兴趣的人来说,查看章程(openjdk.java.net/bylaws)是值得的,它详细涵盖了所有这些内容。

成为贡献者

成为参与者非常容易。唯一需要的是对 OpenJDK 感兴趣,并且至少参与邮件列表上的讨论。参与者可以向 OpenJDK 成员请求将代码添加到代码库中,但他们自己不能这样做。然而,贡献者有一个更复杂的角色,这些人可以提交补丁,并对项目有更大的影响力。

准备工作

准备遵循这个食谱很容易;所需的一切只是一个成为参与者的愿望。重要的是要理解这不是一个快速的过程,可能需要几周甚至几个月。所以,要有耐心,不要放弃。

准备补丁需要运行 Kron shell 脚本,因此需要安装该 shell。如果使用 Windows 机器,则系统需要 Cygwin。

如何操作...

下一步将描述如何成为 OpenJDK 的参与者。请记住,这不是一个确定性的过程,在实践中,可能需要几个额外的步骤,或者以下步骤中的一些可能不需要:

  1. 第一步是让其他 OpenJDK 社区成员看到你。这可以通过参与邮件列表上的讨论来实现,并且提出一两个小修复并请求 OpenJDK 成员或贡献者提交它们是个好主意。这将表明你对 OpenJDK 感兴趣以及如何为其做出贡献。

  2. 然后,通过填写 java.net/people/new 上的表格在 Java.net 创建一个账户。这个账户用于 Java.net、OpenJDK 错误跟踪和源代码库。

  3. 下一步是签署Oracle 贡献协议OCA)。需要从 www.oracle.com/technetwork/oca-405177.pdf 下载。在签署之前,仔细阅读并理解协议。幸运的是,它只有一页长。然后,签署并扫描电子邮件发送到 <oracle-ca_us@oracle.com>。可能需要几周时间才能收到回复。所以,如果规定的时间已经过去,而你还没有收到回复,那么发送一个提醒是值得的。

  4. 一旦你从 Oracle 收到 OCA 的回复,就是时候了解你的贡献将是什么了。可能最简单的事情就是找到一个错误并为它修复。错误可以在 OpenJDK JIRA bugs.openjdk.java.net/ 上找到。选择一个尚未分配给任何人的错误。务必选择一个不需要接口更改或任何重大开发的错误。

  5. 在实际工作之前,最好在适当项目的邮件列表上讨论这个错误和提出的修复方案。建议使用格式,<bugid>: <bug_description> 作为主题,例如,JDK-8042253: StressRedefine 测试超时

  6. 执行修复。对于在修复范围内所做的任何工作,进行 jtreg 回归测试是个好主意。

  7. 使用 webrev:[user@localhost hotspot]$ ksh ../make/scripts/webrev.ksh 生成一个用于审查的补丁。确保生成的 webrev.zip 文件可供相关社区使用。例如,可以将其放在一个公开访问的文件托管上。常规贡献者更喜欢使用 cr.openjdk.java.net 来实现这一目的。有关 webrev 的更多详细信息,请参阅 使用 webrev 生成补丁 菜单。

  8. 要发布补丁,请使用格式 RFR <bugid>: <bug_description> 作为邮件列表的主题。例如,RFR JDK-8042253 StressRedefine 测试超时,其中 RFR 代表 请求审查。提供补丁的描述并提供上一步生成的 webrev 文件的链接。

  9. 审查总意味着将有一些清理、代码更改、更多测试等等。执行更新,更新 webrev,并通知其他成员已进行更改。准备好,因为这个过程可能需要多次迭代。

  10. 一旦所有人都同意修复质量良好,发起人会将修复推送到仓库。在这个阶段,你可能会被要求生成一个变更集。请注意,这一步骤可能需要几周时间才能执行。

参见

使用 webrev 生成补丁

在软件产品中进行任何更新通常都需要某种形式的审查过程。代码不应提交到仓库,除非有人已经查看过它。在 OpenJDK 中,用于此目的的工具是 webrev,它允许你创建可以与其他社区成员共享的代码审查。

准备工作

Webrev 是一个 Korn shell 脚本,这意味着在使用它之前,必须在机器上安装 Korn shell。在 Linux 机器上,运行yum install ksh或等效命令。在 Windows 机器上,确保 Cygwin 安装中包含了ksh

除了设置用于创建补丁审查的环境之外,还必须在源代码中进行一些更改。作为一个简单的例子,我们可以修改文件中的某些注释,如以下所述。打开文件hotspot/src/os/linux/vm/jvm_linux.cpp并更新头文件或仅添加注释。

如何操作...

由于 webdev 是一个 shell 脚本,请先启动你的 shell,然后按照给定的步骤操作。假设当前文件夹是jdk源根目录,修改的文件是hotspot/src/os/linux/vm/jvm_linux.cpp

  1. 首先,将当前文件夹更改为hostpot;这不是严格必要的,但它会使 webrev 脚本的工作量减少:

    cd ./hostpot
    
  2. 运行 webrev 脚本:

    [user@localhost hotspot]$ ksh ../make/scripts/webrev.ksh
     SCM detected: mercurial
     No outgoing, perhaps you haven't commited.
     Workspace: /home/user/openjdk/jdk7u/hotspot
     Compare against: http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot
     Output to: /home/user/openjdk/jdk7u_clean/hotspot/webrev
     Output Files:
     src/os/linux/vm/jvm_linux.cpp
     patch cdiffs udiffs sdiffs frames old new
     index.html: grep: /home/user/.hgrc: No such file or directory
    Done.
    Output to: /home/user/openjdk/jdk7u/hotspot/webrev
    
    

    如输出所示,它检测到jvm_linux.cpp已更改,并生成了放在hotspot/webrev文件夹中的审查文件。

  3. 现在,在hotspot文件夹中找到webrev.zip文件,并将其提供给任何对您刚刚所做的更改感兴趣的人。此文件将具有与webrev文件夹相同的内容。

它是如何工作的...

正如你已经注意到的,webrev 只是一个 shell 脚本,如果你需要了解它是如何工作的,那么相对容易做到。它所做的只是扫描你的磁盘以查找更改,并将它们与父 Mercurial 森林进行比较。然后,根据比较结果,它生成各种报告和一个补丁文件。最后,所有文件都被打包成一个 ZIP 文件,这使得与其他社区成员或只是想查看更改的人分享变得容易。

还有更多...

Webrev 有一些命令选项,如果你输入一个它不理解的命令,就会显示出来。例如,尝试运行这个:

[stas@localhost hotspot]$ ksh ../make/scripts/webrev.ksh ?

这将打印出所有可用的命令行选项和环境变量,它们可能会影响脚本的执行。

参见

将 OpenJDK v9 补丁回滚到 OpenJDK v8

在改进源代码结构的倡议之一(bugs.openjdk.java.net/browse/JDK-8051619)的范围内,OpenJDK v9 中源文件的定位方式发生了重大变化。这意味着如果有人为 OpenJDK v9 制作补丁,并希望这些更改应用于 OpenJDK v8,他/她必须遵循一个特殊的程序。该程序将在更改文件的路径上执行所需的转换。

准备工作

我们需要一个可以运行 bash shell 的计算机,即带有 Cygwin 的 Linux 或 Windows 计算机,以及 OpenJDK v9 和 OpenJDK v8 的源代码。

如何做到这一点…

按照以下步骤,开发者可以学习如何将 OpenJDK v9 的更改移植到 OpenJDK v8:

  1. 有一个特殊的脚本被创建出来,用于帮助将 OpenJDK v9 的补丁移植到 OpenJDK v8。此脚本位于 common/bin/unshuffle_patch.sh。使用 --help 参数运行此脚本以查看其用法:

    [user@localhost jdk9]$ common/bin/unshuffle_patch.sh --help
    Usage: common/bin/unshuffle_patch.sh [-h|--help] [-v|--verbose] <repo> <input_patch> <output_patch>
    where:
    <repo>            is one of: corba, jaxp, jaxws, jdk, langtools, nashorn
     [Note: patches from other repos do not need updating]
    <input_patch>     is the input patch file, that needs shuffling/unshuffling
    <output_patch>    is the updated patch file
    
    

    如果你能看到帮助输出,这意味着脚本可用,应该可以正常工作。

  2. 现在,只需在源代码树中进行必要的更改,提交代码,并生成补丁。在我们的例子中,我们将编辑 Sockets.java 文件。只需添加一行带有几个注释的新行,如下所示:

    [user@localhost jdk9]$ vi ./jdk/src/java.base/share/classes/jdk/net/Sockets.java
    now commit the change:
    [user@localhost jdk9]$ cd jdk
    [user@localhost jdk]$ hg commit
    
    
  3. 接下来,获取更改集的修订号:

    [user@localhost jdk]$ hg log -l 1
    changeset:    11063:9742f66b011
    tag:          tip
    user:         User <user@user.org>
    date:         Sun Dec 14 21:16:27 2014 +0000
    summary:      test commit
    
    
  4. 现在,将我们刚刚创建的更改集导出为扩展 GIT 格式的补丁文件:

    [user@localhost jdk]$ hg export -r 11063 --git > 11064.patch
    
    
  5. 然后,运行脚本以使补丁与 OpenJDK 8 源代码树兼容:

    [user@localhost jdk]$ ../common/bin/unshuffled_patch.sh jdk 11063.patch 11063_updated.patch
    
    
  6. 最后,将当前文件夹更改为 jdk8u 源根目录中的 jdk 目录,将更新的补丁复制到其中并应用:

    [user@localhost jdk]$ cp ../../jdk9/jdk9/11063_updated.patch ./
    [user@localhost jdk]$ hg import 11063_updated.patch
    
    

就这样。现在所需做的就是提交更改,然后遵循在 OpenJDK 中进行更改的正常流程。

参见

理解 OpenJDK 组

OpenJDK 小组审视 OpenJDK 的广泛领域,并定义支持这些领域所需的项目。例如,编译器小组资助的项目 Coin 为 JDK7 添加了新的语言特性,而 HotSpot 小组资助的项目 Graal 通过一组 API 使 VM 功能可用。小组通常比项目寿命更长,并且不会经常出现和消失。

准备工作

所需的只是能够访问互联网的电脑、一个网络浏览器和一些耐心。

如何操作…

以下步骤展示了 OpenJDK 中的文档和一些小组的示例。它们还将展示小组是什么,如何管理、创建以及它们做什么:

  1. 要熟悉 OpenJDK 中小组的定义,请访问 openjdk.java.net/groups/。那里有关于支持小组并使它们良好运行所需的各种流程和程序的信息。这些程序包括以下主题:

    • 提议成立一个新的小组。

    • 指定一个贡献者成为小组成员。

    • 指定一个小组成员成为 OpenJDK 成员

    • 指定一个小组负责人

  2. 每个小组都有一些公开的网页内容。您可以在左侧的 openjdk.java.net/ 找到这些内容,在 Groups 下。通常,小组页面上的内容具有相对标准的结构,并包含介绍、小组支持的规范列表、文档、一些关于如何贡献的指南、查找源代码的地方等信息。此外,还有邮件列表、博客和联系方式的链接。例如,您可以查看 JMX 小组网页 openjdk.java.net/groups/jmx/

  3. 我们还建议您查看项目列表,看看它们属于哪些小组。这将有助于了解小组和项目之间的关系。项目列表可以在 openjdk.java.net/ 的左侧 Groups 下找到。

参考以下内容

值得花些时间去探索 openjdk.java.net/,看看有什么可用以及哪些小组存在。正如我们之前提到的,小组的创建并不经常发生,自 2007 年以来没有创建新的小组。最后一个是在 2007 年 9 月提出的符合性小组。您可以在邮件列表存档 mail.openjdk.java.net/pipermail/announce/2007-September.txt 中找到该提案电子邮件。查看该文件中的最后一条消息。

理解 OpenJDK 项目

OpenJDK 项目旨在交付某种形式的成果,这可能是一段源代码、文档或其他内容。项目由一个或多个 OpenJDK 小组赞助。根据其性质,项目通常比小组存在的时间更短,并且通常可以涵盖 JEPs 或 JSRs 的实现。

如何操作…

这个食谱将带你了解关于 OpenJDK 项目的一些信息来源,并提供了关于它们的一些高层次信息:

  1. 与 OpenJDK 小组类似,有一个网页提供了关于项目和它们如何运作的一些定义。这个页面可在openjdk.java.net/projects/找到。在流程列表中,你会找到以下内容:

    • 成为作者

    • 指派贡献者为作者

    • 指派贡献者或作者成为提交者

    • 指派提交者成为审阅者

    • 提出新项目

  2. 与小组非常相似,每个项目在openjdk.java.net/上也有自己的页面。项目列表及其页面链接可以在网站左侧的项目部分找到。项目页面可能不是很信息丰富,但可能包含指向 wiki 的链接,通常包含大量信息。

  3. 作为项目创建的一个例子,我们可以看看项目“Sumatra”的提案,该提案可在mail.openjdk.java.net/pipermail/announce/2012-September/000135.html找到。这个帖子还包含了投票结果,因此,创建该项目的决定也是基于这个投票。

参见

  • 如同往常,建议你花些时间探索openjdk.java.net/,看看有哪些项目可供选择。

提出新 JSR

Java 规范请求JSR)是对 Java 语言、APS、JVM 等规范的变更请求。此类变更由Java 社区进程JCP)管理,社区中的每个成员都可以注册并参与审查。

这个食谱是按照提交 JSR 的方式来写的,但请记住,JSR 通常不是由单个个人提交的,而是由一群提出提案的专家团队提交的。该团队有一个规范负责人负责提交 JSR。因此,这个食谱更多的是为了让你对整个过程有一个高层次的理解,帮助你了解 JSR 是如何运作的,以及它涉及的内容。要获得更深入的见解,请参阅“参见”部分提供的相关资源。

在以下食谱中,我们可以看到,成为 JSR 的规范负责人不仅是一个技术职位,还涉及到与人们做大量工作,并需要具备一定量的软技能和领导力。领导者必须推动项目前进,并能够处理困难情况。可能会出现某些专家组成员由于某些原因不能再参与的情况,或者存在个性冲突。另一种可能的情况是,JSR 本身面临复杂的问题,并受到其他社区成员的挑战。这些问题必须得到清晰且知识丰富、充满热情的回答,以便人们相信继续前进并将该 JSR 纳入 JDK 是值得的。

准备工作

由于这是一个更程序化的食谱,所需的一切仅是一台可以访问互联网的计算机和浏览器。

如何去做...

以下步骤从高层次上概述了 JSR 生命周期的各个阶段,从想法开始,到实际实施变更结束:

  1. 首先,创建一个 JCP 账户是个不错的选择。这是提交 JSR 提案和参与 JCP 任何部分所必需的。这个过程非常简单。只需访问jcp.org/en/home/index,然后按照注册链接中的步骤操作。

  2. 好的想法是探索网站,看看那里已经有什么。所有 JSR 的列表可在www.jcp.org/en/jsr/all找到。由于整个列表可能包含太多信息,有选项可以按审批流程的阶段(www.jcp.org/en/jsr/stage)、技术(www.jcp.org/en/jsr/tech)、委员会(www.jcp.org/en/jsr/ec)或平台(www.jcp.org/en/jsr/platform)来过滤 JSR。还有一个按投票结果列出的 JSR 列表,你可以在www.jcp.org/en/jsr/vote_summary找到每年投票的结果。

  3. 如果你的提案是值得做的事情,那么,类似于所有 OpenJDK 变更流程,你需要在相关的邮件列表上描述你的提案。这将确保整个流程合理,并有助于提高材料的质量。作为规范负责人,你需要有追随者,也就是说,一群将参与 JCR 并推动其前进的专家。这样的人可以在邮件列表、相关论坛上找到,或者可以是任何有正确心态并认为你的想法值得去做的人。

  4. 要进行实际提交,请填写 Java 规范请求提案,该提案可在 jcp.org/en/jsr/proposal 获取。提交后,JSR 必须经过 JSR 审批投票,这将决定初始 JSR 提交是否应该被批准。

  5. 在下一阶段,该小组必须开始作为一个团队工作,推进提案,讨论它,并回答来自社区其他成员的问题。定期进行团队电话会议和定期的面对面讨论也可能有益。这个阶段可能是最重要的,因为它形成了 JSR 的精确形状。

  6. 所有讨论都必须公开进行,提出 JSR 的专家小组必须公开回答所有提出的问题。这也意味着必须有一个公开可用的所有相关通信的存档。

  7. 当收到所有评论和问题的所有回复,并且 JSR 已相应更新后,它就准备好进行最终提交。规范负责人负责完成并提交 技术兼容性套件TCK)和 参考实现RI)。如果 JSR 针对多个环境,则可能需要为每个平台提交一个 TCK 和 RI。提交必须遵循 JCP 2 流程中 最终发布 部分所述的过程,见 jcp.org/en/procedures/jcp2#4

  8. 在最终成功通过投票后,规范负责人将成为维护负责人,JSR 将进入维护阶段。有关详细信息,请参阅 JCP 2 流程中的 维护 部分,见 jcp.org/en/procedures/jcp2#5

参见

由于整个过程由 JCP 管理,因此您最好查阅 jcp.org/en/procedures/jcp2 上的文档,该文档描述了流程的最新版本。在撰写本文时,最新版本为 2.9。

在 JCP 网站 jcp.org/en/resources/speclead_life 上,有一篇关于成为规范负责人的好文章,其中包含一些有价值的见解和建议。它涵盖了该角色的几乎所有方面,从 JSR 的提交开始,到建立团队和与人沟通结束。它绝对值得一读。

作为 JSR 流程的一部分,需要提交 技术兼容性套件TCK)。这是设计用来确保特定实现符合 JSR 的测试套件。这部分可以被认为是 JSR 申请中最复杂的一部分。实现测试套件的最常见工具是 JT Harness,但也有一些情况下 TCK 可能基于 JUnit 或 TestNG 实现。

下一个 JCP 版本将更新一些当前流程。这次修订由 JSR 358 负责。要了解更多信息,请访问其主页java.net/projects/jsr358/pages/Home和 JRS 页面jcp.org/en/jsr/detail?id=358

提出新 JEP

JEP 是 JDK Enhancement Proposal 的缩写。它意味着 OpenJDK 中相对较大的变更,需要大量的实施工作,但它并不暗示 Java 规范的变化。JEP 的定义包含在JEP 1 JDK Enhancement-Proposal & Roadmap Process中,该文档解释了 JEP 定义的细节、流程以及所需的文档。根据 JEP 1(openjdk.java.net/jeps/1)的定义,JEP 必须满足以下至少一项标准:

  • 需要两周或更长时间的工程努力

  • 它对 JDK 或其开发流程和基础设施做出了重大改变

  • 受到开发人员或客户的强烈需求

此食谱将涵盖 JEP 的定义、其生命周期以及如何找到有关它们的最新信息。它还将介绍创建 JEP 所需的步骤。这种方法将使您对流程有一个良好的理解,并了解 JEP 是什么。

准备中

此食谱不需要任何特殊工具。你只需要这本书,以及最好有一台带有网络浏览器的电脑。

如何做...

首先,查看已经提交的 JEP 是一个好主意。要获取 JEP 的完整列表,只需访问openjdk.java.net/jeps/。在那里,您可以找到 JEP 的完整列表,包括其状态、名称以及更多相关信息。以下是一个示例:

如何做...

如您从列表中看到的,JEP 表有多个列,提供了简要概述和一些附加信息。例如,第一列是 JEP 的类型,P代表ProcessF代表Feature等。如果您点击JEP并查看其标题,就不难找出特定值的含义:

如何做...

假设我们有一些完全新的内容,不在列表中,但肯定需要成为 OpenJDK 的一部分:

  1. 下一步是阅读JEP 1: JDK Enhancement-Proposal & Roadmap Process,该文档可在openjdk.java.net/jeps/1找到。它涵盖了流程和流程的一些机制。

  2. 下一步是进行更多阅读。JEP 2: JEP 模板 包含了 JEP 的模板。这个模板必须填写提案的详细信息。这些细节将包括概述、测试、依赖关系等。查看其他 JEP 示例以填写模板是值得的。还有一个样本草案,可在 cr.openjdk.java.net/~mr/jep/draft-mcimadamore-inference-01.md 找到。

  3. 在尝试发布 JEP 之前,将提案提交给适当的 OpenJDK 邮件列表进行讨论是个好主意。这将帮助您制作出高质量的提案。

  4. 当提案准备提交时,请将其发送到 <jep-submit@openjdk.java.net>。在此之后,假设提案质量足够,它将可在存储库 (hg.openjdk.java.net/jep/jeps) 和本食谱第 1 步中提到的网页上找到。

  5. 在此之后,将会有几轮更新和讨论,这最终可能导致 JEP 被批准,其状态变为 Candidate。这意味着它被接受并包含在内,并且有很大机会被资助用于下一个版本。值得一提的是,JEP 也可能被拒绝,这意味着它被认为根本不值得实施。

  6. 当一个 JEP 转变为 Funded 状态时,这意味着小组或区域负责人愿意资助它。这意味着实际的实施现在可以开始,并将被包含在未来的某个版本中。

它是如何工作的…

如前所述,详细流程在 JEP 1: JDK 增强提案 & 路线图流程 中描述。与所有 OpenJDK 变更一样,此流程需要社区和 OpenJDK 成员的大量参与。

另请参阅

第十一章。故障排除

在本章中,我们将涵盖以下主题:

  • 在流程工作流程中导航

  • 向 OpenJDK 错误系统提交缺陷

  • 使用 NetBeans 创建补丁

  • 创建代码审查

简介

OpenJDK 的开放性是我们快节奏世界中最有价值的特性。我们可以确信,当需要时,它永远不会消失并被遗忘。如果其支持被终止,我们可以自己支持它。由于代码是开放的,我们可以独立修复错误。

然而,OpenJDK 以及 Oracle Java 实现仍然存在许多问题。其中一些是安全问题,应尽快修复。一些问题可能几乎看不见,甚至只对几乎不存在的百分之一的客户有价值。它们可能在生产 JDK 中永远不会被修复,但每个人都有机会尝试自己修复。

过去的 OpenJDK 使用 Bugzilla 错误跟踪器来跟踪错误。Bugzilla 是一个知名但道德上过时的项目,最初由 Terry Weissman 于 1998 年编写。它从一开始就是开源的,现在全世界有成千上万的人在使用它。它非常简单且易于使用。

然而,在一段时间前,OpenJDK 基金会决定从 Bugzilla 切换到 JIRA,这是一个私有但强大的错误跟踪系统,主要用 Java 编写。JIRA 支持不同的工作流程类型,如 Scrum、Kanban 以及自定义敏捷工作流程,同时还包含了所有 Bugzilla 的功能。

有时有机会扩展 OpenJDK 的功能,并创建一个功能齐全的 JSR 原型,以对 OpenJDK 开发做出贡献,这对你的项目有很大好处。有时你需要切换到其他实现,如 GNU 类路径,甚至将一些解决方案合并到你的特定版本中。

在本章中,我们将了解流程工作流程是如何组织的,需要遵循哪些步骤,以及如何应对社区流程。我们还将了解如何提交错误,并在其上创建补丁。

在流程工作流程中导航

对于开发者来说,了解流程工作流程以及像 OpenJDK 这样的复杂项目中是如何做事的至关重要。在本食谱中,我们将解释在贡献 OpenJDK 时是如何做事的。我们将解释工作是如何组织的,团队是如何合作的。

准备工作

我们需要一个互联网连接和一个浏览器。

如何操作...

我们将遍历 OpenJDK 流程工作流程,看看如何为 OpenJDK 做出贡献。在开始工作流程之前,你需要遵循一些初步步骤:

  1. 首先,你需要成为一名贡献者。为此,你需要签署一份 Oracle 贡献者协议,并将其电子邮件发送给 Oracle。

    注意

    你可能会发现以下链接很有用:

    如何成为openjdk.java.net/contribute/的贡献者

    了解在 mail.openjdk.java.net/mailman/listinfo 上讨论的内容。

  2. 然后,你需要找到一些有趣的事情来工作。

  3. 现在,你可以自由地使用 JIRA 缺陷跟踪系统讨论和提交补丁。

假设你发现了一个缺陷:

  1. 当发现缺陷时,检查它是否已经在 JIRA 中。

  2. 点击 问题 并搜索问题。你将看到以下屏幕:如何操作...

  3. 在高亮部分,你可以看到一个过滤器面板。结合关键字搜索使用它。注意右下角的 切换到高级 链接。

  4. 如果缺陷尚未在 JIRA 中存在,请创建一个 JIRA 问题。

    小贴士

    JIRA 是一个常用的缺陷跟踪工具。你可以在 confluence.atlassian.com/display/JIRA/ 找到如何添加问题的方法。

  5. 如果你不知道如何解决这个问题,这是你参与的圆满结局。OpenJDK 团队将感激了解有关此问题的所有可用信息。如果你有任何补充,欢迎在评论中发布信息。

  6. 然而,如果你觉得你可以解决这个问题,请在评论中说明,并指出你正在努力解决解决方案。在某些情况下,你可能需要等待 JIRA 工单分配给你。个人工作流程在这方面采用不同的方法。

  7. 然后,解决解决方案并创建补丁。

    补丁创建过程在本章的 使用 NetBeans 创建补丁 菜单中描述。你还可以在 第八章 中找到有关构建、调试和编辑 OpenJDK 代码的一些有用信息,Hacking OpenJDK

  8. 如果你是一个提交者(例如,你有提交权限到 OpenJDK 仓库),你可以自由地使用 webrev 创建代码审查(使用 bitbucket.org/adoptopenjdk/betterrev 中的信息来学习如何创建代码审查)。当它被批准后,你可以将你的更改提交到仓库,并将你的问题标记为已解决。

  9. 如果你不是提交者,你也可以创建一个 webrev 审查,但提交过程略有不同。你可能需要将你的代码更改推送到 Bitbucket 仓库,并附带一个 pull 请求。这已经在 创建代码审查 菜单中详细描述。更多关于 OpenJDK 测试过程的信息,请参阅 第九章,测试 OpenJDK

它是如何工作的…

OpenJDK 有一个既定的工作流程,这可能会因项目而异。尽管所有情况下都有团队合作规则,但为了使工作富有成效,每个人都应该遵循这些规则。

在这个过程的底层,还有其他团队成员,他们的职能是审查和测试这些更改。

向 OpenJDK 缺陷系统提交缺陷

本指南将向您展示如何向 OpenJDK 错误系统提交缺陷。此错误系统供没有 OpenJDK 开发者访问权限的人使用。对于 OpenJDK 开发者,有 JIRA。

如何操作...

我们将考虑提交缺陷到 OpenJDK 的必要步骤。

首先,我们将描述填写错误报告的一些先决条件:

  1. 首先,快速搜索错误。可能已经创建了类似甚至相同的错误。即使您对这个主题有更多要说的,也请在现有主题中发表,而不要创建新的主题。

  2. 然后,考虑缺陷的可重现性。需要详细描述如何重现您的缺陷以及可以和不可以重现的情况。

  3. 此外,您还可以创建并添加日志、跟踪和其他调试信息。

    小贴士

    请包括尽可能完整的日志,不要有删减。日志中的任何字符串都可能至关重要。

  4. 此外,您还可以将截图与日志一起包含,尤其是在您描述 UI 问题的时候。

  5. 尽可能多地包含系统信息。例如,对于 Sumatra 项目,甚至您的显卡驱动程序版本也可能有帮助。

如果我们需要报告一个问题,而不需要引用代码片段来描述它,而是一般性地描述,不具体说明,我们可以填写标准错误报告,如下所示:

  1. 按照以下截图所示填写字段:如何操作...

  2. 然后,点击继续

您将需要提供一些信息以获得团队的反馈:

如何操作...

它是如何工作的…

OpenJDK 项目有一个非开发者错误报告系统,您可以在其中提交您的更改,方便地提交更改。然而,它有一个 JIRA 错误跟踪器,开发者使用它以更复杂的方式处理各种错误。

使用 NetBeans 创建补丁

本节将向您展示如何使用 NetBeans 创建 OpenJDK 项目的补丁。

准备工作

您需要一个配置好的开发环境。最好使用 NetBeans,因为 NetBeans 是开发 OpenJDK 和调试的标准工具。

如何操作...

我们将通过以下步骤使用 Mercurial 和 NetBeans 进行简单的补丁创建:

  1. 使用 NetBeans 打开 OpenJDK 项目。

  2. 按照以下截图所示在项目代码中进行一些更改:如何操作...

  3. 然后,按照前面截图所示的上下文菜单链进行操作。

  4. 点击确定按钮。如何操作...

  5. 您的补丁将保存到指定的目录。稍后,您可以使用 Mercurial 上下文菜单中的 应用差异补丁 项应用补丁。

它是如何工作的

NetBeans 调用一个程序,该程序将差异(您的未提交更改与存储库中的最新版本)导出到一个文件,可以在需要时读取和应用。

另请参阅...

此外,不同分支之间的差异也可以导出。要这样做,只需单击突出显示的项目:

另请参阅...

创建代码审查

OpenJDK 使用 webrev 作为代码审查工具。创建代码审查和处理过程由 AdoptOpenJDK 社区承担,该社区开发了一个名为 Betterrev 的网络工具。也可以使用 Review Board 以及 Mercurial 插件。它自动生成代码审查,与 Oracle 仓库同步,并执行其他有用任务。

准备工作

我们需要一个能够构建 OpenJDK 且能够处理大量代码的计算机。此外,我们还需要安装一个开发环境,如第八章中所述的黑客 OpenJDK,以及互联网接入。

如何操作...

我们将通过遵循给定的步骤使用 Betterrev 工具创建代码审查:

  1. 首先,让我们从 Bitbucket 克隆一个仓库。

  2. 前往 Betterrev Bitbucket 网址 bitbucket.org/adoptopenjdk/betterrev/src如何操作...

  3. 使用高亮按钮进行 Fork 操作:如何操作...

  4. 名称 字段中指定仓库名称,如有需要,添加描述。

  5. 如果您想使您的仓库私有,请勾选 这是一个私有仓库 复选框。

  6. 点击 Fork 仓库 按钮。

  7. 您将看到以下界面一段时间。这需要一些时间,但请放心,一切都在顺利进行。如何操作...

  8. 对代码中需要审查的更改进行修改。

  9. 将它们提交到您的本地仓库。

  10. 按照所示进行拉取请求:如何操作...

Betterrev 将自动为您的问题生成一个审查。

第十二章。与未来技术合作

在本章中,我们将涵盖以下主题:

  • 在 Mac OS X 上使用 Clang 构建 OpenJDK 9

  • 在 Windows 上使用 MSYS 构建 OpenJDK 9

  • 运行和测试 OpenJDK 9 的早期访问预览

  • 使用 Jigsaw

  • 使用 Graal 构建 OpenJDK 9

  • 使用 Sumatra 构建 OpenJDK 9

简介

Java 经常因为某些程度的保守性而受到批评,尤其是在涉及主要语言变化时。然而,最近的 Java 8 版本已经做了很多工作来缓解人们对 Java 将保持保守和停滞不前的担忧。

然而,还有更多的变化即将到来。Java 9 被认为将支持一些长期期待的功能,这可能会将其带入一个全新的市场和编程水平。

在过去,注解支持和泛型在 Java 编程中引起了一场革命。思维方式发生了改变,尽管在低级设计中没有添加任何全新的内容,但高级设计和编程技术无疑发生了变化。结果是,例如,基于注解的框架数量增加,整体编程变得更加简单。

Java 8 已经发布了支持 lambda 表达式、类型注解和公共用途的参数反射。但至少从 2012 年底开始就可以使用它。在官方发布日期之前,就可以拥有所有这些功能,编写具有所有这些特性的程序,享受测试新技术带来的乐趣。一些企业开发者认为,即使在发布后,现代 Java 仍然是不稳定的,并且有些不可预测。然而,任何对新技术测试和支持感兴趣并为之做出贡献的程序员,都能在开发阶段测试和尝试 OpenJDK 8。

OpenJDK 9 早期访问预览在 Java 8 发布后立即发布。因此,我们现在可以尝试 OpenJDK 9。当然,它仍然不稳定,甚至没有通过一些回归测试,但它将会改变。

OpenJDK 8 和 9 之间有哪些主要区别?

有三个主要特性:

  • 第一项是长期期待的类型擦除的消除。通过新的精细泛型,将能够在每个泛型引用上确定在集合、映射或元组中使用的是哪种类型。对于所有 Java 程序员来说,这将是一个巨大的缓解。

  • 第二个特性旨在为 Java 平台带来一个新的盟友——GPU 的全部力量现在将掌握在程序员手中,只需使用标准特性,无需任何原生代码。这一点将在本章中进一步解释。

  • 第三项是 Graal 项目,它将 Java VM API 暴露给最终用户。这是一个巨大的突破,因为现在可以即时更改 Java 的操作方式。

还有更多的工作要做;Java 9 也将包含更少的 GC 类型,而不会降低性能。

小贴士

注意,下划线 (_) 在 Java 9 中将不会是一个合法的标识符名称,所以请及时准备你的代码。更多详细信息请查看 openjdk.java.net/jeps/213

此外,对于那些处理货币交易和金融分析的人来说,Java 9 还有一个特性——JSR 354,货币 API。它将实现 ISO-4217 标准货币和一些额外的货币。还将引入货币算术。

小贴士

要测试 Java Money API,从 github.com/JavaMoney/jsr354-api 构建源代码。然而,这是一个满足 JSR 要求的 Maven 项目,不是 OpenJDK 项目的组成部分。

在 Mac OS X 上使用 Clang 构建 OpenJDK 9

在撰写本文时,OpenJDK 9 项目仍然与 OpenJDK 8 非常相似。因此,关于构建 OpenJDK 9 的大部分信息都可以在 第四章,构建 OpenJDK 8 中找到。

区分 OpenJDK 9 和 OpenJDK 8 的一个点是 Mac OS X 上使用 Clang 编译器。从 Xcode 版本 5 开始,Clang 成为了 Mac OS X 的官方编译器,而不是 GCC。原本计划将其用作 Mac OS X 上 OpenJDK 8 的官方编译器,但这个切换被推迟到了 OpenJDK 9。

在这个配方中,我们将使用 Xcode 5 和 Clang 编译器构建 OpenJDK 9 的当前代码库。

准备工作

对于这个配方,我们需要一个干净安装了 Mercurial 源控制工具的 Mac OS X 10.8 Mountain Lion 或 10.9 Mavericks 系统。

如何操作...

以下步骤将帮助我们构建 OpenJDK 9:

  1. developer.apple.com/xcode/ 下载 Xcode 5(需要 Apple 开发者账户,注册免费)并安装它。

  2. 使用之前提到的相同下载链接下载对应 Xcode 小版本的命令行工具,并安装它。

  3. 从终端运行以下命令以设置命令行工具:

    sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer/
    
    
  4. 安装 JDK 8—Oracle 发行版,或者可以使用预构建的 OpenJDK 二进制文件。

  5. 从 Mercurial 仓库获取源代码:

    hg checkout http://hg.openjdk.java.net/jdk9/jdk9/
    bash ./get_source.sh
    
    
  6. 运行 autotools 配置脚本:

    bash ./configure –-with-boot-jdk=path/to/jdk8
    
    
  7. 开始构建:

    make all 2>&1
    
    
  8. 构建的二进制文件将被放入以下目录:

    build/macosx-x86_64-normal-server-release/images/j2sdk-image
    
    

它是如何工作的...

Xcode 5 默认使用 Clang 编译器,OpenJDK 9 已经对所有从 GCC 切换到 Clang 所需的调整。

还有更多...

在 OpenJDK 9 的早期版本中,可能需要安装 X11 服务器。X11 服务器可以从 XQuartz 项目中进行安装。

参见

  • 从 第四章,构建 OpenJDK 8 中获取构建 OpenJDK 8 在 Mac OS X 上的配方

在 Windows 上使用 MSYS 构建 OpenJDK 9

Windows 操作系统有着悠久的提供类似 Unix 环境的不同工具的历史。在 Windows 历史的各个时期,存在过诸如 Microsoft POSIX 子系统、Interix、Windows Services for UNIX、MKS Toolkit、Cygwin、MinGW/MSYS 等工具,它们提供了不同级别的 Unix 兼容性。

这三个后者的工具与 OpenJDK 构建最为相关。MKS Toolkit 曾用于 Sun Microsystems 的内部构建,因为它提供了比 Cygwin 更好的速度。OpenJDK 7 不再支持 MKS Toolkit。我们在第二章网站下载并安装 Mercurial SCM 工具。

  1. 将 OpenJDK 9 当前的开发森林克隆到C:\openjdk目录:

    hg clone http://hg.openjdk.java.net/jdk9/jdk9/
    
    
  2. mingw.org/下载mingw-get实用程序(mingw-get-setup.exe)并将其安装到C:\MinGW路径。

  3. 运行cmd.exe外壳程序并导航到目录。

  4. 运行以下命令将已安装的 MSYS 版本从最新版本回退到 1.0.17:

    mingw-get install --reinstall --recursive msys-core=1.0.17-1
    
    
  5. 运行以下命令安装所有必需的 MSYS 软件包:

    mingw-get install msys-zip
    mingw-get install msys-unzip
    mingw-get install msys-diffutils
    mingw-get install msys-file
    mingw-get install msys-mktemp
    mingw-get install msys-tar
    mingw-get install msys-xargs
    mingw-get install msys-findutils
    mingw-get install msys-make
    
    
  6. 使用C:\MinGW\msys\1.0\msys.bat文件运行 MSYS 外壳程序。

  7. 导航到c/openjdk目录,并使用以下命令下载所有 OpenJDK 子存储库的源代码:

    bash ./get_source.sh
    
    
  8. PATH变量中添加 JDK 8 二进制文件的路径:

    export PATH=/c/jdk8/bin:$PATH
    
    
  9. 更改所有源文件的文件系统权限:

    chmod -R 777 .
    
    
  10. 指定 FreeType 二进制文件的路径运行配置脚本:

    ./configure --with-freetype=/c/freetype
    
    
  11. 同时将输出写入屏幕和日志文件以启动构建过程:

    make | tee make.log
    
    
  12. 等待构建完成。

它是如何工作的...

在撰写本文时,OpenJDK 9 代码使用与 OpenJDK 8 相同的工具链,因此环境设置类似。

我们使用 MSYS 的 1.0.17 版本,因为 1.0.18 版本中出现了与多核支持相关的回归问题。这个回归问题在撰写本文时尚未修复,但很可能将在后续版本中得到修复。

mingw-get实用程序是一个软件包管理器,允许我们安装或更新所需的 MSYS 软件包。

参见

运行并测试 OpenJDK 9 的早期访问预览

我们将获取最新的 OpenJDK 代码,并测试功能可用性。不要犹豫去尝试新功能,它们可能在更新的版本中可用。自从 OpenJDK 9 发布以来,这将是测试 OpenJDK 9 最快的方式。希望发布时,同样的事情也会适用于 OpenJDK 10。

准备工作

您需要一个互联网连接。除此之外,不需要任何东西。

如何操作...

我们将下载、解包并运行最新的公开可用完整 OpenJDK 构建:

  1. 打开jdk9.java.net/页面。

  2. 下载早期访问预览,如图所示:如何操作...

  3. 运行安装程序。

  4. 您可以在本书的第一章 OpenJDK 入门中找到如何从存档中安装 OpenJDK 的方法。

测试早期访问 JDK 中已经包含的一些有趣的功能。查看以下代码:

 DatagramSocket socket = new DatagramSocket(4445);
 System.out.println(socket.supportedOptions());

当执行时,您可能期望它返回以下字符串或类似的内容:

[SO_SNDBUF, SO_RCVBUF, SO_REUSEADDR, IP_TOS]

虽然有一些小的改进和大量的错误修复,但在早期访问预览中还没有任何重大变化。

它是如何工作的...

hg.openjdk.java.net/jdk9/jdk9的源代码仓库中,有一些标签,例如jdk9-b<build number>,它们会自动构建成早期访问版本。尽管没有夜间构建,但如果您有很多时间和一台足够强大的机器,您始终可以从源代码构建它们。

不要忘记更新您已安装的版本——将会有真正重大和令人兴奋的变化,包括下面解释的。迟早,开发者会推出完整的 Java 9 版本,那时将有机会在它成为生产就绪之前对其进行测试。

还有更多...

您也可以从源代码构建 OpenJDK 9:

  1. 克隆源代码仓库hg hg.openjdk.java.net/jdk9/jdk9

  2. 获取 OpenJDK 子项目的源代码:

    chmod 755 ./get_source.sh && ./get_source.sh
    cd jigsaw
    
    
  3. 然后配置要构建的 OpenJDK 实例:

    chmod 755 ./configure.sh && ./configure.sh
    
    
  4. 最后,进行构建本身:

    Make
    
    
  5. 您输出的最终字符串将看起来像这样:

    -------------------------
    Finished building OpenJDK for target 'default'
    
    

使用 Jigsaw

Jigsaw 是 Java 全新的模块化系统。它让人联想到一些现有的产品,如 Maven 或 Gradle,但它的最有趣的特点是 JDK 本身的模块化可能性。Jigsaw 将允许在其完全完成后,甚至对一些被认为不可分割的功能进行模块化,例如 HotSpot 二进制文件。

Jigsaw 包含了关于 Java 模块系统的提案。模块化意味着可扩展性——从小型、嵌入式设备,它们只需要基本功能且性能较差,到拥有数十台机器的全规模数据中心。其中一些目标已经实现——但 Jigsaw 提供了一个在所有平台上解决依赖关系的通用方式,从 Java 本身开始。

几个 JEP 是 Jigsaw 的一部分:

  • JEP 200:这使得 JDK 本身模块化

  • JEP 201:这使得源代码模块化

  • JEP 220:这使得运行时 Java 镜像模块化,因此它们可以部分加载

关于 JEPs 进度的某些信息可以在以下 JIRA 链接中找到:

此外,Jigsaw 的核心是 JSR 376——Java 平台模块系统。

准备中

你将需要互联网访问。此外,希望有一些使用 Maven 或类似软件的经验。任何关于构建系统内部工作原理的知识都将受到欢迎。

如何做...

以下步骤将教你如何构建启用 Jigsaw 的 Java:

  1. 首先,让我们从hg.openjdk.java.net/jigsaw/jigsaw克隆一个源代码仓库hg

  2. 然后,让我们获取 OpenJDK 子项目的源代码:

    chmod 755 ./get_source.sh && ./get_source.sh
    cd jigsaw
    
    
  3. 然后配置要构建的 OpenJDK 实例:

    chmod 755 ./configure && ./configure
    
    
  4. 最后,进行构建本身:

    make all
    -------------------------
    Finished building OpenJDK for target 'default'
    
    

恭喜,你已经构建了启用 Jigsaw 的 Java。

现在,我们将进行一些测试:

  1. 让我们考虑简单的helloworld1程序:

    package me.dsmd.jigsawsample
    import me.dsmd.helloworldstrings.Strings
    public class Main{
      public void main(String [] args){
        System.out.println(Strings.helloworld1());
      }
    }
    
  2. 它有一个类,这个类是从一个尚不存在的包中导入的。

  3. 让我们创建它。

    package me.dsmd.helloworldstrings
    public class Strings{
      public String helloworld1(){
        return "Hello World";
      }
    }
    

现在,我们将尝试使用 Jigsaw 链接它。

Jigsaw 将模块声明存储在名为module-info.java的文件中。

  1. 让我们为这两个包创建如下:

    module  me.dsmd.jigsawsample @ 1.0{
     requires me.dsmd.helloworldstrings
     class me.dsmd.jigsawsample.Main
    }
    module  me.dsmd.helloworldstrings @ 0.1 {
     class me.dsmd.helloworldstrings.Strings
    }
    
    

    这些文件应放置在包的根目录中。

  2. 让我们考虑这样一个情况,即所有这些模块都放置在同一个名为src的目录中:

    .
    ├── modules
    └── src
     ├── me.dsmd.helloworldstrings
     │   ├── me
     │   │   └── dsmd
     │   │       └── helloworldstrings
     │   │           └── Strings.java
     │   └── module-info.java
     └── me.dsmd.jigsawsample
     ├── me
     │   └── dsmd
     │       └── jigsawsample
     │           └── Main.java
     └── module-info.java
    
    
  3. 然后,让我们使用javac从你的 jigsaw 构建中编译它们:

    javac -verbose -d modules -modulepath modules -sourcepath \ `find src -name '*.java'`
    
    

它是如何工作的...

Jigsaw 是一个模块化系统,为 Java 提供了它自己的构建系统。在 Java 世界中,很久以前就出现了类似的系统,但它们缺乏核心支持。它们从未能够将模块化支持的优点带给 Java 本身的功能。当然,也有一些缺点。一次编写,到处运行的口号不像以前那样适用了。

我们使用新构建的 OpenJDK 命令来创建、安装和导出模块。这些命令仍在积极开发中,但规范已经写出,所以,希望在生产访问发布之前不会有显著的变化。

还有更多...

你还可以将模块作为库安装。为此,运行以下命令:

  • 要创建一个模块库:

    $ jmod -L lib1 create 
    
    

    这将创建一个模块库lib1

  • 要将一些模块安装到库中:

    $ jmod -L lib1 install modules module1 module2
    
    

    这将在库lib1下将你的模块安装到系统父目录下。

目前,没有方法可以从库中删除模块。也许,在发布时也不会有。目前,从库中删除模块的最简单方法是物理地从存储库中删除它:

rm -rf lib1/module1

此外,如果您要运行一个模块,如果它包含标准入口点:

java -L lib1 -m module1

小贴士

新的-m选项也仅包含在 Jigsaw 启用的 Java 命令中。截至现在(2014 年 6 月),它不包含在任何公共早期访问预览中。

作为下一个特性,您可以通过执行以下命令将模块导出为文件:

jpkg -m modules/module1 jmod module1

将创建module1@<module version>.jmod文件。它将包含已导出、准备使用的模块。

使用 Graal 构建 OpenJDK 9

如项目页面所述,Graal 是:

JVM 对自身 J 的探索

在完成此项目后,JVM 功能将通过 Java API 公开,因此最终用户将能够访问最底层的操作。例如,将能够用 Java 编写 Java 编译器。现在,我们将尝试对其进行测试。

此外,还有Truffle,这是一个框架,允许您使用 Graal VM 构建自己的语言。它基于抽象语法树AST)的概念,实际上这个过程非常简单。为了更好地了解,请查看以下链接:

cesquivias.github.io/blog/2014/10/13/writing-a-language-in-truffle-part-1-a-simple-slow-interpreter/

准备工作

您需要互联网连接。此外,建议阅读有关构建 OpenJDK 的章节。

如何做...

查看以下步骤以使用 Graal 构建 OpenJDK:

  1. 首先,克隆一个源代码库:

    hg clone http://hg.openjdk.java.net/graal/graal
    cd graal
    export EXTRA_JAVA_HOMES=/usr/lib/jvm/java-1.7.0-openjdk
    ./mx.sh build
    
    
  2. 输入一个选择值,vm graal将基于此构建。不幸的是,它不会在未经修改的情况下针对 OpenJDK 9-ea 预览版构建:

    ./mx.sh build
    [1] /usr/lib/jvm/java-1.7.0-openjdk
    [2] /usr/lib/jvm/java-1.6.0-openjdk-amd64
    [3] /usr/lib/jvm/java-6-oracle
    [4] /usr/lib/jvm/jdk1.9.0
    [5] /usr/lib/jvm/java-7-oracle
    [6] /usr/lib/jvm/java-8-oracle
    [7] /usr/lib/jvm/java-6-openjdk-amd64
    [8] /usr/lib/jvm/java-7-openjdk-amd64
    [9] /usr/lib/jvm/java-1.7.0-openjdk-amd64
    [10] <other>
    
    
  3. 然后,选择要执行的 VM 类型。有两种类型的 VM。简而言之,server vm将使用默认的 hotspot 编译,仅使用 Graal 本身进行显式 Graal API 调用,而graal VM将通过 Graal 编译一切。第一个选项更适合生产 VM,而第二个选项更适合测试目的。

    Please select the VM to be executed from the following:
    [1] server - Normal compilation is performed with a tiered system (C1
     + C2), Truffle compilation is performed with Graal. Use this for
     optimal Truffle performance.
    [2] graal - Normal compilation is performed with a tiered system (C1 +
     Graal), Truffle compilation is performed with Graal.
    
    
  4. 然后,泡一杯茶,这个过程可能需要几十分钟。

  5. 然后,如果您想初始化您的 IDE 项目,请运行./mx.sh ideinit

  6. 然后,打开您喜欢的 IDE 并打开生成的项目。这里以 IntelliJ Idea 为例:如何做...

  7. 探索各种测试。

它是如何工作的...

Graal 启用的虚拟机将向最终用户公开 Java API。

还有更多...

在第八章 Hacking OpenJDK 中,我们向 HotSpot 添加了新的内建函数,以 crc32 计算为例。在 Graal 项目中,有一个类似的测试,它测试了CRC32#updateByteBuffer方法的编译替换。它包含在com.oracle.graal.hotspot.jdk8.test包中。运行它,并享受性能变化。

使用 Sumatra 构建 OpenJDK 9

很长时间以来,Java 被认为是一个主要的后端工具,因为它具有跨平台的向量功能。只有 J2ME 能够在移动领域实现长期的优势。但现在它将改变。Sumatra 项目的目标是向 Java 开发者提供 GPU 计算标准。

准备工作

您可能需要一个支持 CUDA 和 OpenGL 的 GPU,或者一个正在运行的 HSAIL 模拟器(因为板载 GPU 不支持本地 GPU 语言)。

如何做到这一点...

Sumatra 开发者广泛使用前面提到的 Graal 项目。构建分为两个阶段。首先,Sumatra JDK 像正常的 OpenJDK 构建一样构建,如下所示:

make
Building OpenJDK for target 'default' in configuration 'linux-x86_64-normal-server-release'

## Starting langtools
## Finished langtools (build time 00:00:03)

## Starting hotspot
## Finished hotspot (build time 00:00:01)

## Starting corba
## Finished corba (build time 00:00:00)

## Starting jaxp
## Finished jaxp (build time 00:00:01)

## Starting jaxws
## Finished jaxws (build time 00:00:02)

## Starting jdk
## Finished jdk (build time 00:00:21)

----- Build times -------
Start 2014-07-06 00:17:24
End   2014-07-06 00:17:52
00:00:00 corba
00:00:01 hotspot
00:00:01 jaxp
00:00:02 jaxws
00:00:21 jdk
00:00:03 langtools
00:00:28 TOTAL
-------------------------
Finished building OpenJDK for target 'default'

第二阶段是在 Sumatra JDK 之上构建一个 Graal JDK。这可能有点棘手,但希望它能成功:

  1. 克隆一个hg仓库hg clone [hg.openjdk.java.net/sumatra/sumatra-dev/](http://hg.openjdk.java.net/sumatra/sumatra-dev/)

    chmod 755 configure && ./configure
    
    
  2. 获取源代码:

    chmod 755 get_source.sh && ./get_source.sh
    
    
  3. 然后制作源代码:

    make
    
    
  4. JAVA_HOME导出到新构建的 OpenJDK 实例:

    export JAVA_HOME=<path-to-sumatra-dev>/build/linux-<your-arch>-normal-server-release/images/j2sdk-image
    
    
  5. 构建启用了 HSAIL 的 grail:

    /mx.sh --vmbuild product --vm server build
    
    

恭喜!您有一个启用了 Sumatra 的虚拟机。

让我们做一个简单的测试。考虑一个来自官方样本的代码:

package simple;

import java.util.stream.IntStream;

public class Simple {

    public static void main(String[] args) {
        final int length = 8;
        int[] ina = new int[length];
        int[] inb = new int[length];
        int[] out = new int[length];

        // Initialize the input arrays - this is offloadable
        IntStream.range(0, length).parallel().forEach(p -> {
            ina[p] = 1;
            inb[p] = 2;
        });

        // Sum each pair of elements into out[] - this is offloadable
        IntStream.range(0, length).parallel().forEach(p -> {
            out[p] = ina[p] + inb[p];
        });

        // Print results - this is not offloadable since it is
        // calling native code etc.
        IntStream.range(0, length).forEach(p -> {
            System.out.println(out[p] + ", " + ina[p] + ", " + inb[p]);
        });
    }
}

它包含两个可卸载的 lambda 函数。我们将尝试使用HSA API 使它们并行运行。

  1. 首先,将JAVA_HOME设置为 Graal JDK:

    export JAVA_HOME=/path/to/graal/jdk1.8.0-internal/product/
    
    
  2. 然后克隆OKRA HSA接口:

    git clone https://github.com/HSAFoundation/Okra-Interface-to-HSA-Device.git
    
    
  3. 使其可运行:

    export PATH=$PATH:/path/to/okra/dist/bin
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/okra/dist/bin
    
    
  4. 运行带有和不带有卸载的示例。

  5. 您将在终端中获得以下代码:

    $JAVA_HOME/bin/java -server -esa -XX:+TraceGPUInteraction -Dcom.amd.sumatra.offload.immediate=true -G:Log=CodeGen  simple.Simple
    ...
    [HSAIL] library is libokra_x86_64.so
    [GPU] registered initialization of Okra (total initialized: 2)
    [CUDA] Ptx::get_execute_kernel_from_vm_address
    [thread:1] scope:
     [thread:1] scope: GraalCompiler
     [thread:1] scope: GraalCompiler.CodeGen
     Nothing to do here
     Nothing to do here
     Nothing to do here
     version 0:95: $full : $large; 
    // static method HotSpotMethod<Simple.lambda$main$0(int[], int[], int)> 
    kernel &run ( 
     align 8 kernarg_u64 %_arg0,
     align 8 kernarg_u64 %_arg1
     ) {
     ld_kernarg_u64  $d0, [%_arg0];
     ld_kernarg_u64  $d1, [%_arg1];
     workitemabsid_u32 $s0, 0;
    
    @L0:
     cmp_eq_b1_u64 $c0, $d1, 0; // null test
     cbr $c0, @L1;
    @L2:
     ld_global_s32 $s1, [$d1 + 12];
     cmp_ge_b1_u32 $c0, $s0, $s1;
     cbr $c0, @L8;
    @L3:
     cmp_eq_b1_u64 $c0, $d0, 0; // null test
     cbr $c0, @L4;
    @L5:
     ld_global_s32 $s1, [$d0 + 12];
     cmp_ge_b1_u32 $c0, $s0, $s1;
     cbr $c0, @L7;
    @L6:
     cvt_s64_s32 $d2, $s0;
     mul_s64 $d2, $d2, 4;
     add_u64 $d0, $d0, $d2;
     st_global_s32 1, [$d0 + 16];
     cvt_s64_s32 $d0, $s0;
     mul_s64 $d0, $d0, 4;
     add_u64 $d1, $d1, $d0;
     st_global_s32 2, [$d1 + 16];
     ret;
    @L1:
     mov_b32 $s0, -6155;
    @L9:
     ret;
    @L4:
     mov_b32 $s0, -4363;
     brn @L9;
    @L8:
     mov_b32 $s0, -6683;
     brn @L9;
    @L7:
     mov_b32 $s0, -4891;
     brn @L9;
    }; 
    
    [HSAIL] heap=0x00007f4c9801de38
    [HSAIL] base=0x05a00000, capacity=209190912
    External method:simple.Simple.lambda$main$0([I[II)V
    installCode0: ExternalCompilationResult
    [HSAIL] sig:([I[II)V  args length=2, _parameter_count=3
    [HSAIL] static method
    [HSAIL] HSAILKernelArguments::do_array, _index=0, 0x82b20888, is a [I
    [HSAIL] HSAILKernelArguments::do_array, _index=1, 0x82b208b8, is a [I
    [HSAIL] HSAILKernelArguments::not pushing trailing int
     [thread:1] scope: GraalCompiler
     [thread:1] scope: GraalCompiler.CodeGen
     Nothing to do here
     Nothing to do here
     Nothing to do here
     version 0:95: $full : $large; 
    // static method HotSpotMethod<Simple.lambda$main$1(int[], int[], int[], int)> 
    kernel &run ( 
     align 8 kernarg_u64 %_arg0,
     align 8 kernarg_u64 %_arg1,
     align 8 kernarg_u64 %_arg2
     ) {
     ld_kernarg_u64  $d0, [%_arg0];
     ld_kernarg_u64  $d1, [%_arg1];
     ld_kernarg_u64  $d2, [%_arg2];
     workitemabsid_u32 $s0, 0;
    
    @L0:
     cmp_eq_b1_u64 $c0, $d0, 0; // null test
     cbr $c0, @L1;
    @L2:
     ld_global_s32 $s1, [$d0 + 12];
     cmp_ge_b1_u32 $c0, $s0, $s1;
     cbr $c0, @L12;
    @L3:
     cmp_eq_b1_u64 $c0, $d2, 0; // null test
     cbr $c0, @L4;
    @L5:
     ld_global_s32 $s1, [$d2 + 12];
     cmp_ge_b1_u32 $c0, $s0, $s1;
     cbr $c0, @L11;
    @L6:
     cmp_eq_b1_u64 $c0, $d1, 0; // null test
     cbr $c0, @L7;
    @L8:
     ld_global_s32 $s1, [$d1 + 12];
     cmp_ge_b1_u32 $c0, $s0, $s1;
     cbr $c0, @L10;
    @L9:
     cvt_s64_s32 $d3, $s0;
     mul_s64 $d3, $d3, 4;
     add_u64 $d1, $d1, $d3;
     ld_global_s32 $s1, [$d1 + 16];
     cvt_s64_s32 $d1, $s0;
     mul_s64 $d1, $d1, 4;
     add_u64 $d2, $d2, $d1;
     ld_global_s32 $s2, [$d2 + 16];
     add_s32 $s2, $s2, $s1;
     cvt_s64_s32 $d1, $s0;
     mul_s64 $d1, $d1, 4;
     add_u64 $d0, $d0, $d1;
     st_global_s32 $s2, [$d0 + 16];
     ret;
    @L1:
     mov_b32 $s0, -7691;
    @L13:
     ret;
    @L4:
     mov_b32 $s0, -6411;
     brn @L13;
    @L10:
     mov_b32 $s0, -5403;
     brn @L13;
    @L7:
     mov_b32 $s0, -4875;
     brn @L13;
    @L12:
     mov_b32 $s0, -8219;
     brn @L13;
    @L11:
     mov_b32 $s0, -6939;
     brn @L13;
    }; 
    [HSAIL] heap=0x00007f4c9801de38
    [HSAIL] base=0x05a00000, capacity=209190912
    External method:simple.Simple.lambda$main$1([I[I[II)V
    installCode0: ExternalCompilationResult
    [HSAIL] sig:([I[I[II)V  args length=3, _parameter_count=4
    [HSAIL] static method
    [HSAIL] HSAILKernelArguments::do_array, _index=0, 0x82b208f8, is a [I
    [HSAIL] HSAILKernelArguments::do_array, _index=1, 0x82b20888, is a [I
    [HSAIL] HSAILKernelArguments::do_array, _index=2, 0x82b208b8, is a [I
    [HSAIL] HSAILKernelArguments::not pushing trailing int
    3, 1, 2
    3, 1, 2
    3, 1, 2
    3, 1, 2
    3, 1, 2
    3, 1, 2
    3, 1, 2
    3, 1, 2
    
    

它是如何工作的...

Sumatra 运行在 Graal 项目之上。由于所有与 GPU 的操作都是在虚拟机级别实现的,Sumatra 使用 Graal 来访问它们。Sumatra 的功能处于高度发展阶段,并且可能面临各种不可预测的变化。

但最终用户现在就可以使用其中的一些,以牺牲一些兼容性和标准化为代价,获得新的 Java 生产力水平。

还有更多...

在 Graal 套件中,可以测试 Sumatra HSAIL 功能。

要这样做,运行以下代码:

./mx.sh --vm server unittest -XX:+TraceGPUInteraction -XX:+GPUOffload -G:Log=CodeGen hsail.test.IntAddTest

输出应该看起来像以下这样(对于 Linux Mint 15,或其他发行版/操作系统,结果可能略有不同):

 [HSAIL] library is libokra_x86_64.so
[HSAIL] using _OKRA_SIM_LIB_PATH_=/tmp/okraresource.dir_2488167353114811077/libokra_x86_64.so
[GPU] registered initialization of Okra (total initialized: 2)
[CUDA] Ptx::get_execute_kernel_from_vm_address
JUnit version 4.8
.[thread:1] scope:
 [thread:1] scope: GraalCompiler
 [thread:1] scope: GraalCompiler.CodeGen
 Nothing to do here
 Nothing to do here
 Nothing to do here
 version 0:95: $full : $large;
// static method HotSpotMethod<IntAddTest.run(int[], int[], int[], int)>
kernel &run (
 align 8 kernarg_u64 %_arg0,
 align 8 kernarg_u64 %_arg1,
 align 8 kernarg_u64 %_arg2
 ) {
 ld_kernarg_u64  $d0, [%_arg0];
 ld_kernarg_u64  $d1, [%_arg1];
 ld_kernarg_u64  $d2, [%_arg2];
 workitemabsid_u32 $s0, 0;
@L0:
 cmp_eq_b1_u64 $c0, $d0, 0; // null test
 cbr $c0, @L1;
@L2:
 ld_global_s32 $s1, [$d0 + 12];
 cmp_ge_b1_u32 $c0, $s0, $s1;
 cbr $c0, @L12;
@L3:
 cmp_eq_b1_u64 $c0, $d2, 0; // null test
 cbr $c0, @L4;
@L5:
 ld_global_s32 $s1, [$d2 + 12];
 cmp_ge_b1_u32 $c0, $s0, $s1;
 cbr $c0, @L11;
@L6:
 cmp_eq_b1_u64 $c0, $d1, 0; // null test
 cbr $c0, @L7;
@L8:
 ld_global_s32 $s1, [$d1 + 12];
 cmp_ge_b1_u32 $c0, $s0, $s1;
 cbr $c0, @L10;
@L9:
 cvt_s64_s32 $d3, $s0;
 mul_s64 $d3, $d3, 4;
 add_u64 $d1, $d1, $d3;
 ld_global_s32 $s1, [$d1 + 16];
 cvt_s64_s32 $d1, $s0;
 mul_s64 $d1, $d1, 4;
 add_u64 $d2, $d2, $d1;
 ld_global_s32 $s2, [$d2 + 16];
 add_s32 $s2, $s2, $s1;
 cvt_s64_s32 $d1, $s0;
 mul_s64 $d1, $d1, 4;
 add_u64 $d0, $d0, $d1;
 st_global_s32 $s2, [$d0 + 16];
 ret;
@L1:
 mov_b32 $s0, -7691;
@L13:
 ret;
@L4:
 mov_b32 $s0, -6411;
 brn @L13;
@L10:
 mov_b32 $s0, -5403;
 brn @L13;
@L7:
 mov_b32 $s0, -4875;
 brn @L13;
@L12:
 mov_b32 $s0, -8219;
 brn @L13;
@L11:
 mov_b32 $s0, -6939;
 brn @L13;
};

[HSAIL] heap=0x00007f95b8019cc0
[HSAIL] base=0x05a00000, capacity=210763776
External method:com.oracle.graal.compiler.hsail.test.IntAddTest.run([I[I[II)V
installCode0: ExternalCompilationResult
[HSAIL] sig:([I[I[II)V args length=3, _parameter_count=4
[HSAIL] static method
[HSAIL] HSAILKernelArguments::do_array, _index=0, 0x82b21970, is a [I
[HSAIL] HSAILKernelArguments::do_array, _index=1, 0x82b477f0, is a [I
[HSAIL] HSAILKernelArguments::do_array, _index=2, 0x82b479e0, is a [I
[HSAIL] HSAILKernelArguments::not pushing trailing int

Time: 0.153

OK (1 test)

完成此测试意味着 HSAIL 函数正在正常工作,因此最前沿的 Java 已经从您的 GPU 中受益。

第十三章。构建自动化

在本章中,我们将介绍:

  • 安装 VirtualBox

  • 准备 SSH 密钥

  • 准备装有 Linux 的 VirtualBox 机器

  • 准备装有 Mac OS X 的 VirtualBox 机器

  • 准备装有 Windows 的 VirtualBox 机器

  • 自动化构建

  • 构建跨平台安装程序

简介

自动化构建在跨平台软件开发中被广泛使用。需要一个 构建农场,其中包含每个支持的操作系统的构建机器,以远程构建项目和在所有平台上运行测试。随着软件虚拟化工具的日益流行,使用单个物理机器部署 虚拟构建农场 变得可能。

除了每个操作系统的构建环境之外,自动化构建的关键部分是 机器和 构建 机器之间的通信。可能需要以下通信任务:

  • 主服务器应准备并启动构建机器

  • 主服务器应直接将源代码发送到构建机器,或者从构建机器本身启动源代码检索过程

  • 主服务器应启动构建过程

  • 构建机器应在构建过程中将构建日志发送到主服务器

  • 构建机器应将结果二进制文件发送到主服务器

  • 主服务器应关闭构建机器

在本章中,我们将为 Windows、Linux 和 Mac OS X 准备 OpenJDK 构建环境。这项任务可以使用高级构建自动化(或持续集成)工具来完成。然而,这样的工具在功能上可能有限,并且对于我们的任务来说可能不够灵活。虽然所有这样的工具都应该执行类似的工作(如之前所列),但不同的工具可能有不同的配置和特性,对一种工具的了解可能对另一种工具不那么有用。此外,这样的工具还引入了一个额外的复杂性层次,可能会带来特定工具的问题。

您将学习如何使用最基本的工具进行构建自动化,以便更好地理解这个过程。bash shell 已经在所有平台上用于 OpenJDK 构建(在 Linux/Mac 上是原生的,在 Windows 上通过 Cygwin),因此我们将使用 bash 脚本来设置和启动构建虚拟机。对于通信(发送命令和数据),我们将使用 SSH 协议;Unix-like 操作系统上通常预装了该协议的实现,也可以在 Windows 上安装。对于虚拟化,我们将使用来自甲骨文的流行工具 VirtualBox。

安装 VirtualBox

VirtualBox 是来自甲骨文公司的一个流行的虚拟化工具箱,它允许我们在宿主操作系统之上运行其他操作系统的 虚拟 实例。在本食谱中,我们将安装 VirtualBox 并配置宿主网络接口。这样的配置将允许我们在自动化构建过程中从宿主连接到客户机,然后再返回。

准备工作

对于这个食谱,我们需要运行 Ubuntu 12.04 amd64 操作系统。

如何操作...

以下步骤将帮助您安装 VirtualBox:

  1. 从 VirtualBox 网站([www.virtualbox.org/](https://www.virtualbox.org/))下载安装包并安装。

  2. 安装虚拟网络接口包:

    sudo apt-get install uml-utilities
    
    
  3. 创建一个虚拟接口tap0,该接口将用于连接到和从客户机:

    sudo tunctl -t tap0 -u your_username
    
    
  4. 配置接口tap0

    sudo ifconfig tap0 192.168.42.2 up dstaddr 192.168.42.1 netmask 255.255.255.0
    
    
  5. 使用 GUI 表单创建任意 VirtualBox 机器。

  6. 导航到设置 | 网络表单,当使用桥接适配器模式时,检查tap0网络接口是否在接口下拉列表中可用。

工作原理...

VirtualBox 支持为虚拟机提供不同的网络选项。其中之一——桥接适配器——允许我们使用静态 IP 地址从主机连接到客户机并返回。要在主机端设置此模式,我们需要一个额外的虚拟网络接口和单独的地址。uml-utilities包中包含的tunctl实用程序允许我们创建一个如tap0这样的虚拟接口。

更多内容...

可以创建多个具有不同地址的虚拟接口,以同时运行多个虚拟机。Mac OS X 可以用作主机机器,需要tuntaposx内核扩展来使用tunctl实用程序。

参见

准备 SSH 密钥

在本章中,我们将使用虚拟机来构建 OpenJDK。在构建源代码、控制命令、日志和结果二进制文件时,应在主机和虚拟机之间发送。我们将使用无处不在的安全外壳协议SSH)及其最流行的实现OpenSSH来完成这些任务。

SSH 允许我们在机器之间发送数据并远程运行命令。当客户端执行 SSH 连接时,它应通过服务器进行认证。除了用户/密码认证外,OpenSSH 还支持使用非对称加密(RSA 或类似)密钥进行认证。配置了 SSH 密钥后,客户端可以无需手动干预连接到服务器。这简化了复制多个文件或运行远程任务的脚本编写。

在这个配方中,我们将准备一套公钥和私钥,并在构建过程中在所有虚拟机和主机机器上使用这些密钥。

准备工作

对于这个配方,我们需要一个运行着 OpenSSH 服务器和客户端的类 Unix 操作系统。例如,可以使用 Ubuntu 12.04 操作系统。

如何操作...

以下步骤将帮助我们安装 VirtualBox:

  1. 为主机和客户机生成客户端 RSA 密钥对:

    ssh-keygen -q -P "" -t rsa -f vmhost__id_rsa
    ssh-keygen -q -P "" -t rsa -f vmguest__id_rsa
    
    
  2. 为主机和客户机生成服务器 RSA 密钥对:

    ssh-keygen -q -P "" -t rsa -f vmhost__ssh_host_rsa_key
    ssh-keygen -q -P "" -t rsa -f vmguest__ssh_host_rsa_key
    
    
  3. 在主机机器上创建一个新用户,该用户将用于管理构建,并以此用户登录:

    sudo adduser packt
    
    
  4. 运行 VirtualBox,并按照上一配方“安装 VirtualBox”中的说明配置网络,使用192.168.42.2的主机 IP 地址和192.168.42.1的客户机 IP 地址。

  5. 在虚拟机机器上创建一个具有相同名称的用户,并以此用户登录:

    sudo adduser packt
    
    
  6. 检查ping命令是否可以从主机成功连接到虚拟机,并从虚拟机返回到主机:

    ping 192.168.42.1
     PING 192.168.42.2 (192.168.42.2) 56(84) bytes of data.
     64 bytes from 192.168.42.2: icmp_req=1 ttl=64 time=0.181 ms
     ...
    
    
  7. 在主机机器上,设置客户端密钥:

    cp vmhost__id_rsa.pub ~/.ssh/id_rsa.pub
    cp vmhost__id_rsa ~/.ssh/id_rsa
    chmod 600 ~/.ssh/id_rsa
    
    
  8. 在虚拟机机器上设置服务器密钥:

    sudo rm -rf /etc/ssh/ssh_host_*
    sudo cp vmguest__ssh_host_rsa_key.pub /etc/ssh/ssh_host_rsa_key.pub
    sudo cp vmguest__ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key
    sudo chmod 600 /etc/ssh/ssh_host_rsa_key
    
    
  9. 在虚拟机机器上设置主机客户端公钥:

    cp vmhost__id_rsa.pub ~/.ssh/authorized_keys2
    
    
  10. 在主机机器上,尝试连接到虚拟机并确认新的虚拟机服务器密钥:

    ssh packt@192.168.42.1
    
    
  11. 在主机机器上,保存获取的虚拟机服务器密钥指纹:

    cp ~/.ssh/kno
    wn_hosts vmhost__known_hosts
    
    
  12. 在主机机器上,检查我们是否现在可以从主机连接到虚拟机而无需任何密码或额外确认:

    ssh packt@192.168.42.1
    
    
  13. 重复步骤 7 到 11,交换主机和虚拟机两端以设置从虚拟机到主机的连接。

  14. 保存以下密钥以供构建过程中使用:

    • vmhost__id_rsa: 这是主机客户端私钥

    • vmhost__id_rsa.pub: 这是主机客户端公钥

    • vmhost__ssh_host_rsa_key: 这是主机服务器的私钥

    • vmhost__ssh_host_rsa_key.pub: 这是主机服务器的公钥

    • vmhost__known_hosts: 这是主机上要使用的虚拟机服务器密钥指纹

    • vmguest__id_rsa: 这是虚拟机客户端私钥

    • vmguest__id_rsa.pub: 这是虚拟机客户端私钥

    • vmguest__ssh_host_rsa_key: 这是虚拟机服务器的私钥

    • vmguest__ssh_host_rsa_key.pub: 这是虚拟机服务器的公钥

    • vmguest__known_hosts: 这是虚拟机上要使用的主机服务器密钥指纹

工作原理...

ssh-keygen命令生成一对非对称加密(在我们的例子中,RSA)密钥。

SSH 支持基于密钥的无密码身份验证。我们准备了一套密钥,可以加载到主机和虚拟机端(对于所有虚拟机),以允许主机到虚拟机和虚拟机到主机的无缝连接。因此,现在我们可以在主机机器上调用一个脚本,该脚本将连接到(或发送文件)到虚拟机,并能够从相同的虚拟机会话连接回主机。

所有密钥都是故意使用空密码生成的,以便无需手动输入密码即可建立连接。

更多内容...

SSH 连接是安全的,如果你想要使用远程机器而不是本地虚拟机进行构建,这可能会很有用。如果不需要安全,则可以使用其他协议。它们不需要身份验证或密钥设置,例如,一些支持命令和发送文件的 HTTP 自定义协议。

可以使用 DSA 或 ECDSA 密钥代替 RSA 密钥。

可以使用像expect这样的 shell 自动化工具设置带密码的自动连接,而不是客户端密钥。

参见

准备 Linux 虚拟机

许多基于 Linux 的操作系统都支持使用 VirtualBox 进行虚拟化,并且它们通常在主要软件包仓库中预安装或提供 OpenSSH 客户端和服务器。

在这个菜谱中,我们将设置一个 Ubuntu Linux 虚拟机,它可以用于自动化 OpenJDK 构建。

准备工作

对于这个菜谱,我们需要安装了 VirtualBox 并配置了虚拟网络接口的 Ubuntu 12.04 amd64 操作系统。

如何做...

以下程序将帮助我们准备 Linux 虚拟机:

  1. 按照菜谱 准备 SSH 密钥 中所述准备 SSH 密钥。

  2. 从 Ubuntu 网站下载 Ubuntu 12.04 服务器 amd64 镜像([www.ubuntu.com/](http://www.ubuntu.com/))。

  3. 在 VirtualBox 中,使用 IDE 存储控制器和其他设置的默认值创建一个虚拟机实例。

  4. 在虚拟机上安装 Ubuntu,按照菜谱 安装 VirtualBox 中所述设置网络,并启动虚拟机。

  5. 在主机机器上创建一个具有相同名称的用户,并在此用户下登录:

    sudo adduser packt
    
    
  6. 设置客户端密钥:

    cp vmguest__id_rsa.pub ~/.ssh/id_rsa.pub
    cp vmguest__id_rsa ~/.ssh/id_rsa
    chmod 600 ~/.ssh/id_rsa
    
    
  7. 设置服务器密钥:

    sudo rm -rf /etc/ssh/ssh_host_*
    sudo cp vmguest__ssh_host_rsa_key.pub /etc/ssh/ssh_host_rsa_key.pub
    sudo cp vmguest__ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key
    sudo chmod 600 /etc/ssh/ssh_host_rsa_key
    
    
  8. 设置主机客户端公钥:

    cp vmhost__id_rsa.pub ~/.ssh/authorized_keys2
    
    
  9. 设置主机密钥指纹:

    cp vmguest__known_hosts ~/.ssh/known_hosts
    
    
  10. 检查从主机到客户机和返回的连接是否无缝工作:

    ssh packt@192.168.42.1
    
    
  11. 使用来自 第四章 的 Building OpenJDK 8 Ubuntu Linux 12.04 LTS 菜谱完成 OpenJDK 的手动构建。

它是如何工作的...

在这个菜谱中,我们创建了一个带有 Ubuntu Linux 的虚拟机实例,并配置了 SSH 密钥以实现到它的无缝自动化连接。

在此 VM 上进行了手动构建,以确保所有环境都正确。

还有更多...

可以使用其他基于 Linux 的操作系统代替 Ubuntu 12.04。可以使用其他协议/工具代替 OpenSSH 在主机和客户机之间进行交互。

参见

  • 来自 第四章 的 Building OpenJDK 8 Ubuntu Linux 12.04 LTS 菜谱,Building OpenJDK 8

  • 安装 VirtualBox 菜谱

  • 准备 SSH 密钥 菜谱

  • Oracle VirtualBox 用户手册 www.virtualbox.org/manual/UserManual.html

准备带有 Mac OS X 的 VirtualBox 机器

Mac OS X 操作系统的现代版本支持在虚拟化环境中使用 VirtualBox 运行。准备 Mac OS X 镜像以进行虚拟化的说明可能因 Mac OS X 版本和主机操作系统而大相径庭。确切说明超出了本书的范围,可以在互联网上找到。

请注意,在非 Mac 主机操作系统上运行客户机 Mac OS X 可能违反您与 Apple Inc. 的最终用户许可协议,最好咨询您的律师。

Mac OS X 预装了 OpenSSH 客户端和服务器。

准备工作

对于这个菜谱,我们需要一个可用于 VirtualBox-VDI 的已准备好的 Mac OS X 镜像(版本 10.7 或更高)。

如何操作...

以下步骤将帮助我们准备 Mac OS X 虚拟机:

  1. 按照本章中 准备 SSH 密钥 菜谱中的说明准备 SSH 密钥。

  2. 在 VirtualBox 中,创建一个至少拥有 2048 RAM、PIIX3 芯片组、禁用 UEFI、单 CPU 和 IDE 存储控制器的虚拟机实例。

  3. 按照本章中 安装 VirtualBox 菜谱中的说明设置网络,并启动虚拟机。

  4. 在主机机器上创建一个具有相同名称的用户,并以此用户身份登录。

  5. 设置客户端密钥:

    cp vmguest__id_rsa.pub ~/.ssh/id_rsa.pub
    cp vmguest__id_rsa ~/.ssh/id_rsa
    chmod 600 ~/.ssh/id_rsa
    
    
  6. 设置服务器密钥:

    sudo rm -rf /etc/ssh_host_*
    sudo cp vmguest__ssh_host_rsa_key.pub /etc/ssh_host_rsa_key.pub
    sudo cp vmguest__ssh_host_rsa_key /etc/ssh_host_rsa_key
    sudo chmod 600 /etc/ssh_host_rsa_key
    
    
  7. 设置主机客户端公钥:

    cp vmhost__id_rsa.pub ~/.ssh/authorized_keys2
    
    
  8. 设置主机密钥指纹:

    cp vmguest__known_hosts ~/.ssh/known_hosts
    
    
  9. 检查从主机到客户机和返回的连接是否无缝工作:

    ssh packt@192.168.42.1
    
    
  10. 使用来自第四章 在 Mac OS X 上构建 OpenJDK 8构建 OpenJDK 8 on Mac OS X 菜谱完成 OpenJDK 的手动构建。

它是如何工作的...

在这个菜谱中,我们创建了一个带有 Mac OS X 的虚拟机实例,并配置了 SSH 密钥以实现无缝的自动化连接。

手动构建是在这个虚拟机上完成的,以确保环境设置正确。

更多内容...

在某些 Mac OS X 版本中,预装的 OpenSSH 可能不支持 ECDSA SSH 密钥。尽管如此,我们仍然可以使用 RSA SSH 密钥完成这个菜谱。但是,如果你想使用 ECDSA 密钥,你可以相对容易地使用 Homebrew 打包系统和其系统副本仓库 homebrew/dupes 更新 OpenSSH 安装。

可以使用其他协议/工具在主机和客户机之间进行交互,而不是 OpenSSH。

相关链接

  • 来自第四章 在 Mac OS X 上构建 OpenJDK 8构建 OpenJDK 8 on Mac OS X 菜谱 Chapter 4,构建 OpenJDK 8

  • 安装 VirtualBox 菜谱

  • 准备 SSH 密钥 菜谱

  • Oracle VirtualBox 用户手册,www.virtualbox.org/manual/UserManual.html

准备带有 Windows 的 VirtualBox 机器

流行的虚拟化工具对 Windows 的虚拟化支持非常好。与 Mac OS X 相比,Windows 的 VirtualBox 设置可能更容易。然而,在 Unix-like 操作系统上,SSH 协议比在 Windows 上更受欢迎,Windows 上的 SSH 服务器设置可能更复杂。

在这个菜谱中,你将学习如何设置 Windows 虚拟机以进行自动化构建。关于在 Windows 上配置免费 SSH 服务器的一套深入指导将构成菜谱的重要部分。

准备工作

对于这个菜谱,我们需要一个 Windows 7 虚拟机 VirtualBox 镜像。

如何操作...

以下步骤将帮助我们准备 Windows 虚拟机:

  1. 按照本章中 准备 SSH 密钥 菜谱中的说明准备 SSH 密钥。

  2. 下载 Copssh SSH 服务器实现版本 3.1.4。不幸的是,它已被作者从公共下载中移除,但仍然可以在互联网上找到,以下是一些文件详细信息:

    Copssh_3.1.4_Installer.exe
    size: 5885261 bytes
    sha1: faedb8ebf88285d7fe3e141bf5253cfa70f94819
    
    
  3. 使用默认安装参数将 Copssh 安装到任何 Windows 实例中,并将安装的文件复制到某处以供以后使用。

  4. 在 VirtualBox 中,使用 IDE 存储控制器和其他设置的默认值创建虚拟机实例。

  5. 按照本章中安装 VirtualBox配方中的说明设置网络,并启动虚拟机。

  6. 在主机机器上创建具有相同名称的用户,并在此用户下登录(我们将使用名称packt)。

  7. 从 Microsoft 网站下载 Windows Server 2003 资源工具包,并从中提取ntrightsinstsrvsrvany实用程序。

  8. 将提取的 Copssh 文件复制到c:\ssh目录。

  9. 使用以下脚本设置 SSH 服务的用户和权限:

    net user sshd sshd /ADD
    net user SvcCOPSSH SvcCOPSSH /ADD
    net localgroup Administrators SvcCOPSS
    H /add
    ntrights +r SeTcbPrivilege -u SvcCOPSSH
    ntrights +r SeIncreaseQuotaPrivilege -u SvcCOPSSH
    ntrights +r SeCreateTokenPrivilege -u SvcCOPSSH
    ntrights +r SeServiceLogonRight -u SvcCOPSSH
    ntrights +r SeAssignPrimaryTokenPrivilege -u SvcCOPSSH
    ntrights +r SeDenyInteractiveLogonRight -u SvcCOPSSH
    ntrights +r SeDenyNetworkLogonRight -u SvcCOPSSH
    
    
  10. 生成内部 Copssh 登录和密码信息,并将 Copssh 注册为 Windows 服务:

    c:\copssh\bin\mkpasswd -l > c:\obf\copssh\etc\passwd
    c:\copssh\bin\cygrunsrv.exe --install OpenSSHServer --args "-D" --path /bin/sshd
     --env "CYGWIN=binmode ntsec tty" -u SvcCOPSSH -w SvcCOPSSH
    
    
  11. 安装 Cygwin 工具并运行 bash shell。

  12. 设置客户端密钥:

    cp vmguest__id_rsa.pub /cygdrive/c/ssh/home/packt/.ssh/id_rsa.pub
    cp vmguest__id_rsa /cygdrive/c/ssh/home/packt/.ssh/id_rsa
    chmod 600 /cygdrive/c/ssh/home/packt/.ssh/id_rsa
    
    
  13. 设置服务器密钥:

    rm -rf /cygdrive/c/ssh/etc/ssh_host_*
    cp vmguest__ssh_host_rsa_key.pub /cygdrive/c/ssh/etc/ssh_host_rsa_key.pub
    cp vmguest__ssh_host_rsa_key /cygdrive/c/ssh/etc/ssh_host_rsa_key
    chmod 600 /cygdrive/c/ssh/etc/ssh_host_rsa_key
    
    
  14. 设置主机客户端公钥:

    cp vmhost__id_rsa.pub /cygdrive/c/ssh/home/packt/.ssh/authorized_keys2
    
    
  15. 设置主机密钥指纹:

    cp vmguest__known_hosts /cygdrive/c/ssh/home/packt/.ssh/known_hosts
    
    
  16. 检查从主机到虚拟机以及返回的连接是否无缝工作:

    ssh packt@192.168.42.1
    
    
  17. 使用instsrvsrvany实用程序注册将用于启动构建过程的 Windows 服务:

    instsrv.exe packt_build c:\path\to\srvany.exe
    reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\packt_build\Parameters" /v Application /t reg_sz /d "C:\packt\build.bat"
    
    
  18. 配置服务以避免在操作系统启动时自动启动:

    sc config obf_build start= demand
    
    
  19. 使用第四章中在 Windows 7 SP1 上构建 OpenJDK 8配方完成 OpenJDK 的手动构建。

它是如何工作的...

在此配方中,我们创建了一个带有 Windows 的虚拟机实例,并配置了 SSH 服务器以启用到它的无缝自动化连接。

Copssh SSH 服务器版本 3.1.4 的二进制文件由作者在 GNU 通用公共许可证版本 3 的条款下作为免费软件发布。这意味着我们可以为任何目的发布或使用未经更改的二进制文件,而无需额外的许可限制。

Copssh 使用 Cygwin 环境和 OpenSSH 服务器作为底层。它还提供了与 Windows 用户权限系统的集成。

授予 SvcCOPSSH 用户的授权角色是支持 SSH 密钥认证所必需的。

我们使用 Cygwin 设置密钥文件以支持 Cygwin 文件权限的正确设置。

Copssh 使用了 Cygwin 环境的旧版本,我们需要额外的完整 Cygwin 安装来支持 OpenJDK 构建过程。同一台机器上运行的不同版本的 Cygwin 可能会相互干扰,导致错误。尽管在重用此类设置期间,我从未观察到任何问题,但最好记住这一点,以防出现神秘的 Cygwin/Copssh 错误/崩溃。

Copssh 内部使用两个额外的 Windows 用户(sshd 和 SvcCOPSSH)用于 SSH 服务器。

使用ntrighs实用程序为 SvcCOPSSH 用户分配了额外的角色。此实用程序在新版本的 Windows 中未得到官方支持,但应该仍然可以正常工作。

对于自动化构建,需要 Windows 服务注册来通过 SSH 连接启动实际的构建过程。为了设置适当的环境,Windows 上的构建过程应该从 cmd.exe shell(通常运行批处理文件)启动。不能直接从在 Cygwin 环境中的客户机 Windows 机器内的 SSH 会话启动。instsrvntrighs 工具允许我们创建一个 Windows 服务,该服务将在注册表中预先配置的路径上运行批处理文件(进而启动实际的构建过程)。这个 packt_build 服务可以通过 net start 命令从 SSH 会话启动,从而有效地启动构建过程。

在这个虚拟机上进行的手动构建是为了确保环境设置正确。

更多...

理论上可以使用其他 SSH 服务器,尽管我不了解其他适用于 Windows 的免费(如“言论自由”)SSH 服务器实现,它支持密钥认证。

可以使用其他协议/工具来代替 OpenSSH 在主机和客户机之间进行交互。

参见

  • 来自第四章 Building OpenJDK 8 的 在 Windows 7 SP1 上构建 OpenJDK 8 脚本

  • 来自第二章 Building OpenJDK 6 的 为 Windows 构建 Cygwin 的安装脚本

  • 安装 VirtualBox 脚本

  • 准备 SSH 密钥 脚本

  • Oracle VirtualBox 用户手册位于 www.virtualbox.org/manual/UserManual.html

  • Copssh 网站 www.itefix.net/copssh

自动化构建

这个脚本将本章中所有之前的脚本合并在一起。准备好的虚拟机镜像和带有密钥认证的 SSH 将允许我们使用简单的 bash 脚本在完全自动化的模式下构建 OpenJDK,而无需额外的工具。

准备工作

对于这个脚本,我们需要一个运行 Linux 或 Mac OS 的主机机器。

如何做到这一点...

以下步骤将帮助我们准备 Windows 虚拟机:

  1. 按照本章中 准备 SSH 密钥 脚本所述准备 SSH 密钥。

  2. 按照本章中 安装 VirtualBox 脚本所述设置 VirtualBox 安装及其网络设置。

  3. 按照本章之前 recipes 中所述准备虚拟机镜像。

  4. 对于每个 VM 映像,准备一个环境变量列表,这些变量将由构建脚本使用(例如,Windows):

    export VM_ADDRESS=192.168.42.1
    export VM_NAME=jdk7-windows-amd64
    export VM_OSTYPE=Windows7_64
    export VM_MEMORY=1780
    export VM_IOAPIC=on
    export VM_NICTYPE=82545EM
    export VM_MACADDR=auto
    export VM_OBF_DIR=/cygdrive/c/packt
    export VM_START_BUILD="net start obf_build >> build.log 2>&1"
    export VM_SHUTDOWN="shutdown /L /T:00 /C /Y"
    export VM_IDE_CONTROLLER=PIIX4
    
    
  5. 将以下步骤(第 6 步至第 12 步)的片段添加到主构建脚本中。

  6. 使用 VBoxManage 工具创建虚拟机实例:

    SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    VBoxManage createvm --name "$VM_NAME" --register --basefolder "$SCRIPT_DIR"/target >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --ostype "$VM_OSTYPE" >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --memory "$VM_MEMORY" >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --nic1 bridged --bridgeadapter1 tap0 >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --nictype1 "$VM_NICTYPE" >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --macaddress1 "$VM_MACADDR" >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --cpus 1 >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --audio none >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --usb off >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --vrde on
    VBoxManage modifyvm "$VM_NAME" --ioapic "$VM_IOAPIC" >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --mouse usbtablet >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage modifyvm "$VM_NAME" --keyboard usb >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage setextradata global GUI/SuppressMessages remindAboutAutoCapture,remindAboutMouseIntegrationOn,showRuntimeError.warning.HostAudioNotResponding,remindAboutGoingSeamless,remindAboutInputCapture,remindAboutGoingFullscreen,remindAboutMouseIntegrationOff,confirmGoingSeamless,confirmInputCapture,remindAboutPausedVMInput,confirmVMReset,confirmGoingFullscreen,remindAboutWrongColorDepth >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage storagectl "$VM_NAME" --name "IDE" --add ide >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage internalcommands sethduuid "$SCRIPT_DIR"/target/$VM_NAME.vdi >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage storageattach "$VM_NAME" --storagectl "IDE" --port 0 --device 0 --type hdd --medium "$SCRIPT_DIR"/target/"$VM_NAME".vdi >> "$SCRIPT_DIR"/build.log 2>&1
    VBoxManage storagectl "$VM_NAME" --name "IDE" --controller "$VM_IDE_CONTROLLER"
    
    
  7. 启动虚拟机实例:

    VBoxManage startvm "$VM_NAME" --type headless >> "$SCRIPT_DIR"/build.log 2>&1
    ssh "$VM_ADDRESS" "ls" > /dev/null 2>&1
    while [ $? -ne 0 ]; do
    echo "Waiting for VM ..."
     sleep 10
     ssh "$VM_ADDRESS" "ls" > /dev/null 2>&1
    done
    echo "VM started"
    
    
  8. 启用通过 SSH 将远程日志记录回主机机器:

    ssh "$VM_ADDRESS" "cd "$VM_OBF_DIR" && echo 'Starting build' > build.log"
    nohup ssh "$VM_ADDRESS" "tail -f "$VM_OBF_DIR"/build.log | ssh 192.168.42.2 'cat >> "$SCRIPT_DIR"/build.log'" >> "$SCRIPT_DIR"/build.log 2>&1 &
    LOGGER_PID="$!"
    
    
  9. 将 OpenJDK 源代码复制到构建 VM 并开始构建:

    scp "$SCRIPT_DIR"/openjdk.zip "$VM_ADDRESS":"$VM_OBF_DIR"
    ssh "$VM_ADDRESS" "cd "$VM_OBF_DIR" && unzip -q openjdk.zip >> build.log 2>&1"
    ssh "$VM_ADDRESS" "cd "$VM_OBF_DIR" && "$VM_START_BUILD""
    
    
  10. 定期轮询构建机器,寻找在构建完成后应该创建的 build_finished.flag 文件:

    ssh "$VM_ADDRESS" "if [ ! -f "$VM_OBF_DIR"/build_finished.flag ]; then exit 1; else exit 0; fi" > /dev/null 2>&1
    while [ $? -ne 0 ]; do
     echo "Waiting for build ..."
     sleep 300
    ssh "$VM_ADDRESS" "if [ ! -f "$VM_OBF
    _DIR"/build_finished.flag ]; then exit 1; else exit 0; fi" > /dev/null 2>&1
    done
    
    
  11. 复制构建结果,停止记录器,关闭虚拟机实例,并在 VirtualBox 中注销它:

    scp -r "$VM_ADDRESS":"$VM_OBF_DIR"/dist/* "$SCRIPT_DIR"/dist
    kill -9 $LOGGER_PID >> "$SCRIPT_DIR"/build.log 2>&1
    ssh "$VM_ADDRESS" "$VM_SHUTDOWN" >> "$SCRIPT_DIR"/build.log 2>&1 || true
    sleep 15
    VBoxManage controlvm "$VM_NAME" poweroff > /dev/null 2>&1 || true
    VBoxManage unregistervm "$VM_NAME" >> "$SCRIPT_DIR"/build.log
    
    
  12. 要使用所选的虚拟机镜像启动构建,请使用以下命令:

    . windows_amd64.env # please not the dot before the command
    nohup build.sh >> build.log 2>&1 &
    echo "$!" > .pid
    tail -F "$SCRIPT_DIR"/build.log
    
    
  13. 构建完成后,OpenJDK 二进制文件将被复制回主机机器。

它是如何工作的...

我们使用低级 VBoxManage 工具来操作虚拟机实例,以便更好地控制此过程。

为了确保虚拟机实例实际上启动了,我们通过 SSH 定期检查它,并在第一次成功连接后停止检查。

对于远程记录,我们在构建机器上运行tail -f进程,该进程立即通过 SSH 将输出发送回主机机器。我们使用nohup工具在主机机器的背景中使用连接启动此进程,并将主机进程pid写入.pid文件以在构建后终止进程。

我们使用scp将源代码复制到构建机器,并使用 SSH 命令解压缩源代码和启动构建。

构建开始后,我们通过 SSH 定期检查构建机器,以查找构建脚本应在构建机器上创建的build_finished.flag文件。

构建完成后,我们将 OpenJDK 二进制文件复制回主机机器,并在注销之前优雅地关闭虚拟机实例。

每个虚拟机镜像的.env文件中列出了不同的虚拟机配置和环境选项。我们使用“点优先”语法从env文件导入变量到当前 shell。这允许我们为所有虚拟机镜像使用通用的构建脚本。

更多内容...

这个脚本可以被视为构建自动化的基本示例。不同的命令、工具和协议可以用来达到相同的目标。

build_finished.flag 文件(包含自定义内容)也可以在出现错误后提前结束构建。

参见

  • 安装 VirtualBox 脚本

  • 准备 SSH 密钥 脚本

  • 准备带有 Linux 的 VirtualBox 机器 脚本

  • 准备带有 Mac OS X 的 VirtualBox 机器 脚本

  • 准备带有 Windows 的 VirtualBox 机器 脚本

  • 第四章, 构建 OpenJDK 8

  • Oracle VirtualBox 用户手册,www.virtualbox.org/manual/UserManual.html

构建跨平台安装程序

当云服务成为安装桌面软件的普遍方式时,经典的 GUI 安装程序几乎变得过时。云包仓库或商店可以安装和首先更新桌面应用程序更加方便。

同时,GUI 安装程序在许多免费和商业应用程序中仍然被广泛使用。特别是对于跨平台应用程序,尽管在那些平台上不是完全本地的,GUI 安装程序应该在所有支持的平台上显示相同的行为。一些应用程序在安装时需要复杂的环境更改,例如,将自己注册为 Windows 服务或设置环境变量。

在这个配方中,我们将准备一个适用于所有支持平台(Windows、Linux 和 Mac OS X)的 OpenJDK 跨平台安装程序。我们将使用一个流行的开源安装工具IzPack,它是用 Java 编写的。

准备工作

对于这个配方,我们将需要 OpenJDK 的二进制文件(用于包装到安装程序中)。

如何操作...

以下步骤将帮助我们准备安装程序:

  1. 从 IzPack 网站(izpack.org/)下载 IzPack 编译器版本 4.3.5 并安装它。

  2. 从 Izpack 网站的文档部分下载示例安装配置文件。

  3. jre目录从 OpenJDK 镜像中上移一级,紧邻openjdk目录。

  4. 使用以下配置片段将jre目录添加到安装程序配置中,作为一个“松散”包:

    <pack name="OpenJDK RE" required="yes" loose="true">
        <description>OpenJDK Runtime Environment</description>
        <file src="img/jre" targetdir="$INSTALL_PATH"/>
    </pack>
    
  5. openjdk目录(现在不包含 JRE)作为一个普通包添加:

    <pack name="OpenJDK DK" required="no">
        <description>OpenJDK Development Kit</description>
        <fileset dir="openjdk" targetdir="$INSTALL_PATH"/>
        <file src="img/uninstall" targetdir="$INSTALL_PATH"/>
    </pack>
    
  6. 根据您的喜好调整标签、GUI 表单、区域设置和图标。

  7. 运行 IzPack 编译器:

    ./izcomp/bin/compile ./config.xml -h ./izcomp -o installer.jar
    
    
  8. 将生成的install.jarjre目录放入openjdk-installer目录中。

  9. 将 bash/batch 脚本添加到openjdk-installer目录中,这将允许我们使用jre目录的相对路径来运行安装程序:

    ./jre/bin/java -jar install.jar
    
    
  10. 压缩openjdk-installer目录——它现在包含 OpenJDK 安装程序。

它是如何工作的...

我们安装程序的主要特点是安装程序本身运行在它将要安装的相同版本的 Java 上。JRE 包中的loose="true"配置指示安装程序在主安装.jar文件之外的相对路径上查找此包,而不复制jre目录的内容。

更多内容...

IzPack 安装程序支持许多配置选项,我们在此配方中仅突出显示了基本选项。除了 GUI 和安装表单的自定义之外,一个特别有用的功能,特别是对于 OpenJDK,是在安装时运行脚本。我们可以准备脚本以调整环境,并使用可执行配置元素将它们添加到相应的包中。在简单的类 Unix 操作系统上,这样的脚本可以简单地将PATH变量更改追加到~/.bashrc~/.bash_profile文件中。在 Windows 上,可以使用pathmansetxreg等实用程序来调整环境变量或 Windows 注册表。

在安装时运行脚本,而不是直接从 Java 代码中扩展 IzPack 本身(添加新表单等)并直接执行环境注册。

参考信息

  • 第二章, 构建 OpenJDK 6

  • 第三章, 构建 OpenJDK 7

  • 第四章, 构建 OpenJDK 8

  • IzPack 安装程序网站位于izpack.org/

posted @ 2025-09-11 09:45  绝不原创的飞龙  阅读(2)  评论(0)    收藏  举报