JDK与JRE及JVM之间的关系
部分内容引用自Java 9 揭秘(8. JDK 9重大改变) 以下仅总结及补充原文部分内容,完整内容见原文镜像链接
JDK目录及变化(不包含独立安装的JRE)
Java SE 9及以上与老版本的变化
JDK和JRE已经在Java SE 9中进行了模块化处理。对结构进行了一些修改。 还进行了一些其他更改,以提高性能,安全性和可维护性。 大多数这些变化会影响类库开发人员和IDE开发人员,而不是应用程序开发人员。
结构更改会影响运行时映像中的目录和文件的组织方式,并影响其内容。 在Java SE 9之前,JDK构建系统用于生成两种类型的运行时映像 ——Java运行时环境(JRE)和Java开发工具包(JDK)。 JRE是Java SE平台的完整实现,JDK包含了JRE和开发工具和类库。 可下图显示了Java SE 9之前的JDK安装中的主目录。JDK_HOME是安装JDK的目录。 如果你只安装了JRE,那么你只有在jre目录下的目录。
安装JDK时内部就已经包含了JRE,这个内含的JRE与单独安装的JRE不同,具体区别见
在Java SE 9之前的目录结构
在 Java SE 9之前,JDK中:
- bin目录用于包含命令行开发和调试工具,如javac,jar和javadoc。 它还用于包含Java命令来启动Java应用程序。
- include目录包含在编译本地代码时使用的C/C++头文件。
- lib目录包含JDK工具的几个JAR和其他类型的文件。 它有一个tools.jar文件,其中包含javac编译器的Java类。
- jre\bin目录包含基本命令,如java命令。 在Windows平台上,它包含系统的运行时动态链接库(DLL)。
- jre\lib目录包含用户可编辑的配置文件,如.properties和.policy文件。
- jre\lib\approved目录包含允许使用标准覆盖机制的JAR。 这允许在Java社区进程之外创建的实施标准或独立技术的类和接口的更高版本被并入Java平台。 这些JAR被添加到JVM的引导类路径中,从而覆盖了Java运行时中存在的这些类和接口的任何定义。
- jre\lib\ext目录包含允许扩展机制的JAR。 该机制通过扩展类加载器(该类加载器)加载了该目录中的所有JAR,该引导类加载器是系统类加载器的子进程,它加载所有应用程序类。 通过将JAR放在此目录中,可以扩展Java SE平台。 这些JAR的内容对于在此运行时映像上编译或运行的所有应用程序都可见。
- jre\lib目录包含几个JAR。 rt.jar文件包含运行时的Java类和资源文件。 许多工具依赖于rt.jar文件的位置。
- jre\lib目录包含用于非Windows平台的动态链接本地库。
- jre\lib目录包含几个其他子目录,其中包含运行时文件,如字体和图像。
JDK和JRE的根目录包含多个文件,如COPYRIGHT,LICENSE和README.html。 根目录中的发行文件包含一个描述运行时映像(如Java版本,操作系统版本和体系结构)的键值对。 以下代码显示了JDK 8中的示例版本文件的部分内容:
JAVA_VERSION="1.8.0_66"
OS_NAME="Windows"
OS_VERSION="5.2"
OS_ARCH="amd64"
BUILD_TYPE="commercial"
在Java SE 9之后的目录结构
Java SE 9调整了JDK的目录层次结构,并删除了JDK和JRE之间的区别。 上图显示了Java SE 9中JDK安装的目录。JDK 9中的JRE安装不包含include和jmods目录。
在Java SE 9 的JDK中:
- 没有名为jre的子目录,原jre文件夹里的内容分散到了各处,比如jvm.dll跑到了bin/server中。
- 本人补充:在新版本的目录中未找到client端的JVM,我推测JDK9之后,因jre文件夹取消,JDK已不包含client端,但server端依旧存在。
- bin目录包含所有命令。 在Windows平台上,它继续包含系统的运行时动态链接库。
- conf目录包含用户可编辑的配置文件,例如以前位于jre\lib目录中的.properties和.policy文件。
- include目录包含要在以前编译本地代码时使用的C/C++头文件。 它只存在于JDK中。
- jmods目录包含JMOD格式的平台模块。 创建自定义运行时映像时需要它。 它只存在于JDK中。
- legal 目录包含法律声明。
- lib目录包含非Windows平台上的动态链接本地库。 其子目录和文件不应由开发人员直接编辑或使用。
JDK 9的根目录有如COPYRIGHT和README等文件。 JDK 9中的发行文件包含一个带有MODULES键的新条目,其值为映像中包含的模块列表。 JDK 9映像中的发行文件的部分内容如下所示:
MODULES=java.rmi,jdk.jdi,jdk.policytool
OS_VERSION="5.2"
OS_ARCH="amd64"
OS_NAME="Windows"
JAVA_VERSION="9"
JAVA_FULL_VERSION="9-ea+133"
在列表中只显示了三个模块。 在完整的JDK安装中,此列表将包括所有平台模块。 在自定义运行时映像中,此列表将仅包含你在映像中使用的模块。
Tips JDK中的lib\tools.jar和JRE中的lib\rt.jar已从Java SE 9中删除。这些JAR中可用的类和资源现在以文件中的内部格式存储在lib目录的命名模块中。 可以使用称为jrt的新方案来从运行时映像检索这些类和资源。 依靠这些JAR位置的应用程序将不再工作。
JDK中rt.jar、tools.jar和dt.jar作用
JDK集成的JRE与独立安装的JRE的区别
因JDK9后已不包含JRE,本处仅讨论JDK9之前版本。
JDK中的JRE中的JVM包括client和server端,但是单独的JRE中的JVM只包含client端
本人补充:在新版本的目录中未找到client端的JVM,我推测JDK9之后,因jre文件夹取消,JDK已不包含client端,但server端依旧存在。
JVM的Client端和Server端的区别
以下的三个段落引用自jdk中集成的jre和单独安装的jre有什么区别?
在命令行环境下(模拟用户使用)
使用javac 命令 编译一个 Test.class 输出 hello world 的。(为什么不用考虑javac? 因为javac 不是jre 中的, 不在讨论范围内):
将JAVAHOME\jdk1.8\jre 这个文件夹剪切到其它的地方,命令行输入java Test, 输出正常:
将JAVAHOME\jre1.8 这个文件夹剪切到其它的地方,命令行输入java Test, 输出错误:
说明命令行这种用户使用场景下,JVM是JAVAHOME\jre1.8 里面的虚拟机环境。
那有人可能要问了, 自己明明没有将JAVAHOME\jre1.8 加到环境变量里面去, 为什么会使用这里面的JVM?
我们看到
原因是:在安装jre 的时候,安装程序自动在System32\java (我的电脑里面是C:\Program Files (x86)\Common Files\Oracle\Java\javapath) 路径加到Path 的最上面, 在JAVAHOME\jdk1.8\bin\前面, 而且在以上目录下有java.exe 文件,这个文件应该是一个wrapper, 而目的是为了让使用者免于输入太长的指命。Java.exe的工作就是找到合适的JRE来运行Java程序。 Java.exe依照底下的顺序来查找JRE:自己的目录下有没有JRE;父目录有没有JRE;查询注册表: [HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment] , 查询注册表之后,发现以上键值 是和JAVAHOME\jre1.8\一致的。
说明在用户级别使用java虚拟机去运行jar 等程序时, 默认使用过的是单独安装的jre, 即JAVAHOME\jre1.8
Intellij 使用环境下 (模拟一般IDE 开发使用)
将JAVAHOME\jdk1.8\jre (即jdk集成的jre) 文件夹剪切到其它地方之后,用Intellij 打开之前一切正常的工程,发现
报错 Cannot resolve symbol ‘Stack’
将JAVAHOME\jre1.8 移除, 保留JAVAHOME\jdk1.8\jre 则 IDE 一切正常。、
这说明 想Intellij 这样的开发环境下,对代码的纠错,预运行等需要使用 JAVAHOME\jdk1.8\jre 这样jdk 中集成的jre
综述
根据以上的小实验结果,我们有理由相信:
- JAVAHOME\jre1.8 这样单独安装的jre 是给普通用户,和其它大多数的一般应用程序使用的java运行环境。
- JAVAHOME\jdk1.8\jre 这样集成在jdk 中的jre 是给IDE 开发环境使用的。
环境变量
Path
当我们安装完jdk之后,打开cmd(在非安装目录的路径下)输入javac、java,会提示找不到命令。我们需要将命令所在的路径添加到Path系统变量中,这时,系统就可以找到可执行文件了。
Windows在查找可执行文件是这样的:在终端输入java时,系统就会先在当前目录查找java程序,如果有就会执行java,否则就会在Path中指定的路径中找,因为我们在PATH配置了…\jdk\bin,系统会在这个路径下找到Java程序并执行。否则就提示找不到命令。Path的作用其实就是方便我们使用一些命令。
JAVA_HOME
JAVA_HOME里面的内容是JDK安装目录。其实不设置JAVA_HOME也是可以的,只要有Path就行了。但是设置JAVA_HOME可以方便在Path中使用、更新JDK目录。比如,jdk的安装路径是C:\jdk1.5.0_22,Path设置为C:\jdk1.5.0_22\bin,而当我们把JAVA_HOME设为C:\jdk1.5.0_22时,设置Path就可以写成%JAVA_HOME%\bin,以后当我们使用其他版本的jdk,就可以只修改JAVA_HOME的值,此外,当我们要使用Redis等组件时,也可以灵活使用JAVA_HOME做些改动。
CLASSPATH
编译、运行Java程序时,JRE会去该变量指定的路径中搜索所需的类(.class)文件,很多配置教程都让我们设置CLASSPATH环境变量为:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar,但是其实没有必要。以jdk1.5为例,当我们配置完成后,写个HelloWorld.java。
在cmd中进入文件目录,可以发现依然可以编译、运行
再查看运行的详细信息,可以看到没有CLASSPATH的情况下rt.jar、当前目录也是可以被JVM找到的。
也就是说,在JDK 1.5之后,完全可以不配置这个变量。不配置CLASSPATH,JRE会自动搜索当前路径下的类文件。编译、运行时,系统可以自动加载dt.jar和tools.jar文件中的Java类。
当然,使用JDK 1.5以上的版本也可以在属性配置CLASSPATH环境变量,一旦设置了该变量,JRE就会按照该变量指定的路径搜索Java类(如果CLASSPATH中不包括当前路径.,JRE就不会在当前路径下搜索Java类)。这种方法是一劳永逸的,当我们只是想临时运行某些类,我们就可以将它们临时加到CLASSPATH中(cmd已关闭,就没了)。如下面所示:
返回父目录,毫无疑问父目录没有HelloWorld.class文件,所以运行时找不到主类。这是因为JVM只会在当前目录、dt.jar、tools.jar中找class文件,而找不到当前目录的子目录中的class文件。
解决方法是(1)将当前目录的子目录用set命令临时添加到CLASSPATH中(只在当前窗口生效),这时就可以运行了:
(2)此外想在运行Java程序时临时指定JRE搜索Java类的路径,也可以使用-classpath(-cp)选项,如下面的格式。(dir是要指定的路径名)
java -classpath dir 类名
对于第二种方法,有个注意的点:采用这种方法会先在classpath中查找class文件,即时当前目录有class也有可能找不到,如下所示:新建名为1的一个空文件夹,进入helloworld文件夹,尽管helloworld里面有class文件也没法识别到。
也就是说,使用了-classpath选项后,JRE将严格按-classpath指定的路径来搜索Java类。如果想使CLASSPATH环境变量指定的路径还生效,可以按下面的格式来运行Java程序。
java -classpath %CLASSPATH%;.;dir 类名
小结
1、 path环境变量是先在当前目录找执行程序,如果没有,再到path指定目录中去寻找。而classpath是先在classpath环境变量中去找执行程序,找到了,即使当前目录中有同样的执行程序也执行不到;且只要在classpath的值后面加了英文句号才会当前目录中来寻找执行程序。为了方便,以后尽量用set CLASSPATH,而不要使用-classpath的选项。
2、 在设置临时变量时,如果想保留原有的path值,只需在新设的值后面加上%path%这样的语句就行了。如:set path=新路径;%path%;
3、 在使用javac和java进行编译和执行程序时,如果出现找不到文件的情况,在环境变量设置正确的前提下,只有两种可能:一是目录错误;二是文件名错误。