13.1 命令行工具

我们要介绍的命令行工具是最常用的,也是最实用的。不过,我们不会详细说明每个可用的工具,尤其是涉及 CORBA1 和 RMI2 服务器部分的工具。

1CORBA 是 Common Object Request Broker Architecture 的简称,中文意思是“通用对象请求代理架构”,是一种软件架构标准。——译者注

2RMI 是 Remote Method Invocation 的简称,中文意思是“远程方法调用”。使用这种机制可以在一个 Java 虚拟机的对象上调用另一个 Java 虚拟机中的方法。——译者注

13.1 命令行工具 - 图1 有时我们要讨论指定文件系统路径的选项。遇到这种情况时,和本书其他地 方一样,我们都使用 Unix 惯用的路径表示方法。

我们要介绍的工具包括:

  • javac

  • java

  • jar

  • javadoc

  • jdeps

  • jps

  • jstat

  • jstatd

  • jinfo

  • jstack

  • jmap

  • javap

13.1.1 javac

1. 基本用法

javac some/package/MyClass.java

2. 说明

javac 是 Java 源码编译器,把 .java 源码文件编译成字节码(保存在 .class 文件中)。

现代化 Java 项目往往不直接使用 javac,因为它相对低层,也不灵便,尤其是对较大型的代码基而言。现代化集成开发环境(Integrated Development Environment,IDE)要么自动为开发者调用 javac,要么提供原生编译器,在编写代码的同时调用。部署时,多数项目会使用单独的构建工具,例如 Maven、Ant 或 Gradle。本书不会介绍这些工具。

尽管如此,开发者还是要掌握如何使用 javac,因为对小型代码基来说,有时手动编译更好,而不用安装和管理产品级构建工具,例如 Maven。

3. 常用选项

  • -classpath

提供编译时需要的类。

  • -d some/dir

告诉 javac 把编译得到的类文件放在哪儿。

  • @project.list

从 project.list 文件中加载选项和源码文件。

  • -help

选项的帮助信息。

  • -X

非标准选项的帮助信息。

  • -source

设定 javac 能接受的 Java 版本。

  • -target

设定 javac 编译得到的类文件版本。

  • -profile

设定编译应用时 javac 使用的配置。本章后面会详细介绍紧凑配置。

  • -Xlint

显示详细的警告信息。

  • -Xstdout

把编译过程中的输出存入一个文件。

  • -g

把调试信息添加到类文件中。

4. 备注

根据习惯,javac 有两个选项(-source-target)用来指定编译器接受的源码语言版本和编译得到的类文件格式的版本。

这个功能为开发者提供了些许好处,却为编译器带来了额外的复杂度(因为内部要支持多种语言句法)。Java 8 稍微对这个功能做了清理,变得更正式了。

从 JDK 8 开始,javac 的这两个选项只能指定为前三个版本,即 JDK 5、JDK 6、JDK 7、JDK 8。不过,这对 java 解释器没影响——所有 Java 版本的类文件都能在 Java 8 的 JVM 中运行。

C 和 C++ 开发者可能觉得 -g 选项不如在这两个语言中有用。这是因为 Java 生态系统广泛使用 IDE,较之类文件中附加的调试符号,IDE 集成的调试信息有用得多,而且更易于使用。

是否使用 Lint 功能,在开发者中还有一些争议。很多 Java 开发者编写的代码会触发大量编译提醒,而他们直接将其忽略。可是,编写大型代码基的经验告诉我们,大多数情况下,触发提醒的代码可能潜藏着难以发现的缺陷。因此,强烈推荐使用 Lint 功能或静态分析工具(例如 FindBugs)。

13.1.2 java

1. 基本用法

  1. java some.package.MyClass
  2. java -jar my-packaged.jar

2. 说明

java 是启动 Java 虚拟机的可执行文件。程序的首个入口点是指定类中的 main() 方法。这个方法的签名如下:

  1. public static void main(String[] args);

这个方法在启动 JVM 时创建的应用线程里运行。这个方法返回后(以及启动的其他所有非守护应用线程都终止运行),JVM 线程就会退出。

如果执行的是 JAR 文件而不是类(可执行的 jar 格式),那么 JAR 文件必须包含一个元数据,告诉 JVM 从哪个类开始执行。

这个元数据是 Main-Class: 属性,包含在 META-INF/ 目录里的 MANIFEST.MF 文件中。详情参见 jar 工具的说明。

3. 常用选项

  • -cp

定义从哪个路径读取类。

  • -X-?-help

显示 java 可执行文件及其选项的帮助信息。

  • -D

设定 Java 系统属性,在 Java 程序中能取回设定的属性。使用这种方式可以设定任意个属性。

  • -jar

运行一个可执行的 JAR 文件(参见对 jar 的介绍)。

  • -Xbootclasspath(/a or /p)

运行时使用其他系统类路径(极少使用)。

  • -client-server

选择一个 HotSpot JIT 编译器(参见“备注”)。

  • -Xint-Xcomp-Xmixed

控制 JIT 编译(极少使用)。

  • -Xms

设定分配给 JVM 堆内存的最小值。

  • -Xmx

设定分配给 JVM 堆内存的最大值。

  • -agentlib:-agentpath:

指定一个 JVM Tooling Interface(JVMTI)代理,附着在启动的进程上。这种代理一般用于监测程序。

  • -verbose

生成额外的输出,有时对调试有用。

4. 备注

HotSpot 虚拟机中有两个不同的 JIT 编译器,一个是客户端编译器(C1),一个是服务器编译器(C2)。这两个编译器的作用不同,客户端编译器更能预知性能,而且启动快,不过不会主动优化代码。

以前,Java 进程使用的 JIT 编译器在启动进程时通过指定 -client-server 选项指定。不过,随着硬件的发展,编译过程的消耗越来越少,因此出现了一种新的方式:早期使用客户端编译器,预热 Java 进程之后,换用服务器编译器,优化代码提高性能。这种方案叫分层编译(Tiered Compilation),是 Java 8 默认采用的方案。多数进程都不再需要显式指定 -client-server 选项。

在 Windows 平台中,经常使用一个稍微不同的 java 可执行文件——javaw。这个版本启动 Java 虚拟机时不会强制显示 Windows 终端窗口。

旧版 Java 支持一些过时的不同解释器和虚拟机模式。现在,这些模式基本都移除了,依然存在的应该理解为残留品。

-X 开头的选项是非标准选项。不过,有些选项也开始变成标准了(尤其是 -Xms-Xmx)。与此同时,不同的 Java 版本不断引入 -XX: 选项。这些选项是实验性质的,不要在生产中使用。不过,随着实现越来越稳定,高级用户可以使用其中一些选项(甚至可以在部署到生产环境的应用中使用)。

总之,本书不会详细介绍所有选项。配置生产环境使用的 JVM 需要专业知识,开发者一定要小心,尤其不能随意调整垃圾回收子系统相关的选项。

13.1.3 jar

1. 基本用法

jar cvf my.jar someDir/

2. 说明

实用工具 jar 用于处理 Java 档案(.jar)文件。这是 ZIP 格式的文件,包含 Java 类、附加的资源和元数据(通常会有)。这个工具处理 .jar 文件时有五种主要的操作模式:创建、更新、索引、列表和提取。

这些模式由传给 jar 命令的参数字符(不是选项)控制,而且一次只能指定一个字符,不过还可以使用可选的修饰符。

3. 常用选项

  • c

新建一个档案文件。

  • u

更新档案文件。

  • i

索引档案文件。

  • t

列出档案文件中的内容。

  • x

提取档案文件中的内容。

4. 修饰符

  • v

详细模式。

  • f

处理指定的文件,而不是标准输入。

  • 0

存储但不压缩添加到档案文件中的文件。

  • m

把指定文件中的内容添加到 JAR 文件的元数据清单文件中。

  • e

把 JAR 文件变成可执行文件,而且使用指定的类作为入口点。

5. 备注

jar 命令的句法是故意制定得和 Unix 的 tar 命令非常类似的,因此 jar 才使用命令参数,而不使用选项(Java 平台的其他命令使用选项)。

创建 .jar 文件时,jar 工具会自动添加一个名为 META-INF 的目录,并在其中创建一个名为 MANIFEST.MF 的文件——这个文件中保存的是元数据,格式为首部与值配对。默认情况下,MANIFEST.MF 文件中只包含两个首部:

  1. Manifest-Version: 1.0
  2. Created-By: 1.8.0 (Oracle Corporation)

使用 m 修饰符,创建 JAR 文件时才会把额外的元信息添加到 MANIFEST.MF 文件中。经常添加的属性是 Main-Class:,指定 JAR 文件中所含应用的入口点。包含 Main-Class: 属性的 JAR 文件可以通过 java -jar 命令直接由 JVM 执行。

因为经常要添加 Main-Class: 属性,索性 jar 提供了 e 修饰符,直接在 MANIFEST.MF 文件中创建这个属性,而不用再单独创建一个文本文件。

13.1.4 javadoc

1. 基本用法

javadoc some.package

2. 说明

javadoc 从 Java 源码文件中生成文档。javadoc 会读取特定格式的注释(叫 Javadoc 注释),将其解析成标准的文档格式,然后再输出为各种格式的文档(不过,目前为止,HTML 是最常用的)。

Javadoc 句法的详细说明参见第 7 章。

3. 常用选项

  • -cp

定义要使用的类路径。

  • -D

告诉 javadoc 把生成的文档保存在哪里。

  • -quiet

静默命令行输出,但保留错误和提醒信息。

4. 备注

Java 平台的 API 文档都是使用 Javadoc 写的。

javadoc 底层使用的类和 javac 一样,而且实现 Javadoc 的功能时用到了源码编译器的部分基础设施。

javadoc 一般用于生成整个包的文档,而不是单个类。

javadoc 的参数非常多,能控制很多方面的行为。不过本书不会详细介绍所有选项。

13.1.5 jdeps

jdeps 是个静态分析工具,用于分析包或类的依赖。这个工具有多种用途,可以识别开发者编写的代码中对 JDK 内部未注释的 API 的调用,还能帮助跟踪传递依赖。

jdeps 还能确认 JAR 文件是否能在某个紧凑配置中运行(本章后面会详细介绍紧凑配置)。

1. 基本用法

jdeps com.me.MyClass

2. 说明

jdeps 分析指定的类,输出依赖信息。指定的类可以是类路径中的任何类、文件路径、目录或者 JAR 文件。

3. 常用选项

  • -s-summary

只打印依赖概要。

  • -v-verbose

打印所有类级依赖。

  • -verbose:package

打印包级依赖,并且排除同一个档案文件中的依赖。

  • -verbose:class

打印类级依赖,并且排除同一个档案文件中的依赖。

  • -p -package

找出指定包的依赖。这个选项可以多次使用,指定多个不同的包。-p 选项和 -e 选项是互斥的。

  • -e -regex

找出包名匹配正则表达式的包的依赖。-p 选项和 -e 选项是互斥的。

  • -include

限制只分析匹配模式的类。这个选项的作用是过滤要分析的类。这个选项可以结合 -p-e 使用。

  • -jdkinternals

找出 JDK 内部 API 的类级依赖(即使是平台的小版本发布,内部 API 也可能发生变化或消失)。

  • -apionly

限制只分析 API。例如,对公开类的公开方法和受保护的方法来说,从其签名中能找出的依赖包括:字段类型、方法参数类型、返回值类型和已检异常类型。

  • -R-recursive

递归遍历所有依赖。

  • -h-?-help

打印 jdeps 的帮助信息。

4. 备注

虽然 Jigsaw 项目没有随 Java 8 一起发布,但是,使用 jdeps 分析 JRE 的依赖,让我们第一次知道,它不是一个整体,而是更加模块化。

13.1.6 jps

1. 基本用法

jps

2. 说明

jps 列出本地设备中所有活动的 JVM 进程(如果远程设备中运行着合适的 jstatd 实例,还能列出这台远程设备中的 JVM 进程)。

3. 常用选项

  • -m

输出传给 main() 方法的参数。

  • -l

输出应用主类的完整包名(或者应用 JAR 文件的完整路径)。

  • -v

输出传给 JVM 的参数。

4. 备注

严格来说没必要使用这个工具,使用标准的 Unix ps 命令就足够了。不过,jps 没有使用标准的 Unix 机制查询进程,所以某些情况下,已经停止响应的 Java 进程(而且在 jps 看来也已经“死亡”)还会被操作系统作为存活的进程列出来。

13.1.7 jstat

1. 基本用法

jstat

2. 说明

这个命令显示指定 Java 进程的一些基本信息。查看的通常是本地进程,不过,如果远程设备中运行着合适的 jstatd 进程,也能查看这台远程设备中的进程。

3. 常用选项

  • -options

列出 jstat 能输出的信息类型。

  • -class

输出目前为止类加载的活动状态。

  • -compiler

目前为止当前进程的 JIT 编译信息。

  • -gcutil

详细的垃圾回收信息。

  • -printcompilation

更详细的编译信息。

4. 备注

jstat 用来识别进程(可能在远程设备中)的通用句法是:

  1. [<protocol>://]<vmid>[@hostname][:port][/servername]

这个通用句法用于指定远程设备中的进程(通常通过 RMI 使用 JMX3 连接),不过实际上,指定本地进程的句法更常用。本地进程只需指定虚拟机的 ID,在主流平台(例如 Linux、 Windows、Unix 和 Mac 等)中就是操作系统的进程 ID。

3JMX(Java Management Extensions,即 Java 管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。——译者注

13.1.8 jstatd

1. 基本用法

jstatd

2. 说明

jstatd 能让本地 JVM 的信息通过网络传出去。这个过程通过 RMI 实现,JMX 客户端可以访问原本在本地的功能。若想传递信息,需要特殊的安全设置,这和 JVM 的默认设置有所不同。启动 jstatd 之前要先创建下述文件,并将其命名为 jstatd.policy:

  1. grant codebase "file:${java.home}../lib/tools.jar {
  2. permission java.security.AllPermission
  3. }

这个策略文件会为从 JDK 中的 tools.jar 文件中加载的所有类获取安全授权。

若想让 jstatd 使用这个策略文件,要执行下述命令:

  1. jstatd -J-Djava.security.policy=<path to jstat.policy>

3. 常用选项

  • -p

在指定的端口上寻找 RMI 注册表,如果找不到就创建一个。

4. 备注

推荐的做法是,在所有生产环境中都开启 jstatd,但不能通过公网访问。在多数企业环境中有必要这么做,而且需要运营部门和网络工程部门的协作。不过,从生产环境中的 JVM 获取遥测数据的好处(尤其是服务中断期间)不能夸大。

对 JMX 和监控技术的完整介绍已经超出本书范畴。

13.1.9 jinfo

1. 基本用法

jinfo jinfo

2. 说明

这个工具用于显示系统属性和运行中的 Java 进程(或核心文件)的 JVM 选项。

3. 常用选项

  • -flags

只显示 JVM 的命令行标志。

  • -sysprops

只显示系统属性。

4. 备注

其实,这个工具很少使用。不过,偶尔可以用来做健全检查,确认程序正在做本该做的事。

13.1.10 jstack

1. 基本用法

jstack

2. 说明

jstack 实用工具用于输出进程中每个 Java 线程的堆栈跟踪。

3. 常用选项

  • -F

强制线程转储。

  • -l

长模式(包含关于锁的额外信息)。

4. 备注

生成堆栈跟踪时不会停止或终止 Java 进程。jstack 生成的文件可能很大,经常需要做后续处理。

13.1.11 jmap

1. 基本用法

jmap

2. 说明

jmap 用于查看运行中的 Java 进程的内存分配情况。

3. 常用选项

  • -histo

生成内存分配当前状态的直方图。

  • -histo:live

这种直方图只显示存活对象的信息。

  • -heap

生成运行中的进程的堆转储。

4. 备注

生成直方图时会走查 JVM 分配表。分配表中包含存活对象和(还未回收的)死亡对象。直方图按照对象使用内存的方式组织,按使用内存的字节数从高到低排列。生成直方图的标准方式不会中断 JVM。

生成存活对象的直方图时,为了确保结果的准确性,生成前会执行一次完整的 Stop-The-World(STW)垃圾回收。因此,如果一次完整的垃圾回收过程会明显影响用户,就不能在生产系统中使用这种方式生成直方图。

-heap 方式来说,要注意,生成堆转储的过程所需的时间可能很长,而且需要执行 STW 垃圾回收。在很多进程中,得到的文件可能非常大。

13.1.12 javap

1. 基本用法

javap

2. 说明

javap 是 Java 类的反汇编程序,也就是查看类文件内容的工具。javap 能显示 Java 方法编译得到的字节码,还能显示“常量池”信息(包含的信息类似于 Unix 进程的符号表)。

默认情况下,javap 能显示公开方法、受保护的方法和默认方法的签名。使用 -p 选项还能显示私有方法的签名。

3. 常用选项

  • -c

反编译字节码。

  • -v

详细模式(包含常量池信息)。

  • -p

包含私有方法的签名。

4. 备注

只要 javap 所在的 JDK 版本等于或大于生成类文件的 JDK 版本,javap 就能处理这个类文件。

13.1 命令行工具 - 图2 某些 Java 语言特性生成的字节码可能令人奇怪。例如,第 9 章说过,String 类的实例其实是不可变的,JVM 使用运算符 + 连接字符串时,会先从原来的字符串上实例化一个新 StringBuilder 对象,修改之后再调用 toString() 方法,得到连接后的(新)实例。这一点在 javap 反汇编得到的字节码中可以清晰地看出来。