第 1 章 Java 环境介绍

第 1 章 Java 环境介绍

欢迎学习 Java 8。也许应该说欢迎你回来。你可能是从其他语言转到这个生态系统的,也可能这是你学习的第一门编程语言。不管你是如何到达这里的,都要欢迎你。很高兴你选择了 Java。

Java 是一个强大且通用的编程环境,是世界上使用范围最广的编程语言之一,在商务和企业计算领域取得了极大的成功。

本章介绍 Java 语言(供程序员编写应用)、Java 虚拟机(用来运行应用)和 Java 生态系统(为开发团队提供很多有价值的编程环境)。

我们会先简要介绍 Java 语言和虚拟机的历史,然后说明 Java 程序的生命周期,最后厘清 Java 和其他环境之间一些常见的疑问。

本章最后会介绍 Java 的安全性,还会讨论一些安全编程相关的话题。

1.1 Java语言、JVM和生态系统

Java 编程环境出现于 20 世纪 90 年代末,由 Java 语言和运行时组成。运行时也叫 Java 虚拟机(Java Virtual Machine,JVM)。

Java 刚出现时,这种分离方式很新奇,但最近软件开发的趋势表明,这已经变成了通用做法。值得一提的是微软的 .NET 环境,它比 Java 晚几年出现,但沿用了非常类似的平台架构方式。

微软的 .NET 平台和 Java 相比有个重要的区别,人们都觉得 Java 是相对开放的生态系统,有多个开发方。在 Java 的演进过程中,这些开发方在合作的同时也有竞争,不断推进 Java 的技术发展。

Java 成功的主要原因之一是,整个生态系统是个标准的环境。这意味着组成 Java 环境的各种技术都有规范。这些标准让开发者和客户相信,自己所用的技术能和其他组件兼容,即便来自不同的技术提供方也不怕。

Java 目前归甲骨文公司所有(甲骨文收购了发明 Java 的太阳计算机系统公司,以下简称 Sun)。红帽、IBM、惠普、SAP、苹果和富士通等公司也大量参与了 Java 标准技术的实现。

Java 也有开源版本,叫 OpenJDK,由多家公司合作开发。

其实,Java 由多个不同但相互联系的环境和规范组成,包括 Java 移动版(Java ME)、Java 标准版(Java SE)和 Java 企业版(Java EE)。本书只涵盖 Java SE 第 8 版。

后面会详细说明标准,现在先介绍 Java 语言和 JVM。这是两个不同但互有关联的概念。

1.1.1 Java语言是什么

Java 程序的源码使用 Java 语言编写。Java 是人类可读的编程语言,基于类,而且面向对象,比较易读易写(偶尔有点啰嗦)。

Java 有意识地降低了教、学成本,参考了 C++ 等语言的行业经验,尽量删除了复杂的功能,但保留了“前辈”编程语言的精粹。

总的来说,Java 的目的是为企业开发商业应用提供坚实稳定的基础。

作为一门编程语言,Java 的设计相对保守,而且改动频率低。这么做是有意保护企业对 Java 技术的投入。

Java 语言自 1996 年发布之后,一直在不断地修订(但没有完全重写)。也就是说,一开始为 Java 选择的设计方式,即 20 世纪 90 年代末采用的那些权宜之计,现在仍旧影响着这门 语言。详情参见第 2 章和第 3 章。

Java 8 的变动幅度很大,是近十年来罕见的(有些人觉得是 Java 出现以来最大的变动)。lambda 表达式的引入和核心中集合 API 的大幅度改写等,将彻底改变大多数 Java 开发者编写代码的方式。

Java 语言受 Java 语言规范(Java Language Specification,JLS)的约束,这个规范限定了某项功能必须采用某种方式实现。

1.1.2 JVM是什么

JVM 是一个程序,提供了运行 Java 程序所需的运行时环境。如果某个硬件和操作系统平台没有相应的 JVM,就不能运行 Java 程序。

幸好,JVM 被移植到了大多数设备中,机顶盒、蓝光播放器、大型机或许都有适用的 JVM。

Java 程序一般都在命令行中启动,例如:

java <arguments> <program name>

这个命令会在操作系统的一个进程中启动 JVM,提供 Java 运行时环境,然后在刚启动的(空)虚拟机中运行指定的程序。

有一点很重要,你要知道:提供给 JVM 运行的程序不是 Java 语言源码,源码必须转换(或编译)成一种称为 Java 字节码的格式。提供给 JVM 的 Java 字节码必须是类文件格式,其扩展名为 .class。

JVM 是字节码格式程序的解释器,一次只执行字节码中的一个指令。而且,你还要知道,JVM 和用户提供的程序都能派生额外的线程,所以用户提供的程序中可能同时运行着多个不同的函数。

JVM 的设计方式建立在几个早期编程环境的多年发展经验之上,尤其是 C 和 C++,因此有多个目的,这些目的都是为了减轻程序员的负担。

  • 包含一个容器,让应用代码在其中运行。

  • 较之 C/C++,提供了一个安全的执行环境。

  • 代开发者管理内存。

  • 提供一个跨平台的执行环境。

介绍 JVM 时往往都会提到这些目的。

前面介绍 JVM 和字节码解释器时已经提到了第一个目的,即 JVM 是应用代码的容器。

第 6 章介绍 Java 环境如何管理内存时会讨论第二个和第三个目的。

第四个目的有时也说成“一次编写,到处运行”,意思是 Java 类文件可从一个运行平台迁移到另一个平台,只要有可用的 JVM,就能正常运行。

也就是说,Java 程序可以在运行着 OS X 的苹果 Mac 电脑中开发(并转换成类文件),然后把类文件移到 Linux 或微软 Windows(或其他平台)中,无需任何改动,Java 程序依然能运行。

 Java 环境被移植到了众多平台中,除了 Linux、Mac 和 Windows 等主流平台外,还支持很多其他平台。本书使用“大多数实现”来概括大多数开发者能接触到的平台。Mac、Windows、Linux、Solaris、BSD Unix 和 AIX 等被视为“主流平台”,都算在“大多数实现”的范围之内。

除了上述四个主要目的之外,JVM 还有一个设计方面的考量很少被提及和讨论,即 JVM 使用运行时信息进行自我管理。

20 世纪 70 年代和 80 年代对软件的研究表明,程序运行时的行为有很多有趣且有用的模式无法在编译时推论得出。JVM 是真正意义上第一个利用这项研究结果的主流平台。

JVM 会收集运行时信息,从而对如何执行代码做出更好的决定。也就是说,JVM 能监控并优化运行在其中的程序,而没有这种能力的平台则做不到这一点。

一个典型的例子是,在运行 Java 程序的生命周期中,各组成部分被调用的次数并不都是相同的,有些部分调用的次数远比其他部分多得多。Java 平台使用一种名为 JIT 编译(just-in-time compilation)的技术解决这个问题。

在 HotSpot JVM(Sun 为 Java 1.3 开发的 JVM,现在仍在使用)中,JVM 首先识别程序的哪一部分调用最频繁(这一部分叫“热点方法”),然后跳过 JVM 解释器,直接把这一部分编译成机器码。

JVM 利用可用的运行时信息,让程序的性能比纯粹经解释器执行更高。事实上,很多情况下,JVM 使用的优化措施得到的性能提升,已经超过了编译后的 C 和 C++ 代码。

描述 JVM 必须怎样运行的标准叫 JVM 规范。

1.1.3 Java生态系统是什么

Java 语言易于学习,而且和其他编程语言相比,拥有的抽象更少。JVM 为 Java 语言(或其他语言)的运行提供了坚实的基础,并且它写出的程序性能高且是可移植的。这两种相互联系的技术放在一起,可以让企业放心选择在何处下力发展。

然而,Java 的优势不止于此。自 Java 初期开始,就形成了范围极广的生态系统,里面有大量的第三方库和组件。也就是说,开发团队能从现有的连接器和驱动器中获益良多,从中他们能获得几乎任何能想到的技术,有些收费,有些则开源。

在当下的技术生态系统中,很少出现某个技术组件提供 Java 连接器的情况。不管是传统的关系数据库,还是 NoSQL,或者各种企业级监控系统和消息系统,都能集成到 Java 中。

这些正是企业和大型公司采用 Java 技术的主要驱动力。使用现有的库和组件能释放开发团队的潜能,让开发者作出更好的选择,利用 Java 核心技术实现最佳的开放式架构。

1.2 Java和JVM简史

  • Java 1.0(1996年)

    这是 Java 的第一个公开发行版,只包含 212 个类,分别放在八个包中。Java 平台始终关注向后兼容性,所以使用 Java 1.0 编写的代码,不用修改或者重新编译,依旧能在最新的 Java 8 中运行。

  • Java 1.1(1997年)

    这一版 Java 平台是原来的两倍多,并且引入了“内部类”和第一版反射 API。

  • Java 1.2(1998年)

    这是 Java 一个非常重要的版本。这一版 Java 平台是原来的三倍,而且首次出现了集合 API(包括 SetMapList)。1.2 版增加的新功能过多,Sun 不得不把平台重新命名为“Java 2 Platform”。这里的“Java 2”是商标,而不是真实的版本号。

  • Java 1.3(2000年)

    这其实是个维护版本,主要用于修正缺陷,解决稳定性,并提升性能。这一版还引入了 HotSpot Java 虚拟机,这个虚拟机现在还在使用(不过有大量的修改和改进)。

  • Java 1.4(2002年)

    这也是一个重要的版本,增加了一些重要的功能,例如高性能低层 I/O API、处理文本的正则表达式、XML 和 XSLT 库、SSL 支持、日志 API 和加密支持。

  • Java 5(2004年)

    这一版 Java 更新幅度很大,对核心语言做了很多改动,引入了泛型、枚举类型(enum)、注解、变长参数方法、自动装包和新版 for 循环。改动的量非常大,所以不得不修改主版本号,以新的主版本号发布。这一版包含 3562 个类和接口,分别放在 166 个包中。在增加的内容中,值得一提的有并发编程的实用工具、远程管理框架和类,以及 Java 虚拟机本身的监测程序。

  • Java 6(2006年)

    这一版也主要是维护和提升性能,引入了编译器 API,扩展了注解的用法和适用范围,还提供了绑定,允许脚本语言和 Java 交互。这一版还对 JVM 和 Swing GUI 技术进行了缺陷修正和改进。

  • Java 7(2011年)

    这是甲骨文公司接管 Java 后发布的第一个版本,包含语言和平台的多项重要升级。这一版引入了处理资源的 try 语句和 NIO.2 API,让开发者编写的资源和 I/O 处理代码更安全且不易出错。方法句柄 API 是反射 API 的替代品,更简单也更安全,而且打开了动态调用(invokedynamic)的大门(Java 1.0 之后第一种新字节码)。

  • Java 8(2014年)

    这是最新版 Java,变动的幅度是自 Java 5(甚至可能是自 Java 出现)以来最大的一次。这一版引入的 lambda 表达式有望显著提升开发者的效率;集合 API 也升级了,改用 lambda 实现,为此,Java 的面向对象实现方式也发生了根本性变化。其他重要更新包括:实现运行在 JVM 中的 JavaScript(Nashorn),新的日期和时间支持,以及 Java 配置(用于生成不同版本的 Java,尤其适合部署无界面或服务器应用)。

1.3 Java程序的生命周期

为了更好地理解 Java 代码是怎么编译和执行的,以及 Java 和其他编程环境的区别,请看图 1-1 中的流程图。

图像说明文字

图 1-1:Java 代码是怎么编译和加载的

整个流程从 Java 源码开始,经过 javac 程序处理后得到类文件,这个文件中保存的是编译源码后得到的 Java 字节码。类文件是 Java 平台能处理的最小功能单位,也是把新代码传给运行中程序的唯一方式。

新的类文件通过类加载机制载入虚拟机(有关类加载机制更详细的说明参见第 10 章),从而把新类型提供给解释器执行。

常见问题解答

本节解答一些关于 Java 和在 Java 环境中编写的程序的生命周期的最常见问题。

1. 字节码是什么

开发者首次接触 JVM 时,可能认为它是“电脑中的电脑”,然后顺其自然把字节码理解为“内部电脑中 CPU 执行的机器码”或“虚拟处理器执行的机器码”。

其实,字节码和运行于硬件处理器中的机器码不太一样。计算机科学家视字节码为一种“中间表现形式”,处在源码和机器码之间。

字节码的目的是,提供一种能让 JVM 解释器高效执行的格式。

2. javac是编译器吗

编译器一般生成机器码,而 javac 生成的是和机器码不太一样的字节码。不过,类文件有点像对象文件(例如 Windows 中的 .dll 文件,或 Unix 中的 .so 文件),人类肯定读不懂。

在计算机科学理论的术语中,javac 非常像编译器的“前半部分”,它生成的中间表现形式可以进一步处理,生成机器码。

不过,因为类文件的生成是构建过程中单独的一步,类似于 C/C++ 中的编译,所以很多开发者都把运行 javac 的操作称为编译。在本书里,我们使用术语“源码编译器”或“javac 编译器”表示生成类文件的 javac。

我们把“编译”看作一个单独的术语,表示 JIT 编译,因为只有 JIT 编译才会生成机器码。

3. 为什么叫“字节码”

指令码(操作码)只占一个字节(有些操作还可以有参数,即跟随其后的字节流),所以只有 256 个可用的指令。实际上,有些指令用不到,大概只会使用 200 个,而且其中还有一些是最新版 javac 不支持的。

4. 字节码是优化过的吗

Java 平台的早期阶段,javac 会对生成的字节码进行大量优化。后来表明这么做是错的。JIT 编译出现后,重要的方法会被编译成运行速度很快的机器码。之所以要减轻 JIT 编译器的负担,是因为 JIT 编译获得的效果,比字节码优化多很多,而且字节码还要经过解释器处理。

5. 字节码真的与设备无关吗?那字节顺序呢

不管在哪种设备中生成,字节码的格式都是一样的,其中也包括设备使用的字节顺序。如果你想知道,我告诉你,字节码始终使用大字节序(big-endian)。

6. Java是解释性语言吗

JVM 基本上算是解释器(通过 JIT 编译大幅提升性能)。可是,大多数解释性语言(例如 PHP、Perl、Ruby 和 Python)都直接从源码解释程序(一般会从输入的源码文件中构建一个抽象句法树)。而 JVM 解释器需要的是类文件,因此当然需要多一步操作,即使用 javac 编译源码。

7. 其他语言可以在JVM中运行吗

可以。JVM 可以运行任何有效的类文件,因此,Java 之外的语言可以通过两种方式在 JVM 中运行。第一种,提供用于生成类文件的源码编译器(类似于 javac),以类似 Java 代码的方式在 JVM 中运行(Scala 等语言采用的是这种方式)。

Java 之外的语言可以使用 Java 实现解释器和运行时,然后解释该语言使用的源码格式。JRuby 等语言采用的就是这种方式(不过 JRuby 的运行时很复杂,某些情况下能辅助 JIT 编译)。

1.4 Java的安全性

Java 的设计始终考虑安全性,因此和很多其他现有系统和平台相比有很大的优势。Java 的安全架构由安全专家设计,而且这个平台发布之后,很多其他安全专家仍在研究和探讨。专家们一致认为,Java 的安全架构坚固牢靠,在设计层面没有任何安全漏洞(至少还没有发现)。

Java 安全模型的基础是,严格限制字节码能表述的操作,例如,不能直接访问内存,因此避免了困扰 C 和 C++ 等语言的一整类安全问题。而且,只要 JVM 加载了不信任的类,就会执行字节码校验操作,从而避免了大量问题(字节码校验的更多信息,参见第 10 章)。

尽管如此,没有任何系统能保证 100% 的安全性,Java 也不例外。

虽然从理论上讲,设计是牢固的,但安全架构的实现是另外一回事,在某些 Java 实现中,一直都在发现和修补安全缺陷。

不得不说,Java 8 的延期发布,至少部分原因是发现了一些安全问题,必须要投入时间进行修复。

我相信,在实现 Java 虚拟机的过程中始终都会发现(并修正)安全缺陷。

不过,值得注意的是,最近发现的 Java 安全问题大都与桌面技术有密切联系。在日常的服务器端编程方面,Java 仍是当前最安全的通用平台。

1.5 Java和其他语言比较

本节简要列出 Java 平台和其他你可能熟悉的编程环境之间的重要不同点。

1.5.1 Java和C语言比较

  • Java 面向对象,C 面向过程。

  • Java 通过类文件实现可移植性,C 需要重新编译。

  • Java 为运行时提供了全面的监测程序。

  • Java 没有指针,也没有指针相等性运算。

  • Java 通过垃圾回收提供了自动内存管理功能。

  • Java 无法从低层布局内存(没有结构体)。

  • Java 没有预处理器。

1.5.2 Java和C++比较

  • Java 的对象模型比 C++ 简单。

  • Java 默认使用虚分派(virtual dispatch)。

  • Java 始终使用值传递(不过 Java 中的值也能作为对象引用)。

  • Java 不完全支持多重继承。

  • Java 的泛型没 C++ 的模板强大(不过危害性较小)。

  • Java 无法重载运算符。

1.5.3 Java和PHP比较

  • Java 是静态类型语言,PHP 是动态类型语言。

  • Java 有 JIT,PHP 没有(PHP 6 可能会有)。

  • Java 是通用语言,PHP 在网站技术之外很难见到。

  • Java 支持多线程,PHP 不支持。

1.5.4 Java和JavaScript比较

  • Java 是静态类型语言,JavaScript 是动态类型语言。

  • Java 使用基于类的对象,JavaScript 使用基于原型的对象。

  • Java 提供了良好的对象封装,JavaScript 没有提供。

  • Java 有命名空间,JavaScript 没有。

  • Java 支持多线程,JavaScript 不支持。

1.6 回应对Java的一些批评

Java 出现在公共视线中已有很长一段时间了,因此,在这些年里受到的批评也相当多。这些批评可以归咎于一些技术缺点,以及第一版过度的市场推广。

不过,有些批评只是技术圈的传言,不是很准确。本节,我们来看一些常见的抱怨,以及它们在最新版 Java 平台中的状况。

1.6.1 过度复杂

人们经常批评 Java 核心语言过度复杂。即便是 Object o = new Object(); 这样简单的语句,也有重复——赋值符号左右两边都出现了类型 Object。批评人士认为这么做完全是多余的,其他语言都不需要重复声明类型,而且很多辅助功能都不用这么做(例如类型推导)。

这样的说法我不认同。从一开始,Java 的设计目标就是易于阅读(读代码的次数比写代码多很多),许多程序员,尤其是新手,都觉得额外的类型信息有助于阅读代码。

Java 广泛用于企业环境,开发团队往往和运维团队不同。这些额外的信息一般会在处理停机,或者需要维护和修订早就投身其他事务的开发者编写的代码时提供重大帮助。

在最近几个 Java 版本中(7 和后面的版本),语言的设计者已经在尝试回应这些观点,他们寻找可以简化句法复杂度的地方,也更充分地利用类型信息。例如:

// 文件辅助方法
byte[] contents =
  Files.readAllBytes(Paths.get("/home/ben/myFile.bin"));

// 使用菱形句法表示重复的类型信息
List<String> l = new ArrayList<>();

// lambda表达式,简化了Runnable
ExecutorService threadPool = Executors.newScheduledThreadPool(2);
threadPool.submit(() -> { System.out.println("On Threadpool"); });

然而,Java 的总体原则是非常缓慢且谨慎地修改语言,所以这些变化可能无法完全让批评者满意。

1.6.2 变化慢

Java 第一版发布至今已经超过 15 年了,而且在那个时候也没经过完整修订。在这段时间里,很多其他语言(例如微软的 C#)都发布了不向后兼容的版本,而 Java 没这么做,因此受到了部分开发者的批评。

而且,最近几年,Java 语言因为没有及时吸收其他语言中常见的功能而受到严厉批评。

Sun(现在是甲骨文)在语言设计上采取了保守方式,是为了尽量避免把成本和不合理功能的外部效应强加在大量的用户群体身上。很多使用 Java 的公司都为这一技术注入了重资,语言设计者要认真负责,不能影响现有的用户和安装群体。

每一个新语言功能都要审慎考虑,不只是新功能本身,还要考虑它会如何影响语言现有的功能。有时,新功能的影响会超过目及之处,而 Java 的使用范围又如此广泛,因此可能有很多地方会产生意料之外的影响。

功能发布后,如果有问题,几乎无法将其删除。Java 有一些不合理的功能(例如终结机制),在不影响安装群体的情况下,根本无法安全地删除。语言设计者认为,在语言演进的过程中必须极为小心。

话虽如此,但 Java 8 引入的新语言功能向前迈出了一大步,回应了最常见的功能缺失抱怨,应该能为开发者提供他们一直诉求的语言特性。

1.6.3 性能问题

现在仍然有人批评 Java 平台的速度慢,而且所有批评都集中在“平台”上,这或许是最不合理的批评了。

Java 1.3 引入了 HotSpot 虚拟机和 JIT 编译器,而且在随后的 15 年里,一直在革新和改进虚拟机及其性能。现在,Java 平台的速度异常快,经常会在流行的框架性能评测中取胜,甚至打败了编译成本地机器码的 C 和 C++。

针对这方面的批评大都是因为陈旧的记忆,因为以前的某段时间 Java 很慢。Java 使用的大型且不规则延展的架构方式可能也加深了人们对性能低下的印象。

然而,事实上,任何大型架构都需要评测、分析和性能调校,才能得到最好的表现,Java 也不例外。

Java 平台的核心(Java 语言和 JVM)不仅现在是,以后也仍将是开发者可用的速度最快的通用环境。

1.6.4 不安全

2013 年,Java 平台出现了几个安全漏洞,导致 Java 8 的发布日期延后了。其实,在此之前就有人批评 Java 的安全漏洞数量众多。

在这些漏洞中,有很多都涉及 Java 系统的桌面和 GUI 组件,不会影响使用 Java 编写的网站或其他服务器端代码。

所有编程平台都会时不时地出现安全问题,而且很多其他语言的安全漏洞不比 Java 少,只是少有人知罢了。

1.6.5 太注重企业

Java 平台在公司和企业的开发者中使用广泛,因此觉得 Java 太注重企业一点也不奇怪。人们认为 Java 缺少面向社区的语言所具有的自由风格。

其实,Java 一直都是,而且以后仍将是社区和免费或开源软件开发所广泛使用的语言。在 GitHub 和其他项目托管网站中,Java 是最受欢迎的。

而且,使用范围最广的 Java 语言是通过 OpenJDK 实现的。而 OpenJDK 本身就是开源项目,其社区充满活力,一直在不断增长。

目录