标签 Java 下的文章

通过理解和使用 JVM 以及 JVM 参数,开发人员和最终用户都可以诊断故障并且提高 Java 应用程序的性能。

当你在编写源代码时,你是在编写人类可以阅读的代码。在将代码编译成机器语言之前,计算机无法执行它。机器语言是一个通用术语,指的是特定机器所需的任意数量的语言。通常,如果你在 Linux 上编译代码,它只能 Linux 上运行;如果你在 Windows 上编译代码,它就只在 Windows 上运行。但是,Java 是不同的,它并不以真实的机器为目标,而是面向 Java 虚拟机 Java Virtual Machine (JVM)。因此,它可以在任何机器上运行。

Java 源代码被编译成 字节码 bytecode ,然后由安装在计算机上的 JVM 运行。JVM 是一个执行引擎,但我们通常不会直接与它交互。它在后台静默运行,替我们处理 Java 字节码。大多数人不需要考虑,甚至也不需要知道 JVM。但是,了解它的工作原理是对我们来说是非常有用的,因为这会有助于我们调试和优化 Java 代码。例如:

  • 在生产环境中,你发现已经部署的应用程序可能需要提升性能。
  • 如果你写的应用程序出错了,开发人员和最终用户都可以选择对问题进行调试。
  • 如果你想了解关于 JDK(即 Java 开发工具包 Java Development Kit ,用于开发/运行 Java 应用程序)的详细信息,你可以通过查询 JVM 来获取。

本文介绍了一些基础的 JVM 参数,希望在这些场景中可以提供帮助。

JVM 参数

(图源:Jayashree Huttanagoudar,CC BY-SA 4.0)

JVM、JDK 和 JRE 有什么不同?

Java 有许多 J 开头的缩略词,包括 JVM、JDK 和 JRE。

  • Java 开发工具包 Java Development Kit (JDK)可供需要在代码中使用开发库的程序员使用。
  • Java 运行时环境 Java Runtime Environment (JRE)可供想运行 Java 应用程序的人使用。
  • Java 虚拟机 Java Virtual Machine (JVM)是运行 Java 字节码的组件。

JDK 同时包含 JRE 和 JVM,但有些 Java 发行版提供了包含 JRE(包括 JVM)的替代下载。

JDK

(图源:Jayashree Huttanagoudar,CC BY-SA 4.0)

Java 是开源的,因此,许多不同的公司都会构建和发行他们自己的 JDK 发行版。你可以在系统上安装多个 JDK,这会对你参与或者运行不同的 Java 项目时很有帮助,因为其中一些项目可能使用旧版本的 JDK。

你可以使用 alternatives 命令,来查看 Linux 系统上的 JDK 列表:

$ alternatives --config java
There are 2 programs that provide java.
Selection Command
-----------------------------------------------
*+ 1 java-11-openjdk.x86_64 (/usr/lib/jvm/java-11-openjdk-11.0.13.0.8-2.fc35.x86_64/bin/java)
2 java-1.8.0-openjdk.x86_64 (/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-2.fc35.x86_64/jre/bin/java)

Enter to keep the current selection[+], or type selection number:

如果想要在可用的 JDK 之间进行切换,请再次执行该命令:

$ sudo alternatives --config java

或者可以使用 SDKMan,它可以下载、更新和管理系统中的所有 JDK。

什么是 JVM 调优?

JVM 调优指的是,通过调整 JVM 参数,来提高 Java 应用程序性能的过程,它还有助于诊断应用程序的故障。

通常情况下,在调试之前需要考虑以下几点:

  • 成本:有时改进运行代码的硬件可以提高应用程序的性能。这可能看起来像是在“作弊”,但请考虑你愿意花多少时间调整 JVM 参数。有时应用程序需要更多的内存来执行所需的功能,而这点是任何软件技术都无法改变的。
  • 期望结果:长期来看,稳定性比性能更重要。如果你的调优对稳定性产生了影响,那么谨慎地选择你的调优参数可能会更好。
  • 底层问题:有时,问题可能是主机操作系统的底层问题。那么,在调整 JVM 之前,请确保 JVM 平台按预期工作。
  • 内存泄漏:如果你在使用垃圾回收(GC)调优参数,那么,应用程序代码中很可能会存在需要修复的内存泄漏。

参数类型

JVM 参数可以分为以下三类:标准参数、非标准参数和高级选项。

标准参数

所有的 JVM 实现都支持标准参数,在终端执行 java 命令来查看标准参数列表:

$ java
Usage: java [options] <mainclass> [args...]
        (to execute a class)
   or  java [options] -jar <jarfile> [args...]
        (to execute a jar file)

 where options include:

        -cp <class search path of directories and zip/jar files>
        -classpath <class search path of directories and zip/jar files>
        --class-path <class search path of directories and zip/jar files>
                A : separated list of directories, JAR archives,
                and ZIP archives to search for class files.
        --enable-preview
                allow classes to depend on preview features of this release

To specify an argument for a long option, you can use --<name>=<value> or
--<name> <value>.

这些是所有 JVM 都会包含的标准参数,你可以像使用任何 命令行选项 一样安全地使用它们。例如,要验证配置的命令选项,创建 VM 并加载主类而不执行主类,请使用:

$ java --dry-run <classfile>

非标准参数

非标准选项以 -X 开头。这些是通用的,并且特定于 JVM 的特定实现。要列出这些参数,请输入:

$ java -X
-Xbatch disable background compilation
-Xbootclasspath/a:<directories and zip/jar files separated by :>
append to end of bootstrap class path
-Xinternalversion
displays more detailed JVM version information than the
-version option
-Xloggc:<file> log GC status to a file with time stamps
[...]

在这些参数可能会不经通知就发生变化。而且,并非所有 JVM 实现都支持这些参数。

微软构建的 JVM 可能与 RedHat 构建的 JVM 有不同的参数,诸如此类。

要获取详细的 JVM 版本信息,请使用如下命令:

$ java -Xinternalversion --version
OpenJDK 64-Bit Server VM (11.0.13+8) for linux-amd64 JRE (11.0.13+8), built on Nov 8 2021 00:00:00 by "mockbuild" with gcc 11.2.1 20210728 (Red Hat 11.2.1-1)

要获取这些属性设置,请使用:

$ java -XshowSettings:properties --version

高级选项

这些参数不是随意使用的,而是用于调整 Hotspot VM 的特定区域。这些参数可能会发生变化,并且不能保证得到所有 JVM 实现的支持。

这些参数以 -XX 开头。如需列出参数列表,使用如下命令:

$ java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version

例如,需要跟踪类的加载,那么使用下面的命令:

$ java -XX:+TraceClassLoading Hello

在 Hello.java 中:

public class Hello {
  public static void main(String[] args) {
    System.out.println("Inside Hello World!");
  }
}

另一个可能会面临的问题是 OOM( 内存超出 Out Of Memory )错误,它发生的时候可能没有太多的调试信息。为了解决这个问题,使用调试参数 -XX:+HeapDumpOnOutOfMemoryError,它可以创建一个带有调试信息的 .hprof 文件。

// TestClass.java
import java.util.ArrayList;
import java.util.List;

public class TestClass {
  public static void main(String[] args) {
    List<Object> list = new ArrayList<Object>();
    for (int i = 0; i < 1000; i++) {
      list.add(new char[1000000]);
    }
  }
}
$ Javac TestClass.java
$ java -XX:+HeapDumpOnOutOfMemoryError -Xms10m -Xmx1g TestClass
java.lang.OutOfMemoryError: java heap space
Dumping heap to java_pid444496.hprof ...
Heap dump file created [1018925828 bytes in 1.442 secs]
Exception in thread "main" java.lang.OutOfMemoryError: java heap space
at TestClass.main(TestClass.Java:8)

有一些工具 可以查看这个 .hprof 文件以了解问题所在。

总结

通过了解和使用 JVM 以及 JVM 参数,开发人员和终端用户都可以诊断故障并提高 Java 应用程序的性能。下次使用 Java 时,请花点时间看看有哪些参数可以用吧!

(题图由 Seksak Kerdkanno 在 Pixabay 上发布 )


via: https://opensource.com/article/22/4/jvm-parameters-java-developers

作者:Jayashree Huttanagoudar 选题:lkxed 译者:Veryzzj 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

首先在 Java 中创建初始化一个整数列表,然后在 Groovy 中做同样的事。

 title=

我非常喜欢 Groovy 编程语言。我喜欢它是因为我喜欢 Java,尽管 Java 有时候感觉很笨拙。正因为我是那么喜欢 Java,其他运行在 JVM 上语言都不能吸引我。比方说 Kotlin、Scala 还有 Clojure 语言,它们感觉上就和 Java 不一样,因为它们对于什么是好的编程语言的理解不同。Groovy 和它们都不一样,在我看来,Groovy 是一个完美的选项,特别是对于一部分程序员来说,他们喜欢 Java,但是又需要一个更灵活、更紧凑,并且有时候更直接的语言。

列表 List 这种数据结构是一个很好的例子,它可以容纳一个无序的列表,列表中的元素可以是数字、字符串或者对象,程序员可以用某种方式高效地遍历这些元素,特别是对于编写和维护脚本的人来说,“高效”的关键就是要有简洁清晰的表达,而不需要一大堆“仪式”,把代码的意图都变模糊了。

安装 Java 和 Groovy

Groovy 是基于 Java 的,因此需要同时安装一个 Java 才行。你的 Linux 发行版的仓库中可能有最近的比较好的 Java 版本。或者,你也可以在根据 这些指导 来安装 Groovy。对于 Linux 用户来说,SDKMan 是一个不错的代替选项,你可以使用它来获取多个 Java 和 Groovy 版本,以及许多其他的相关工具。在这篇文章中,我使用的 SDK 发行版是:

  • Java: OpenJDK 11 的 11.0.12-open 版本
  • Groovy: 3.0.8 版本

言归正传

Java 中有很多方法可以实例化并初始化列表,从它最初被引入的时候就有了(我记得是在 Java 1.5 的时候,但请不要引用我的话)。在这些方法里,有两个有趣的方法,它们涉及到了 java.util.Arrays 和 java.util.List 这两个类。

使用 java.util.Arrays 类

java.util.Arrays 类定义了一个 asList() 静态方法,它可以被用来创建一个基于数组的列表,因此大小是不可变的,尽管其中的元素是可以被修改的。下面是它的使用方式:

var a1 = Arrays.asList(1,2,3,4,5,6,7,8,9,10); // immutable list of mutable elements

System.out.println("a1 = " + a1);
System.out.println("a1 is an instance of " + a1.getClass());

// output is
// a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// a1 is an instance of class java.util.Arrays$ArrayList

a1.set(0,0); // succeeds
System.out.println("a1 = " + a1); // output is
// a1 = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]

a1.add(11); // fails producing
// Exception in thread "main" java.lang.UnsupportedOperationException
System.out.println("a1 = " + a1); // not reached

使用 java.util.List 类

java.util.List 类定义了一个 of() 静态方法,它可以被用来创建一个不可变的列表,其中的元素是否可变要取决于它们本身是否支持修改。下面是它的使用方式:

var a2 = List.of(1,2,3,4,5,6,7,8,9,10);

System.out.println("a2 = " + a2);
System.out.println("a2 is an instance of " + a2.getClass());

// output is
// a2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// a2 is an instance of class java.util.ImmutableCollections$ListN

a2.set(0,0); // fails producing
// Exception in thread "main" java.lang.UnsupportedOperationException
System.out.println("a2 = " + a2); // not reached

a2.add(11); // also fails for same reason if above two lines commented out
System.out.println("a2 = " + a2); // not reached

因此,我可以使用 Arrays.asList(),也可以使用 List.of() 方法,前提是如果我想要的是一个大小不能改变、且不关心元素是否可变的列表。

如果我想要初始化一个可变的列表,我更倾向于把这些不可变的列表作为参数传给一个列表构造器,就像下面这样:

var a1 = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5,6,7,8,9,10));

System.out.println("a1 = " + a1);
System.out.println("a1 is an instance of " + a1.getClass());

// output is
// a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// a1 is an instance of class java.util.ArrayList

a1.set(0,0);
System.out.println("a1 = " + a1);

//output is
// a1 = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]

a1.add(11);
System.out.println("a1 = " + a1);

// output is
// a1 = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

注意,这个 Arrays.asList() 方法是用来初始化这个新的 ArrayList<Integer>() 的,也就是说,它为这个传进来的列表创建了一个可变的拷贝。

现在,或许只有我这么想,但是这种方式确实看起来需要理解很多关于 java.util.Arrays 和 java.util.List 类的细节才行,而我只是想要创建并初始化一个数字列表而已(尽管真正使用到的语句并没有太多“仪式”)。下面是真正用到的那行代码,仅供参考:

var a1 = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5,6,7,8,9,10));

Groovy 是怎么做的

下面来看看在 Groovy 中如何实现上述需求:

def a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

println "a1 = $a1"
println "a1 is an instance of ${a1.getClass()}"

// output is
// a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// a1 is an instance of class java.util.ArrayList

a1[0] = 0
println "a1 = $a1"

// output is
// a1 = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]

a1 << 11
println "a1 = $a1"

// output is
// a1 = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

我们一眼就能发现,Groovy 使用了 def 关键字而不是 var 关键字。我还发现了,仅仅是把一系列的类型(在这个例子里是整数)放进括号里,我就得到了一个创建好的列表。此外,这样创建出来的列表完全就是我想要的:一个可变的 ArrayList 实例。

现在,或许再一次只有我这么想,但是上面的代码看起来要简单多得多 —— 不用记住 .of() 和 .asList() 返回的是“ 半不变 semi-mutable ”的结果,也不用为它们做一些补偿。另外一个好处是,我现在可以使用括号和下标来引用列表中的某个特定元素,而不用这个叫 set() 方法。另外,这个跟在列表后面的 << 操作符也很方便,我再也不用调用 add() 方法来添加元素啦。还有,你注意到代码中没有分号了吗?没错,在 Groovy 里,句末的分号并不是必须的。最后,我们来看看字符串插值,只要在字符串里用 $变量 或者 ${表达式} 就可以实现了哦!

在 Groovy 世界中还藏着许多“有待发掘”的东西。上面的列表定义其实是一个动态类型(Groovy 中默认)和 Java 中的静态类型的对比。在上面的 Groovy 代码定义的那一行,变量 a1 的类型是在运行的时候,根据等号右边的表达式的计算结果推断出来的。现在我们都知道,动态语言可以给我们带来强大的功能,有了强大的功能,我们有了很多机会去尝试不同的东西。对于那些不喜欢动态类型的程序员来说,Groovy 也支持静态类型。

Groovy 相关资源

Apache Groovy 网站上有非常多的文档。另一个很棒的 Groovy 资源是 Mr. Haki。学习 Groovy 还有一个很棒的原因,那就是可以接着学习 Grails,后者是一个优秀的、高效率的全栈 Web 框架,基于许多优秀组件构建而成,比如有 Hibernate、Spring Boot 和 Micronaut 等。


via: https://opensource.com/article/22/1/creating-lists-groovy-java

作者:Chris Hermansen 选题:lujun9972 译者:lkxed 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在你使用 Java 编写软件时实现持久化配置。

 title=

当你编写一个应用时,你通常都会希望用户能够定制化他们和应用交互的方式,以及应用与系统进行交互的方式。这种方式通常被称为 “ 偏好 preference ” 或者 “ 设置 setting ”,它们被保存在一个 “偏好文件” 或者 “配置文件” 中,有时也直接简称为 “ 配置 config ”。配置文件可以有很多种格式,包括 INI、JSON、YAML 和 XML。每一种编程语言解析这些格式的方式都不同。本文主要讨论,当你在使用 Java 编程语言 来编写软件时,实现持久化配置的方式。

选择一个格式

编写配置文件是一件相当复杂的事情。我曾经试过把配置项使用逗号分隔保存在一个文本文件里,也试过把配置项保存在非常详细的 YAML 和 XML 中。对于配置文件来说,最重要是要有一致性和规律性,它们使你可以简单快速地编写代码,从配置文件中解析出数据;同时,当用户决定要做出修改时,很方便地保存和更新配置。

目前有 几种流行的配置文件格式。对于大多数常见的配置文件格式,Java 都有对应的 库 library 。在本文中,我将使用 XML 格式。对于一些项目,你可能会选择使用 XML,因为它的一个突出特点是能够为包含的数据提供大量相关的元数据,而在另外一些项目中,你可能会因为 XML 的冗长而不选择它。在 Java 中使用 XML 是非常容易的,因为它默认包含了许多健壮的 XML 库。

XML 基础

讨论 XML 可是一个大话题。我有一本关于 XML 的书,它有超过 700 页的内容。幸运的是,使用 XML 并不需要非常了解它的诸多特性。就像 HTML 一样,XML 是一个带有开始和结束标记的分层标记语言,每一个标记(标签)内可以包含零个或更多数据。下面是一个 XML 的简单示例片段:

<xml>
  <node>
    <element>Penguin</element>
  </node>
</xml>

在这个 自我描述的 self-descriptive 例子中,XML 解析器使用了以下几个概念:

  • 文档 Document :<xml> 标签标志着一个 文档 的开始,</xml> 标签标志着这个文档的结束。
  • 节点 Node :<node> 标签代表了一个 节点。
  • 元素 Element :<element>Penguin</element> 中,从开头的 < 到最后的 > 表示了一个 元素。
  • 内容 Content : 在 <element> 元素里,字符串 Penguin 就是 内容。

不管你信不信,只要了解了以上几个概念,你就可以开始编写、解析 XML 文件了。

创建一个示例配置文件

要学习如何解析 XML 文件,只需要一个极简的示例文件就够了。假设现在有一个配置文件,里面保存的是关于一个图形界面窗口的属性:

<xml>
  <window>
    <theme>Dark</theme>
    <fullscreen>0</fullscreen>
    <icons>Tango</icons>
</window>
</xml>

创建一个名为 ~/.config/DemoXMLParser 的目录:

$ mkdir ~/.config/DemoXMLParser

在 Linux 中,~/.config 目录是存放配置文件的默认位置,这是在 自由桌面工作组 的规范中定义的。如果你正在使用一个不遵守 自由桌面工作组 Freedesktop 标准的操作系统,你也仍然可以使用这个目录,只不过你需要自己创建这些目录了。

复制 XML 的示例配置文件,粘贴并保存为 ~/.config/DemoXMLParser/myconfig.xml 文件。

使用 Java 解析 XML

如果你是 Java 的初学者,你可以先阅读我写的 面向 Java 入门开发者的 7 个小技巧。一旦你对 Java 比较熟悉了,打开你最喜爱的集成开发工具(IDE),创建一个新工程。我会把我的新工程命名为 myConfigParser。

刚开始先不要太关注依赖导入和异常捕获这些,你可以先尝试用 javax 和 java.io 包里的标准 Java 扩展来实例化一个解析器。如果你使用了 IDE,它会提示你导入合适的依赖。如果没有,你也可以在文章稍后的部分找到完整的代码,里面就有完整的依赖列表。

Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
File configFile = new File(configPath.toString(), "myconfig.xml");

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

DocumentBuilder builder = null;
builder = factory.newDocumentBuilder();

Document doc = null;
doc = builder.parse(configFile);
doc.getDocumentElement().normalize();

这段示例代码使用了 java.nio.Paths 类来找到用户的主目录,然后在拼接上默认配置文件的路径。接着,它用 java.io.File 类来把配置文件定义为一个 File 对象。

紧接着,它使用了 javax.xml.parsers.DocumentBuilder 和 javax.xml.parsers.DocumentBuilderFactory 这两个类来创建一个内部的文档构造器,这样 Java 程序就可以导入并解析 XML 数据了。

最后,Java 创建一个叫 doc 的文档对象,并且把 configFile 文件加载到这个对象里。通过使用 org.w3c.dom 包,它读取并规范化了 XML 数据。

基本上就是这样啦。理论上来讲,你已经完成了数据解析的工作。可是,如果你不能够访问数据的话,数据解析也没有多少用处嘛。所以,就让我们再来写一些查询,从你的配置中读取重要的属性值吧。

使用 Java 访问 XML 的值

从你已经读取的 XML 文档中获取数据,其实就是要先找到一个特定的节点,然后遍历它包含的所有元素。通常我们会使用多个循环语句来遍历节点中的元素,但是为了保持代码可读性,我会尽可能少地使用循环语句:

NodeList nodes = doc.getElementsByTagName("window");

for (int i = 0; i < nodes.getLength(); i++) {
 Node mynode = nodes.item(i);
 System.out.println("Property = " + mynode.getNodeName());
       
 if (mynode.getNodeType() == Node.ELEMENT_NODE) {
   Element myelement = (Element) mynode;
             
   System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
   System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
   System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
 }
}

这段示例代码使用了 org.w3c.dom.NodeList 类,创建了一个名为 nodes 的 NodeList 对象。这个对象包含了所有名字匹配字符串 window 的子节点,实际上这样的节点只有一个,因为本文的示例配置文件中只配置了一个。

紧接着,它使用了一个 for 循环来遍历 nodes 列表。具体过程是:根据节点出现的顺序逐个取出,然后交给一个 if-then 子句处理。这个 if-then 子句创建了一个名为 myelement 的 Element 对象,其中包含了当前节点下的所有元素。你可以使用例如 getChildNodes 和 getElementById 方法来查询这些元素,项目中还 记录了 其他查询方法。

在这个示例中,每个元素就是配置的键。而配置的值储存在元素的内容中,你可以使用 .getTextContent 方法来提取出配置的值。

在你的 IDE 中运行代码(或者运行编译后的二进制文件):

$ java ./DemoXMLParser.java
Property = window
Theme = Dark
Fullscreen = 0
Icon set = Tango

下面是完整的代码示例:

package myConfigParser;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class ConfigParser {

        public static void main(String[] args) {
                Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
                File configFile = new File(configPath.toString(), "myconfig.xml");
                DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = null;
               
                try {
                        builder = factory.newDocumentBuilder();
                } catch (ParserConfigurationException e) {
                        e.printStackTrace();
                }
       
                Document doc = null;
       
                try {
                        doc = builder.parse(configFile);
                } catch (SAXException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        doc.getDocumentElement().normalize();
       
        NodeList nodes = doc.getElementsByTagName("window");
        for (int i = 0; i < nodes.getLength(); i++) {
           Node mynode = nodes.item(i);
           System.out.println("Property = " + mynode.getNodeName());
           
           if (mynode.getNodeType() == Node.ELEMENT_NODE) {
               Element myelement = (Element) mynode;

               System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
               System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
               System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
           } // close if
        } // close for
    } // close method
} //close class

使用 Java 更新 XML

用户时不时地会改变某个偏好项,这时候 org.w3c.dom 库就可以帮助你更新某个 XML 元素的内容。你只需要选择这个 XML 元素,就像你读取它时那样。不过,此时你不再使用 .getTextContent 方法,而是使用 .setTextContent 方法。

updatePref = myelement.getElementsByTagName("fullscreen").item(0);
updatePref.setTextContent("1");

System.out.println("Updated fullscreen to " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());  

这么做会改变应用程序内存中的 XML 文档,但是还没有把数据写回到磁盘上。配合使用 javax 和 w3c 库,你就可以把读取到的 XML 内容写回到配置文件中。

TransformerFactory transformerFactory = TransformerFactory.newInstance();

Transformer xtransform;
xtransform = transformerFactory.newTransformer();

DOMSource mydom = new DOMSource(doc);
StreamResult streamResult = new StreamResult(configFile);

xtransform.transform(mydom, streamResult);

这么做会没有警告地写入转换后的数据,并覆盖掉之前的配置。

下面是完整的代码,包括更新 XML 的操作:

package myConfigParser;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class ConfigParser {

        public static void main(String[] args) {
                Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
                File configFile = new File(configPath.toString(), "myconfig.xml");
                DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = null;
               
                try {
                        builder = factory.newDocumentBuilder();
                } catch (ParserConfigurationException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
       
                Document doc = null;
       
                try {
                        doc = builder.parse(configFile);
                } catch (SAXException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        doc.getDocumentElement().normalize();
        Node updatePref = null;
//        NodeList nodes = doc.getChildNodes();
        NodeList nodes = doc.getElementsByTagName("window");
        for (int i = 0; i < nodes.getLength(); i++) {
           Node mynode = nodes.item(i);
           System.out.println("Property = " + mynode.getNodeName());
           
           if (mynode.getNodeType() == Node.ELEMENT_NODE) {
               Element myelement = (Element) mynode;

               System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
               System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
               System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());

               updatePref = myelement.getElementsByTagName("fullscreen").item(0);
               updatePref.setTextContent("2");
               System.out.println("Updated fullscreen to " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());          
           } // close if
           
        }// close for

        // write DOM back to the file
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer xtransform;

        DOMSource mydom = new DOMSource(doc);
        StreamResult streamResult = new StreamResult(configFile);

        try {
                xtransform = transformerFactory.newTransformer();
                xtransform.transform(mydom, streamResult);
        } catch (TransformerException e) {
                e.printStackTrace();
        }
                       
    } // close method
} //close class

如何保证配置不出问题

编写配置文件看上去是一个还挺简单的任务。一开始,你可能会用一个简单的文本格式,因为你的应用程序只要寥寥几个配置项而已。但是,随着你引入了更多的配置项,读取或者写入错误的数据可能会给你的应用程序带来意料之外的错误。一种帮助你保持配置过程安全、不出错的方法,就是使用类似 XML 的规范格式,然后依靠你用的编程语言的内置功能来处理这些复杂的事情。

这也正是我喜欢使用 Java 和 XML 的原因。每当我试图读取错误的配置值时,Java 就会提醒我。通常,这是由于我在代码中试图获取的节点,并不存在于我期望的 XML 路径中。XML 这种高度结构化的格式帮助了代码保持可靠性,这对用户和开发者来说都是有好处的。


via: https://opensource.com/article/21/7/parsing-config-files-java

作者:Seth Kenlon 选题:lujun9972 译者:lkxed 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

“Java” 有很多意思。除了是印度尼西亚的爪哇岛之外,它还是一个大型的软件开发生态系统。Java 公开发布于 1995 年 3 月 23 日(LCTT 译注:据维基百科数据)。它仍然是企业和休闲软件开发的一个流行平台。从银行业到“我的世界”,许多东西都是由 Java 开发的。

本文将引导你了解构成 Java 的各个组件,以及它们是如何相互作用的。本文还将介绍 Java 是如何集成在 Fedora Linux 中的,以及该如何管理不同的版本。最后,还提供了一个使用游戏《破碎的像素地牢》做的小演示。

Java 的鸟瞰图

下面几个小节快速回顾了 Java 生态系统的几个重要部分。

Java 语言

Java 是一种强类型的、面向对象的编程语言。它的主要设计者是在 Sun 公司工作的 James Gosling,Java 在 1995 年正式公布。Java 的设计受到了 C 和 C++ 的强烈启发,但使用了更精简的语法。没有指针,参数是按值传递的。整数和浮点数不再有有符号和无符号的变体,更复杂的对象如字符串是基础定义的一部分。

但那是 1995 年,该语言在发展中经历了兴衰。在 2006 年至 2014 年期间,没有任何重大发布,停滞不前,这也为市场竞争打开了大门。现在有多种竞争性的 Java 类语言,如 Scala、Clojure 和 Kotlin。现在很大一部分 “Java” 编程都使用这些替代语言规范中的一种,这些语言专注于函数式编程或交叉编译。

// Java
public class Hello {
  public static void main(String[] args) {
    println("Hello, world!");
  }
}

// Scala
object Hello {
  def main(args: Array[String]) = {
    println("Hello, world!")
  }
}

// Clojure
(defn -main
  [& args]
  (println "Hello, world!"))

// Kotlin
fun main(args: Array<String>) {
  println("Hello, world!")
}

现在选择权在你手中。你可以选择使用现代版本,或者你可以选择替代语言之一,如果它们更适合你的风格或业务。

Java 平台

Java 不仅仅是一种语言。它也是一个运行语言的虚拟机,它是一个基于 C/C++ 的应用程序,它接收代码,并在实际的硬件上执行它。除此之外,该平台也是一套标准库,它包含在 Java 虚拟机(JVM)中,并且是用同样的语言编写的。这些库包含集合和链接列表、日期时间和安全等方面的逻辑。

Java 生态系统并不局限于此。还有像 Maven 和 Clojars 这样的软件库,其中包含了相当数量的可用的第三方库。还有一些针对某些语言的特殊库,在一起使用时提供额外的好处。此外,像 Apache Maven、Sbt 和 Gradle 这样的工具允许你编译、捆绑和分发你编写的应用程序。重要的是,这个平台可以和其他语言一起使用。你可以用 Scala 编写代码,让它与 Java 代码在同一平台上一同运行。

还有就是,在 Java 平台和 Android 世界之间有一种特殊的联系。你可以为 Android 平台编译 Java 和 Kotlin,来使用额外的库和工具。

许可证历史

从 2006 年起,Java 平台在 GPL 2.0 下授权,并有一个 类路径例外 classpath-exception 。这意味着每个人都可以建立自己的 Java 平台;包括工具和库。这使得该生态系统的竞争非常激烈。有许多用于构建、分发和开发的工具彼此竞争。

Java 的原始维护者 Sun 公司在 2009 年被甲骨文公司收购。2017 年,甲骨文改变了 Java 软件包的许可条款。这促使多个知名的软件供应商创建自己的 Java 打包链。红帽、IBM、亚马逊和 SAP 现在都有自己的 Java 软件包。他们使用“OpenJDK”商标来区分他们的产品与甲骨文的版本。

值得特别一提的是,甲骨文提供的 Java 平台包并不是 FLOSS。对甲骨文的 Java 商标平台有严格的许可限制。在本文的其余部分,“Java” 指的是 FLOSS 版本:OpenJDK。

最后,类路径例外 值得特别一提。虽然许可证是 GPL 2.0,但类路径例外允许你使用 Java 编写专有软件,只要你不改变平台本身。这使得该许可证介于 GPL 2.0 和 LGPL 之间,它使 Java 非常适用于企业和商业活动。

Praxis

如果这些看起来如此繁杂,请不要惊慌。这是 26 年的软件历史,有很多的竞争。下面的小节演示了在 Fedora Linux 上使用 Java。

在本地运行 Java

默认的 Fedora 工作站 33 的环境包括 OpenJDK 11。该平台的开源代码是由 Fedora 项目的软件包维护者为 Fedora 工作站捆绑的。要想亲眼看看,你可以运行以下内容:

$ java -version

OpenJDK 的多个版本在 Fedora Linux 的默认存储库中都有。它们可以同时安装。使用 alternatives 命令来选择默认使用哪个已安装的 OpenJDK 版本。

$ dnf search openjdk
$ alternatives --config java

另外,如果你安装了 Podman,你可以通过搜索找到大多数 OpenJDK 软件包。

$ podman search openjdk

运行 Java 有许多方式,包括原生的和容器中的。许多其他的 Linux 发行版也带有开箱即用的 OpenJDK。Pkgs.org 有 一个全面的列表。在这种情况下,GNOME Boxes 或 Virt Manager 可以用来运行它们。

要直接参与 Fedora 社区,请看他们的项目 维基。

替代配置

如果你想要的 Java 版本在软件库中不可用,请使用 SDKMAN 在你的主目录中安装 Java。它还允许你在多个已安装的版本之间进行切换,而且它还带有 Ant、Maven、Gradle 和 Sbt 等流行的 CLI 工具。

同样,一些供应商直接提供了 Java 的下载。特别值得一提的是 AdoptOpenJDK,它是几个主要供应商之间的合作,提供简单的 FLOSS 包和二进制文件。

图形化工具

有几个 集成开发环境(IDE)可用于 Java。一些比较流行的 IDE 包括:

  • Eclipse:这是由 Eclipse 基金会发布和维护的自由软件。可以直接从 Fedora 项目的软件库或 Flathub 上安装它。
  • NetBeans:这是由 Apache 基金会发布和维护的自由软件。可以从他们的网站或 Flathub 上安装它。
  • IntelliJ IDEA:这是一个专有软件,但它有一个免费的社区版本。它是由 Jet Beans 发布的。可以从他们的网站或 Flathub 上安装它。

上述工具本身是用 OpenJDK 编写的。这是自产自销的例子。

示范

下面的演示使用了《破碎的像素地牢》,这是一个基于 Java 的 Roguelike 游戏,它在 Android、Flathub 和其他平台上都有。

首先,建立一个开发环境:

$ curl -s "https://get.sdkman.io" | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk install gradle

接下来,关闭你的终端窗口并打开一个新的终端窗口。然后在新窗口中运行以下命令:

$ git clone https://github.com/00-Evan/shattered-pixel-dungeon.git
$ cd shattered-pixel-dungeon
$ gradle desktop:debug

现在,在 Eclipse 中导入该项目。如果 Eclipse 还没有安装,运行下面的命令来安装它:

$ sudo dnf install eclipe-jdt

使用从文件系统导入项目方式来添加《破碎的像素地牢》的代码。

正如你在左上方的导入资源中所看到的,你不仅有项目的代码可以看,而且还有 OpenJDK 及其所有的资源和库。

如果这激励你进一步深入,我想把你引导到《破碎的像素地牢》的 官方文档。《破碎的像素地牢》的构建系统依赖于 Gradle,这是一个可选的额外功能,你必须 在 Eclipse 中手动配置。如果你想做一个 Android 构建,你必须使用 Android Studio。它是一个免费的、Google 品牌的 IntelliJ IDEA 版本。

总结

在 Fedora Linux 上使用 OpenJDK 开发是一件很容易的事情。Fedora Linux 提供了一些最强大的开发工具。使用 Podman 或 Virt-Manager 可以轻松、安全地托管服务器应用程序。OpenJDK 提供了一种创建应用程序的 FLOSS 方式,使你可以控制所有的应用程序组件。

Java 和 OpenJDK 是 Oracle 和/或其附属公司的商标或注册商标。其他名称可能是其各自所有者的商标。


via: https://fedoramagazine.org/java-development-on-fedora-linux/

作者:Kevin Degeling 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

如果你 在 Ubuntu 上运行 Java 程序 ,使用 Eclipse、Maven 或 Netbeans 等等,你将需要将 JAVA_HOME 环境变量设置为正确的路径。否则,你的系统将会向你控诉 “java\_home 环境变量没有设置”。

在这篇初学者教程中,我将向你展示在 Ubuntu 上正确地设置 JAVA_HOME 变量的步骤。这些步骤应该也适用于大多数的其它的 Linux 发行版。

设置过程包含这些步骤:

  • 确保已安装 Java 开发工具包(JDK)。
  • 查找 JDK 可执行文件的正确的位置。
  • 设置 JAVA_HOME 环境变量,并永久更改它。

步骤 1: 核查 JDK 是否已经安装

核查 Java 开发工具包(JDK)是否已经安装在你的 Linux 系统上的最简单的方法是运行这个命令:

javac --version

上面的命令将核查 Java 编译器的版本。如果已经安装了 Java 编译器,它将显示 Java 版本:

Java Compiler is installed

如果上面的命令显示像这样未找到 javac 命令的错误信息,你得先安装 JDK :

Java Compiler is not installed

如果在你的系统上并没有安装 Java 编译器,使用这条命令来安装 Java 开发工具包 (JDK):

sudo apt install default-jdk

这将在你当前的 Ubuntu 版本中安装默认的 Java 版本。如果你需要一些其它版本的 Java 版本,那么你必须 在 Ubuntu 中安装 Java 时 具体指出它的版本。

在你确保 Java 编译器存在于你的系统之中后,接下来就到了查找其位置的时候了。

步骤 2: 获取 JDK 可执行文件(Java 编译器)的位置

可执行文件通常位于 /usr/lib/jvm 目录之中。但我不会让你来玩一个猜谜游戏,让我们来找出 Java 可执行文件的路径。

使用 which 命令 来获取 Java 编译器可执行文件的位置:

which javac

在这里的问题是,它给出的位置实际上是一个 符号链接 。你将需要按照下图执行几次:

最简单的方法是直接使用下面这条命令跟随符号链接来以获取实际的可执行文件:

readlink -f `which java`

readlink 命令会跟随一个符号链接。我在 which java 的外侧使用 readlink 将会使用 which java 的输出来替换要检查的符号链接,这被称之为命令替换。因此,在这个实例中,上面的命令大体上相当于 readlink -f /usr/bin/java 。

在我的示例中,可执行文件的位置是 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 。对你来说可能会不一样。在你的系统中,复制上述命令所获取的正确的路径。你知道,你可以 在 Ubuntu 的终端中复制和粘贴 。

步骤 3: 设置 JAVA\_HOME 变量

现在,你已经获取了位置,使用它来设置 JAVA_HOME 环境变量:

export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/bin/java

核查 JAVA_HOME 目录的值:

echo $JAVA_HOME

尝试在同一个终端中运行你的 Java 程序或工程,并查看它是否工作。

这尚未结束。你刚刚声明的 JAVA_HOME 环境变量是临时的。如果你关闭这个终端或开始一个新的会话,它将会再次变成空的。

为了“永久地”设置 JAVA_HOME 变量,你应该将其添加到你的家目录中的 .bashrc 文件中。

你可以 在 Linux 终端中使用 Nano 编辑器来编辑文件。 如果你不想使用它,并想采取一种简单的复制和粘贴的方法,使用下面的命令:

首先备份你的 .bashrc 文件(以防万一你把它弄坏了,你还可以将其再恢复回来):

cp ~/.bashrc ~/.bashrc.bak

接下来,使用 echo 命令来追加 在这一节开头使用的 export 命令。你应该适当地更改下面的命令,以便其正确地使用你的系统所显示的路径。

echo "export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/bin/java" >> ~/.bashrc

验证它已经被正确地添加到文件的结尾处:

tail -3 ~/.bashrc

上面的 tail 命令 将显示所具体指定文件的最后 3 行。

这里是上面的三个命令的全部的输出:

现在,即使你退出会话或重新启动系统,JAVA_HOME 环境变量都仍将设置为你所具体指定的值。这就是你所想要的,对吧?

注意,如果你将来更改默认的 Java 版本,你将需要更改 JAVA_HOME 环境变量的值并将其指向正确的可执行文件的路径。

我希望这篇教程不仅会帮助你设置 JAVA_HOME 环境变量,也会教会你如何完成这项工作。

如果你仍然面临难题或者有一些疑问或建议,请在评论区告诉我。


via: https://itsfoss.com/set-java-home-ubuntu/

作者:Abhishek Prakash 选题:lujun9972 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

系统上运行着诸多进程,通过 jps 命令能够快速有效识别 Java 进程。

 title=

在 Linux 中,有一些用于查看系统上运行进程的命令。进程是指由内核管理的正在进行的事件。每启动一个应用程序时,就会产生一个进程,但也有许多在计算机后台运行的进程,如保持系统时间准确的进程、监听新文件系统的进程、索引化文件的进程等。有一些可以用来监测这些进程的实用程序,比如包含在 procps-ng 包 中的程序,但它们往往都是对各种进程通用的。它们会查看计算机上的所有进程,你可以根据需要过滤结果列表。

在 Linux 中,可以通过 ps 命令查看进程。这是查看当前系统上运行进程最简单的方法。

$ ps
    PID TTY          TIME CMD
   4486 pts/0    00:00:00 bash
  66930 pts/0    00:00:00 ps

你也可以通过 ps 命令,并配合结果输出管道符进行 grep,从而查看系统上运行的 Java 进程,。

$ ps ax |grep java
  67604 pts/1    Sl+    0:18 /usr/lib/jvm/java-11-openjdk-11.0.12.0.7-4.fc34.x86_64/bin/java -D[Standalone] -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true --add-exports=java.desktop/sun.awt=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED -Dorg.jboss.boot.log.file=/home/alan/wildfly/24.0.1/standalone/log/server.log -Dlogging.configuration=file:/home/alan/wildfly/24.0.1/standalone/configuration/logging.properties -jar /home/alan/wildfly/24.0.1/jboss-modules.jar -mp /home/alan/wildfly/24.0.1/modules org.jboss.as.standalone -Djboss.home.dir=/home/alan/wildfly/24.0.1 -Djboss.server.base.dir=/home/alan/wildfly/24.0.1/standalone

然而,OpenJDK 有自己专属的进程监视器。 Java 虚拟机进程状态 Java Virtual Machine Process Status (jps)工具可以帮你扫描系统上所有运行的 Java 虚拟机(JVM)实例。

要想实现与 ps 命令类似的输出,可以使用 -v 选项。这很实用,这与 ps 相比,可以减少你的输入。

$ jps -v
67604 jboss-modules.jar -D[Standalone] -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true --add-exports=java.desktop/sun.awt=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED -Dorg.jboss.boot.log.file=/home/alan/wildfly/24.0.1/standalone/log/server.log -Dlogging.configuration=file:/home/alan/wildfly/24.0.1/standalone/configuration/logging.properties

jps 命令的默认输出包含进程标识符,类名或 Jar 文件名。

$ jps
67604 jboss-modules.jar
69430 Jps

注意: jps 的手册页指出此命令是试验性且不受支持的。尽管如此,它仍然是一个不错的选择,因为一个系统通常运行着许多进程,这种只识别 Java 进程的快速方法是很有用的。

当下的 Java 仍然是一种流行的语言,所以熟悉 Java 开发工具包和运行时环境仍然很重要。它们包含着许多适用于 Java 应用程序开发和维护的工具。


via: https://opensource.com/article/21/10/check-java-jps

作者:Alan Formy-Duval 选题:lujun9972 译者:unigeorge 校对:turbokernel

本文由 LCTT 原创编译,Linux中国 荣誉推出