from:《Java深度历险》第一章
第一章
深入 Java 2 SDK
你越讨厌的事情,就越容易遇上
前言
Sun Microsystems 所发表的 Java 开发工具 – Java 2 SDK,永远都是 Java
初 学 者 最 早 接 触 到 的 开 发 工 具 。 一 般 人 习 惯 称 这 套 工 具 叫 作 JDK(Java
Development Kit)。
JDK 是在 Java 2 Platform 之前的 Java Platform 所使用的开发工具名称(在
本文中有时候会写 JDK,有时候会写 Java 2 SDK,但指的将是同一种东西),我
记得曾经有人戏称它是 Java Developer Killer 的缩写,除了挖苦 Sun 所製作的
开发工具没有微软设计的开发工具要来的方便之外,其实还说明了另外一件事,
就是 Java 从 1995 年发表后到现在,即使每一版的 JDK 都会附上为数庞大的官
方说明文件,从来没有任何一份文件或一本书籍详细说明这套官方开发工具的特
性。
没有文件或书籍来描述 JDK 的特性并不代表这些特性不重要。毕竟,任何最
新的标准类别函式库,或是最新版本的虚拟机器,一定都会伴随著最新版的 JDK
所释出。就算您想跳过 JDK,直接使用如 Borland JBuilder 或 Forte for Java
这类的高级开发工具,JDK 仍然如影随形。以 Borland JBuilder 来说,当您将
Borland JBuilder 安装完成之后,在 JBuilder 的所在目录下也会内含一套 Java
2 SDK,如下图所示:
图 :Borland JBuilder 内附的 Java 2 SDK
因此,我们可以得知,不管您如何地讨厌 JDK,只要想开发 Java 相关的应
用程式,您就无法逃离 JDK 的掌握。事实上,不管您开发的是 J2SE、J2EE、J2ME、
甚至 Java Card 的应用程式,除了需要各种版本对应的开发套件之外,一定需
要 JDK 的辅助。
图 : 各种版本的 Java 应用程式,都需要 Java 2 SDK 的辅助
那麽,JDK 到底是什麽东西? 从技术的观点来说,因为高阶开发工具都是
架设在 JDK 上头,因此高阶开发工具的行为或是引发的错误讯息都是根源自
JDK。为了更正确地掌控高阶的 Java 开发工具,所以我们必须了解 JDK 的特性
和组成。 从求知的观点来看,Java 程式设计师每天输入无数的 java xxx.java
与 java xxx,到底 Java 程式是如何运作的? 在我们看不到的底层,到底发生了
什麽事情? 如果我们可以清楚地得知所有的来龙去脉,将会让我们更了解这套开
发工具。
上述两个观点,都是本章所希望告诉您的。让我们开始深入了解 JDK 吧!
执行 java.exe 时所发生的怪事
当您在使用 JDK 时,您是否曾经发现执行 java.exe 的时候,会有底下一些
奇怪的现象:
如果您安装的是 Java 2 SDK 1.3.x :
当您安装完 Java 2 SDK 1.3.x 之后,如果从未修改您电脑裡头的任何设定,
就直接进到命令提示字元下,执行 java.exe,就会出现底下画面:
萤幕上会告诉您,有一个 –hotspot 选项可供您使用。
但是,此时如果我们输入指令:
path=c:\jdk1.3.1\bin
(注:假设笔者的 Java 2 SDK 1.3.x 安装于 c:\jdk1.3.1 底下)
然后重新执行 java.exe,则萤幕上的输出如下:
这一次,萤幕上的输出告诉我们有 –hotspot、-server、以及-classic 可供选择。
这真是奇怪的事情,原本根本没有出现在萤幕上的–hotspot、-server、以及
-classic 选项,在我们使用 path 这个系统命令来改变执行档的搜寻路径之后,
竟然出现了!
如果您安装的是 Java 2 SDK JDK 1.4.x :
当您安装完 Java 2 SDK 1.4.x 之后,如果从未修改您电脑裡头的任何设定,就
直接进到命令提示字元下,执行 java.exe,就会出现底下画面:
画面上告诉您,java.exe 有几个选项,包括 –client、-server、-hotspot(与-client
意义相同,但是已不建议使用)。这几个选项可以用来调整执行 Java 程式时所
使用的 Java 虚拟机器。
所以此时如果您输入
java –version
的时候,萤幕输出如下:
而如果输入:
java –server -version
并执行之,萤幕上会出现错误讯息:
Error: no `server' JVM at `C:\Program
Files\Java\j2re1.4.0-rc\bin\server\jvm.dll`.
意思是说,它在特定位置找不到名为 server 的虚拟机器。也就是虽然萤幕上有
列出此选项,可是却无法使用。
但是,此时如果我们输入指令:
path=d:\j2sdk1.4.0\bin
(注:假设笔者的 JDK 1.4.x 安装于 d:\j2sdk1.4.0 底下)
然后重新执行:
java –server
将不再出现错误讯息:
如果此时您输入
java –server -version
则萤幕的输出如下:
真奇怪,原本无法运作的-server 选项,在我们使用 path 这个系统命令来改变
执行档的搜寻路径之后,竟然可以运作了!
这真是令人匪夷所思的事情。
如此怪异的现象,在 JDK 1.3.x 上最容易看出来(JDK 1.4.x 上其实也是一样
奇怪,因为无法使用的选项以及没有出现在我们眼中的选项,基本上是一样的。
只不过如果没有特别去尝试,就很难看得出来,搞不好您还会认为眼不见为淨,
出现一些无法使用的选项反倒混淆使用者):
上述这麽奇特的现象,该做如何解释? 我们有办法预测其行为吗? 再者,
在您过去使用 Java 2 SDK 来开发 Java 应用程式的生涯之中,是否也曾发生过
底下的怪事:
很可能您才刚刚灌好某个版本的 Java 2 SDK 或刚刚移除某个版本的 Java 2
SDK,但是之后却在执行 java.exe 时,萤幕上竟然告诉您:
在您经验中,萤幕上的 1.4 和 1.3 可能会换成其他版本号码,总之就是版本
不合所引发的问题。
因此接下来,我们将开始探讨这种”灵异现象”的成因,并追踪其来龙去脉。
最后,当您下次再遇到这种情况时,您将可以很容易地找出问题的解决方法。
JDK 、 JRE 、 JVM 之间的关系
要探讨上述的奇怪现象,必须先弄清楚 JDK(Java 开发套件)、JRE(Java
执行环境,Java Runtime Environment)、还有 JVM(Java 虚拟机器,Java
Virtual Machine)三者之间的关系。
当您想要从 http://www.javasoft.com 下载 JDK 来开发程式的时候,或许
您曾经疑惑过,官方网站上会出现 JDK 与 JRE 两种套件,然而一般初学者不清
楚 JDK 和 JRE 有什麽不同,所以产生疑惑。这两者一定有所不同,否则 Sun
Microsystems 不可能将两者区分开来。关于 JDK 与 JRE 两者间的关联,我们可
以用底下图片来描述:
图 :JDK 1.3.x 与 JRE 1.3.x 的关系
JDK 1.4.x 和 JDK 1.3.x 之间有些许的不同,如下图所示:
图 :JDK 1.4.x 与 JRE 1.4.x 的关系
从上图可以得知,如果您装了 JDK,那麽您的电脑底下一定会有两套 JRE、
一套位于 <jdk 安装目录 >\jre 底下(JDK 1.3.x 与 1.4.x 皆是如此),另外一套位
于 C:\Program File\JavaSoft 底 下 (JDK 1.4.x 则 是 放 在 C:\Program
File\Java 底下)。
注意:如果您安装的是 JDK 1.3.x,那麽您没有选择的馀地,安装程式一定会同时安
装两套 JRE 在您的电脑上。
可是如果是安装 JDK 1.4.x 的话,您可以在安装 JDK 时选择是否安装位于
C:\Program File\Java 目录下的 JRE,但是 <jdk 安装目录 >\jre 底下的这套
JRE 一样必须安装,没有选择的馀地。下图是 JDK 1.4.x 的安装画面,您可以
看到 JRE 是否安装可以由您自己决定,不过它指的 JRE 是位于 C:\Program
File\Java 目录下的 JRE,千万别跟 <jdk 安装目录 >\jre 底下那套 JRE 混淆了。
您的电脑上安装了 JDK 1.4.x 之后,档案系统中的档案分佈如下图:
图 : 安装 JDK 1.4.x 之后,系统中的档案分佈情形 (1)
与
图 : 安装 JDK 1.4.x 之后,系统中的档案分佈情形 (2)
不晓得大家有没有发现,两个目录下所出现的档案和子目录根本一模一样(其实
有很小很小的差别,但是对我们的讨论没有任何影响,因此本文中当作两者没有
任何差别)。
如果您只从网路下载了 JRE 而非 JDK,那麽就只会在 C:\Program
File\JavaSoft 底下(JRE 1.4.x 则是在 C:\Program File\Java 底下)安装唯一
的一套 JRE。
接下来您一定会问,那麽 JRE 是做什麽用的? 笔者用下图描述 JRE 的功能:
图 :JRE 与 PC 的类比
从上图您可以知道,JRE 的地位就像一台 PC 一样,我们撰写好的 Win32
应用程式需要作业系统帮我们执行,同样地,我们所撰写的 Java 程式也必须要
JRE 才能帮我们执行。所以。当您装完 JDK 之后,如果分别在硬碟的不同地方
安装了两套 JRE,那麽您就可以想像您的电脑有两台虚拟的 Java PC,都具有
执行 Java 应用程式的功能。所以 Java 虚拟机器只是 JRE 裡头的其中一个成员
而已,以更技术的角度来说,Java 虚拟机器只是 JRE 裡头的一个动态联结函式
库罢了。
所以我们可以说,只要您的使用者电脑之中安装了 JRE,就可以正确地执行
Java 应用程式(Windows XP 宣称不支援 Java,就是因为微软不再于 Windows
XP 中随机附上自己版本的 JRE,不过,就算微软不附自己的 JRE,Sun 还是提
供可以支援 Windows XP 的 JRE,JDK 1.4.x/JRE 1.4.x 就完全支援 Windows
XP)。
接下来您一定会问,那麽 Sun 为何要让 JDK 的安装程式同时安装两套几乎
完全相同的 JRE 呢? 花费额外的硬碟空间在不同的目录下安装两个一模一样的
东西,颇有浪费之嫌,不是吗? 其实真正的原因是因为 - JDK 裡面也附上了很
多用 Java 所撰写开发工具(例如 javac.exe、jar.exe 等),而且他们都放置在
<jdk 安装目录 >\lib\tools.jar 这个档案之中。
什麽? Java 的编译器 javac.exe 也是用 Java 撰写的,真的吗? javac.exe
明明就是一个执行档,用 Java 撰写的程式应该是.class 档才对。为了证明这件
事情,底下我们做个小实验,证明我所言不假:
首先,请先在命令列模式底下执行底下指令 javac.exe :
然后,请到 <jdk 安装目录 >\lib\ 底下把 tools.jar 改名成 tools1.jar,然后重新
执行 javac.exe,您将看到萤幕输出如下:
這個意思是說,您輸入 javac.exe 和輸入
java -classpath d:\j2sdk1.4.0\lib\tools.jar com.sun.tools.javac.Main
會得到相同的結果。請把 tools1.jar 改回成 tools.jar,然後重新執行指令:
您会看到执行结果和输入 javac.exe 一模一样。从这裡我们可以证明 javac.exe
只是一个包装器(wrapper),而製作目的是为了让开发者免于输入太长的指令。
包装器的概念如下图所示:
图 : 包装器的概念
其实包装器的概念您也可以在很多地方看到,比方说当您用 J2ME 开发 Palm 应
用程式的时候,工具都会帮我们打包 JAR 档,然后用一个 PRC 档的外壳罩住,
让 Java 程式看起来像是一个原生(native)的应用程式。如果您开发.NET 应用程
式,那麽.NET 开发工具所造出的执行档也是一个包装器的概念。不管如何,包
装器的设计是为了让使用者更方便地使用您的应用程式。
除了 javac.exe 之外,JDK 很多内附的开发工具也都是採用包装器的概念。
请试著开启 <jdk 安装目录 >\bin 目录,您将发现底下的执行档大小都很小,不
大于 29 KB:
图 :bin 目录下的执行档
从这裡我们可以推得一个结论,就是: JDK 裡面的工具几乎是用 Java 所撰写的,
所以 JDK 本身就是 Java 应用程式,因此要使用 JDK 附的工具来开发 Java 程
式,也必须要自行附一套 JRE 才行,这就是 <jdk 安装目录 >\jre 底下需要一套
JRE 的原因。而位于 Program File\ 底下的那套 JRE 就是拿来执行我们自己所撰
写的 Java 应用程式。不过,两套中任何一套 JRE 都可以拿来执行我们所撰写的
Java 应用程式,可是 JDK 内附的开发工具在预设使用包装器(.exe)来启动的情
形下,都会自己去选用 <jdk 安装目录 >\jre 底下那套 JRE。
但是问题也就出在这裡,在同一台电脑之中安装如此多的 JRE(如果您还安
装了 Borland JBuilder 或 TogetherJ 这些完全使用 Java 开发的软体,那麽您
的电脑裡头就会有更多套的 JRE)。一台电脑之中许多套 JRE ,再加上作业系统本
身 PATH 环境变数的特性,就会造成最开头所示范的怪异现象 : 同样都是执行
java.exe ,但是执行结果却不相同。
您所执行的是哪一个 java.exe ?
既然您的电脑裡头至少有两套 JRE,那麽谁来决定用哪一套 JRE 呢? 这个重
责大任就落在 java.exe 的身上。
当我们在命令列输入
java XXX
的时候,java.exe 的工作就是找到合适的 JRE 来执行类别档。java.exe 依照底
下逻辑来寻找 JRE:
1. 自己的目录下有没有 JRE 目录。(这个部分这样说并不是非常精确,原因
请详见 JDK 原始码,这此不特别说明)
2. 父目录底下 JRE 子目录。
3. 查 询 Windows
Registry(HKEY_LOCAL_MACHINE\Software\JavaSoft\Java
Runtime Environment\)。
所以,java.exe 的执行结果和您电脑裡面哪一个 java.exe 被执行,然后哪一
套 JRE 被拿来执行 Java 应用程式有莫大的关系。
请您先使用 Windows 的搜寻功能,看看您的电脑裡头到底有多少 java.exe?
笔者的电脑上一经搜寻,找到了好几个 java.exe,如下图:
图 : 电脑裡的 java.exe
所以您可以断定,如果指令为:
您执行到的 java.exe 一定是 d:\j2sdk1.4.0\bin 底下的 java.exe,所以
java.exe 选到的是父目录(d:\j2sdk1.4.0)底下的 JRE(d:\j2sdk1.4.0\jre)。
请开启 d:\j2sdk1.4.0\jre\bin 这个目录,您会看到 client 和 server 两个
目录,裡面都会分别看到 jvm.dll,这就是一般我们所谓的 Java 虚拟机器之所
在。
图 : 真正的 Java 虚拟机器之所在 (hotspot client)
图 : 真正的 Java 虚拟机器之所在 (hotspot server)
( 注 : 如 果 您 设 定 path=d:\jdk1.3.1\bin , 那 麽 就 应 该 开 启
d:\jdk1.3.1\jre\bin,您将看到 classic、hotspot、与 server 三个子目录,裡
头都拥有 jvm.dll)
当 您 输 入 的 指 令 为 java –server 时 , 就 会 使 java.exe 自 动 选 择
d:\j2sdk1.4.0\jre\bin\server 这个目录底下的 JVM;而输入 java –classic
时,就会使 java.exe 自动选择 d:\jdk1.3.1\jre\bin\classic 这个目录底下的
JVM。从这裡我们就可以解释之前为何设定了 path=c:\jdk1.3.1\bin 之后,会
在 画 面 中 突 然 出 现 两 个 之 前 不 曾 出 现 的 选 项 , 也 可 以 说 明 为 何 设 定 了
path=d:\j2sdk1.4.0\bin 之后,java –server 的指令会从原本的错误讯息变成
可以正常使用。
至于为何没有设定 PATH 环境变数前,会出现错误讯息,这是因为系统预设
的 PATH 环境变数内容如下:
所以 java.exe 在 c:\winnt\system32 底下找不到 JRE 目录,在 c:\winnt
也找不到 JRE 目录的情况下,根据下一个逻辑,就是去查询 Windows Registry,
如下图所示:
图 :Windows Registry
因为在 Windows Registry 之中查询到 JRE 所在位置为 C:\Programm
Foles\Java\j2re1.4.0-rc 。开启该目录下的 bin 子目录却只有看到 client 子目
录,却没有看到 server 子目录:
这就是之所以出现错误讯息:
的原因,因为 java.exe 根本无法在 JRE 所在目录下的 bin 子目录之中找到名为
server 的子目录。同样的道理,请您自行验证使用 JDK 1.3.x 时萤幕上执行
java.exe 的结果之成因,您将发现 Registry 之中所设定 1.3.x 版的 JRE 之路径
下的 bin 子目录,一样只有 client 目录。
其实,在 java.exe 找到 JRE 之后,还有一个验证版本的程序,也就是
java.exe 和 JRE 两者的版本要一致才能继续执行。所以假如阴错阳差,您执行
到 1.3.x 版 JDK 附的 java.exe,而此 java.exe 却找到版本号码为 1.4.x 的 JRE,
就会发生底下结果:
上面林林总总解释了那麽多,只想告诉大家,当您在开发 Java 程式或是执
行 Java 程式的时候,一定要记得两件事:
1. 那一个 java.exe 被执行。
2. java.exe 找到哪一套 JRE。
只要这两件事都确定了,就知道问题发生的来龙去脉,也可以很容易地解决很多
貌似灵异的怪问题。
常見的錯誤
前面已经拿 JRE 与 PC 做了个比喻,JRE 对 Java 应用程式来说,就是一台
独立的电脑,而您的电脑上会有好几套不同的 JRE,因此对 Java 应用程式来说,
就是有许多不同的「虚拟电脑」。我们可以想像一种情况:您有两台电脑,一台是
PC,一台是 Notebook,如果您在 Notebook 上安装了 DirectX 函式库,那麽
PC 上仍然无法执行需要 DirectX 的游戏,您一定会在 PC 上重新安装 DirectX。
再举个例子,您在 PC 上设定好作业环境(如日期设定、密码设定、安全设定)之
后,您一定不会期待 Notebook 上也有这些设定,您必须自己动手设定您的
Notebook 才行。这个道理很简单 - 因为这两台电脑根本就是两个不同独立的
个体。
对 Java 应用程式来说,每个 JRE 都是独立不相干的个体。举凡函式库、安
全设定等与特定 JRE 相关联的特性,如果您设定的是在 A 处的 JRE,但是执行
时 Java 应用程式却是在 B 处的 JRE 之中执行,那麽您的设定就会完全没有作
用。
所以,您必须清楚地知道哪一个 java.exe 被执行,以及 java.exe 到底选
用到哪个 JRE,这些都是非常重要的细节。底下我们示范一个一般人几乎不太去
注意的小细节:
学习 Java 的朋友,一定会发现书上会希望您设定 PATH 环境变数,一旦
PATH 环境变数指向 <jdk 安装目录 >\bin\ 底下,您就可以在任何目录下执行
java.exe。如果您使用的是 Windows NT/2000/XP 作业系统,设定方式如下:
如上图所示,上面是设定使用者变数,下面是设定系统变数。通常一般人会设定
使用者变数如下:
注意,假设笔者的 JDK 1.3.1 安装在 D 磁碟机,所以会在使用者变数的最开头加
上 d:\jdk1.3.1\bin 。设定完成之后,就开始了您的 Java 程式设计学习之旅。
不过有一天,您学习的越来越深入时,您的生活会开始和 JRE 产生关联。
比方说您会学习不同的 Java 函式库,函式库的说明文件会希望您把这些函式库
(通常是一堆 JAR 档)放到 <JRE 所在目录 >\lib\ext 底下,通常您会选择放到
<jdk 安装目录 >\jre\lib\ext 底下:
此外,您也有可能会学习 Java 的安全机制,与 Java 相关的安全设定档都放置
于 <JRE 所在目录 >\lib\security 底下,改变这个目录下的档案设定,会可以改
变 JVM 在 安 全 上 的 设 定 , 通 常 您 也 会 选 择 修 改 <jdk 安 装 目 录
>\jre\lib\security 目录底下的档案:
接下来,当您执行 Java 程式时,就会发现您的安全设定根本不会发生作用;而
叫用了其他 Java 函式库的程式,在编译阶段没有问题,可是却无法执行(萤幕
上会显示 ClassNotFoundException)。
一般初学者无法理解问题的发生原因,而学习 Java 已久的老鸟也可能不清
楚这个问题之所以产生的真正原因。之所以会发生此怪异情形,我们必须从命令
提示模式下来看:
就我们之前对环境变数的设定来说,在命令提示模式下看起来如此(系统变数在
前,使用者变数在后),我们前面还曾经要大家去搜寻您的电脑中到底有几个
java.exe(还记得吗? C:\winnt\system32 与 d:\jdk1.3.1\bin 底下都曾出现
java.exe),所以当您编译 Java 应用程式时,由于只有 d:\jdk1.3.1\bin 底下
可以找到 javac.exe,而 javac.exe 又会自动使用 JDK 所在目录下的那一套
JRE,因此很自然您的函式库如果拷贝到 <jdk 安装目录 >\jre\lib\ext 底下,很
自然地在编译时 JVM 会找到函式库,所以编译不会发生任何问题。但是执行时
就有差了,因为 PATH 环境变数的关系,所以您打 java XXX 的时候,会优先执
行 c:\winnt\system32 底下的那个 java.exe,也因此很自然地会去使用
c:\program files 目录下的那一套 JRE,因此要让您的 Java 应用程式可以正常
执行,您还必须将那些函式库拷贝到 c:\program files 目录下的那一套 JRE 的
lib\ext 目录之下。Java 安全设定之所以没有作用,也是基于一样的道理,如果
以上述的情境来说,您就必须修改 c:\program files 目录下的那一套 JRE 的
lib\security 目录中的设定档才行。
如果您是每次都在命令提示模式下手动设定 PATH 环境变数如下:
就不会发生上述问题,因为不管是 javac.exe 或 java.exe,都会执行 JDK 所在
目录下的,也因此找到的都是 JDK 安装目录下的那套 JRE,自然就没有上述问
题。当然,我们可以类推,如果您设定的方式为:
就不会发生奇怪的问题,而如果您设定的方式为
则会发生上述的奇怪问题。
总结
在本文中,笔者为大家介绍 JDK、JRE、以及 JVM 三者之间的关系。依照
这三者的关系,笔者为大家解释了很多学习 Java 时遇到的灵异现象。只要知道
事情的来龙去脉,灵异就不再是灵异,而且我们还可以去玩弄这些运作机制。
在看本章之后,如果要证明您已经了解本章内容,请容许笔者出个小题目帮
您做个测试。还记得底下这个错误讯息吗?
请试著「刻意地」造出此错误讯息。如果您成功了,那麽您已经开始进入玩弄
JDK、JRE、以及 JVM 的层次了。