前言
本篇文章是整个"船头灯"计划的第一篇正式文章,同时也是"Java夕拾"这个站点的第一篇。
想了解什么是"船头灯"计划的,可以点击《船头灯 - 一场关于菜鸡程序员的自救指南》前往查看。
想了解"Java夕拾"这个站点主要讲什么的,可以点击《Java夕拾》前往查看。
本篇文章主要将针对以下内容对Java运行环境进行简述:
Java简述
Java语言与是由美国Sun公司于1995年推出的一门计算机编程语言,其拥有跨平台、面向对象、泛型编程等特点,因此被广泛应用在互联网程序的开发领域,如大型网站构建、大数据挖掘及移动应用开发等方向。
Java是一个完整的平台,有一个庞大的库,其中包含了很多可重用的代码,以及一个提供诸如安全性、跨操作系统的可移植性以及自动垃圾收集等服务的执行环境 – 《Java核心技术 卷Ⅰ》
Java语言发展历史(更新至Java 9)
年份 | 版本 | 新语言特性及说明 |
---|---|---|
1990 | Green(Oak) | Java语言的雏形,目标设定在家用电器等小型系统,主要应用于家用电器的控制和通信。 |
1994 | Java 1.0a | 原先的Oak已被注册,所以改名成Java |
1995 | HotJava | Java语言诞生,在5月23日的SunWorld’95大会上展示了由Java编写的名为HotJava的浏览器,以炫耀Java语言超强的能力 |
1996 | 1.0 | 第一个JDK版本 |
1997 | 1.1 | 增加了JIT(即时编译)编译器,内部类。弥补了Java 1.0中大多明显的缺陷,大大改进了反射能力,并为GUI编程增加了新的事件处理模型 |
1998 | 1.2 | 同时发布的还有JSP/Servlet/EJB等规范,并将Java分成了三个版本:标准版(J2SE)、企业版(J2EE)、微型版(J2ME) |
2000 | 1.3 | 对最初的Java 2版做出了增量式的改进,提供了不断扩展的标准类库,修正了一些bug |
2002 | 1.4 | 增加了断言机制,此时已经可以使用Java实现大多数应用了。在此期间,原先对Java applet和客户端应用的炒作逐渐消退,但Java成为了服务器端应用的首选平台 |
2004 | 5.0(1.5) | 原先叫1.5后改名为Java SE 5.0,J2EE与J2ME也相应地改成了Java EE和Java ME。该版本JDK增加了诸如泛型、foreach、可变参数、注释、自动拆箱、自动装箱、元数据、枚举、静态导入等功能 |
2006 | SE 6(1.6.0) | Java SE 6在内部的版本号依旧是1.6.0,这个版本并没有对语言方面再进行修改,而是进行了性能改进,并增强了类库 |
2011 | SE 7(1.7.0) | 2009年4月,Oracle宣布收购SUN公司,此版本是Oracle发布的第一个JDK版本,引入了二进制整数、支持字符串的switch语句、菱形运算符、多异常捕获、自动关闭资源的try语言等新特性。 |
2014 | SE 8(1.8.0) | 带来了全新的Lambda表达式、函数式编程、可以包含默认方法的接口、日期/时间库等大量新特性,该版本也是目前市场主流版本。但是Java SE 8在内部的版本号依旧是1.8.0。 |
2017 | SE 9(9.0.1) | 强化了Java的模块化系统、采用了更高效、更智能的GI垃圾回收器、添加了JShell。在此版本之前的每个版本都提供了32位即64位系统版本的JDK,但从Java 9开始不再提供32位版本。 |
一些术语说明:
- J2:一个过时的术语,用于描述1998~2006之间的Java版本;
- Java SDK:也是一个过时的术语,用于描述1.2版本~1.4版本;
- SE:用于桌面或简单服务器应用的Java平台;
- EE:用于复杂服务器应用的Java平台;
- ME:用于小型设备的Java平台;
Oracle JDK 与 Open JDK的关系
SUN公司在2006年对Java进行了开源(SUN公司被Oracle收购之前),其开源的JDK便是Open JDK。而Oracle JDK则是Oracle公司在Open JDK的基础上进行的构建,相对比Open JDK,Oracle JDK只是增加了更多的类以及一些错误的修复。二者之间的关系可参考下图所示:
-
Oracle JDK 大概每6个月发一次主要版本,而Open JDK大概每三个月发布一次。但这并不是固定的;
-
Open JDK 是一个参考模型并且是完全开源的,而Oracle JDK 是 Open JDK的一个实现,并不完全开源;
-
Oracle JDK 相比起Open JDK会更稳定。在使用Open JDK有时候会遇到一些应用程序崩溃的问题,而这时只需要切换到Oracle JDK(或者下载对应缺失的包)就可以解决问题;
-
Oracle JDK 与 Open JDK 相比提供了更好的性能;
-
Oracle JDK 使用 BCL/OTN协议获得许可,而Open JDK则是根据GPL v2获得许可;
- GPL(General Public License):通用公共许可证,由自由软件基金会发行的用于计算机软件的协议证书;
- BCL 协议(Oracle Binary Code License Agreement): 可以使用JDK(支持商用),但是不能进行修改;
- OTN 协议(Oracle Technology Network License Agreement): 11 及之后新发布的JDK用的都是这个协议,可以个人使用,商用需要付费;
Java语言的特性
-
简单性:Java语言继承了C++的优点,但是又没有C++中诸如:头文件、指针运算(甚至指针语法)、结构、联合、操作符重载、虚基类等概念,可以说Java语言是C++语言的一个"纯净"版本;而从另一个角度来讲,"简单"也意味着小,Java提供了一个独立的具有较小类库的Java微型版(Java Micro Edition),这个版本支持开发能运行在小型机器上独立运行的软件,比如嵌入式设备;
-
面向对象:Java的面向对象特性与C++相当,主要的不同点在于Java中使用了接口"替代"了C++的多重继承机制(有关面向对象相关的内容会在后面专门出一篇面向对象编程相关的文章);
-
平台无关性(可移植性):Java规范中没有"依赖具体实现"的地方,例如基本数据类型的大小以及有关运算的行为都有明确的说明(对于C/C++来说,int可能是16位整数,也可能是32位整数,而Java中int永远是32位整数)。Java的平台无关性主要是JVM的功劳,JVM针对不同系统都有其对应的实现,我们只需要将编译后的字节码文件交给JVM,JVM会根据不同系统的实现翻译成对应系统具体执行的机器码;
- 虽然Java绝大多数库都能支持很好地支持平台无关性,但对于用户界面相关部分的类库虽然经过了多次重写,依旧还是不能很好地进行跨平台移植;
-
可靠性(安全性):Java舍弃了C++的指针对存储器地址的直接操作,程序运行时,只能通过类的实例操作相应的内存空间,这样可以避免病毒通过指针侵入系统进行修改(指针可以随便指向一个内存区域,而不管这个区域是否可用),也避免了指针操作中易产生的错误。同时,Java对程序提供了安全管理器,防止程序的非法访问;
- 早期的Java对于远程代码是全盘接收的态度,Java将不可信代码在一个沙箱环境中执行。但在遭遇多次高调攻击之后,Java浏览器插件不再信任远程代码,除非代码有数字签名并且用户同意执行这个代码;
-
多线程:Java是第一个支持并发程序设计的主流语言(C++语言在11版本之前没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,11版本开始C++才引入了多线程库);
-
支持方便的网络编程:Java语言诞生本身就是为简化网络编程而设计的,Java提供了一种在网页中运行的Java程序applet。要使用applet只需要一个启用Java的Web浏览器,而不需要安装任何软件,任何时候只要访问包含applet的网页,都会得到程序的最新版本;
- 在发明applet的时代只有JavaScript而没有HTML,但因各种原因,现如今applet基本已被弃用;
- 编译与解释并存:Java编译程序生成字节码而不是通用的机器码,再根据解释器对生成的字节码文件解释为适用于不同操作系统的机器码。同时,Java还提供了即时编译器(JIT)可以监控高频代码,并优化这些代码已提高速度,比如消除函数调用(内联);
- 有关内联相关内容会在后期方法&流程结构相关文章中进行说明;
Java内存空间
- 方法区(Method Area):存储.class相关信息,包含方法的信息(方法区中只是存储方法的定义信息,方法的运行还是在栈中)。方法区中还有一块专门的静态区用于存放静态内容;
- 栈(Stack):方法实际运行的地方。在方法运行时(方法进栈),会在栈内存中开辟出一块专属的内存空间。这块内存空间中存放的都是方法中的局部变量(全局变量不属于方法,而是属于类对象)。而当方法运行结束后(方法出栈),这块栈空间中所有的资源都会被回收;
- 堆(Heap):用于存储对象的一块内存空间(通俗来讲,凡是使用new出来的东西,都在堆当中),堆中还有一块专门的字符串常量池用于存放双引号包裹起来(new 出来的字符串不在常量池中)的字符串对象;
- 本地方法栈(Native Method Stack):与操作系统相关,用于处理本地方法调用相关的操作;
- 寄存器(pc Register):与CPU相关,用于指向实际执行的方法或方法中的方法体;
受限于当前能力的制约,有关Java内存空间更多内容,等后续学习的深入会再专门写一篇进行这方面的研究
JVM、JRE与JDK的关系与区别
-
JVM(Java Virtual Machine):即Java虚拟机,是运行Java字节码的虚拟机。JVM针对不同系统都有其对应的实现,目的是使相同的字节码文件在不同的系统上都会有相同的表现。
-
JRE(Java Runtime Environment):Java运行环境,其包含了JVM和运行时所需要的一些核心类库、java命令以及其他一些基础构件,但它只能用于运行Java程序,无法创建或者编译新程序。
-
JDK(Java Development Kit):Java开发工具包,其包含了JRE以及供开发人员使用的工具(如javac、javadoc等),用于创建和编译新程序。
Java的编译执行过程
高级编程语言按照程序的执行方式分为编译型和解释性两种。简单来说,编译型语言是指编译器针对特定的操作系统直接将源代码翻译成该平台支持的机器码;而解释型语言是指在执行时由解释器将源代码逐行解释成特定平台的机器码并执行。
而Java程序是编译与解释并存的语言,一个Java程序从源代码到运行一般要经历如下步骤:
-
编写.java的源程序文件;
-
使用 javac程序(Java编译器)将源程序文件编译生成.class的字节码文件;
# 假设HelloWorld.java就是我们编写的源程序 # 该指令执行成功后默认会在HelloWorld的同级目录下生成一个HelloWorld.class的文件 javac D:\HelloWorld.java
-
使用java程序启动JVM对.class文件中的字节码进行解释,得到JVM对应平台机器可执行的二进制机器码;
# 该指令执行成功后便会执行HelloWorld中编写的main方法 java D:\HelloWorld
-
操作系统执行二进制机器码。
这里需要特别注意的是将.class文件转换成机器码这一步。在这一步,JVM中的类加载器会先加载字节码文件,然后通过解释器逐行进行解释执行,这种方式的执行速度相对较慢。因此,引进了JIT即时编译器,当JIT编译器对一段代码进行第一次编译后,会将其解释后的机器码保存下来供下次直接使用。
HotSpot采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码,即热点代码),而这也正是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就会越快。而在JDK 9 中又引入了一种新的编译模式AOT(Ahead Of Time Compilation),它是直接将字节码编译成机器码,这样就避免了JIT预热等各方面的开销。但是,AOT编译器的编译质量是肯定比不上JIT编译器的。
环境变量配置
在Windows或Linux上安装JDK时,还需要进行环境变量的配置:将JDK下的bin目录添加到可执行路径中。而对于某些软件可能还需要配置其它的一些参数,如下所示:
-
JAVA_HOME:指向的是JDK的安装路径(Tomcat等软件会通过搜索JAVA_HOME变量来找到并使用安装好的JDK),在该路径下你应该能找到bin、lib等目录;
-
JRE_HOME:指向的是JRE的安装路径。若只需要运行Java程序而不需要进行开发则可以只配置该目录来替代JAVA_HOME的使用,若已配置JAVA_HOME可不配置该变量;
-
CLASSPATH:指定类搜索路径,该地址一般配置为JAVA_HOME下的lib目录及lib内的dt.jar、tools.jar文件。编译、运行Java程序时会去该变量指定的路径中搜索所需的类(.class)文件;
-
PATH:指向的是java、javac等指令所在的路径,一般为JAVA_HOME下的bin目录;
Windows下配置示例
-
右键我的电脑选择“属性”或从控制面板点击“系统”进入属性配置;
-
在属性页面点击左侧栏的“高级系统设置”打开系统属性弹窗的“高级”标签页;
-
标签页中点击“环境变量”选项进入环境变量设置页面;
-
环境变量设置中上面部分为当前登录用户的环境变量,下面部分为全局环境变量(对所有用户起效);
-
对环境变量中分别配置如下属性(仅做示例,具体路径需根据实际情况修改):
JAVA_HOME:你电脑上JDK的安装目录,如D:\JDK\jdk-1.8.0_41-b04
CLASSPATH:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
PATH:;%JAVA_HOME%\bin;(若之前已有该环境变量,只需要在结尾进行添加,切不可进行覆盖替换)
Linux下配置示例
-
用vi或vim打开/etc/profile文件;
-
在profile文件最底下添加如下信息(仅做示例,具体路径需根据实际情况修改):
#jdk1.8 start
export JAVA_HOME=/app/java/jdk-1.8.0_41-b04
export CLASSPATH=.: ${JAVA_HOME}/lib:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar
export PATH=${JAVA_HOME}/bin:$PATH
#jdk1.8 end
Java中的注释
与大多数程序设计语言一样,当我们想要对源代码添加一些备注信息,但又不想执行这些备注信息时就可以使用注释功能。Java给我们提供了三种标记注释的方法:
-
单行注释:在Java中可以使用"//"对该行中自此位置开始之后的所有内容标记为注释
// 这是一行注释 int a = 1; // 这也是一行注释
-
多行注释:有时我们会需要更长的注释,这时就可以使用"/*“和”*/"注释界定符将注释的内容包含起来。注释的范围为自"/*“开始直至”*/"结束。
/* 这是注释 */ /* 这里 都是注释 */ /* *这也 *都是注释 */ int b = 2; /* 这也是注释 */ int c = /* 虽然不推荐这种写法,但这也还是注释 */ 3;
-
文档注释:Java中还可以使用"/**“和”*/"来对文档、类、乃至公共字段(通常是静态常量)进行注释,使用该方法注释的内容在可以使用javadoc程序生成一个HTML文档。
-
每个/**…*/文档注释包含标记以及之后紧跟着的自由格式文本。
-
标记以@开始,如:@since标记会建立一个"始于"条目;@author标记将建立一个作者条目;@version标记将建立一个版本条目;@see或@link标记可以链接到javadoc文档的相关部分或外部文档;
-
自由格式文本的第一句建议是一个概要性的句子,javadoc工具会自动地将这些句子抽取出来生成概要页;
-
类注释必须放在import语句之后,类定义之前;
-
每一个方法注释必须放在所描述的方法之前;
-
字段注释需要放在每一个需要被注释的字段上方;
import java.io.*; /** *文档注释示例类 * @author Norton * @since 1.8.1 * @version 1.0.0 */ public class DocDemo { /** * 示例常量 */ public static final int CONST = 1; /** * 示例方法,两个整数求和 * @param a 整数a * @param b 整数b * @return 计算得到的和 */ public int sum(int a, int b) { return a+b; } }
-
更多关于javadoc的操作可以查看官方文档
-
JShell
JShell是Java 9开始引入的一个用于便利地测试Java语句执行的工具,使用JShell可以省去类文件及方法的创建,直接运行语句。
JShell程序提供了一个"读取-计算-打印循环"(Read-Evaluate-Print Loop,REPL)-- 《Java核心技术 卷Ⅰ》
-
要启动JShell首先需要安装JDK 9或以上版本,之后只需要在终端窗口(CMD)中输入jshell指令:
C:\users\xxx>jshell | 欢迎使用JShell -- 版本17.0.1 | 要大致了解该版本,请键入: /help intro jshell>
-
之后在光标处输入想要执行测试的语句:
jshell> int a = 1; a ==> 1 jshell> 2 $2 ==> 2 jshell> a + $2 $3 ==> 3 jshell> System.out.println("sum = " + $3); sum = 3
- 上述的语句中,我们输入了int a = 1;输出了 a ==> 1;
- 随后我们又输入了一个2,但是并没有定义一个变量来接收这个值。这时jshell输出了$2 ==> 2,该行相当于jshell定义了一个名为$2的变量用于接收我们输入的2;
- 我们再将变量a与$2进行相加,同样没有定义变量来接收这个值。但是jshell依旧为我们创建了一个变量$3来接收这个值;
- 最后我们使用打印语句对变量$3进行了输出。
-
当我们忘记某个Java库中的类或对象可以调用哪些方法或参数时,可以按下键盘下的Tab键,JShell会自动为我们提示可调用的方法或自动补全
jshell> Math. E IEEEremainder( PI abs( absExact( acos( addExact( asin( atan( atan2( cbrt( ceil( class copySign( cos( cosh( decrementExact( exp( expm1( floor( floorDiv( floorMod( fma( getExponent( hypot( incrementExact( log( log10( log1p( max( min( multiplyExact( multiplyFull( multiplyHigh( negateExact( nextAfter( nextDown( nextUp( pow( random() rint( round( scalb( signum( sin( sinh( sqrt( subtractExact( tan( tanh( toDegrees( toIntExact( toRadians( ulp( jshell> Math.l log( log10( log1p( jshell> Math.log
- 上述语句中我们首先输入了Math. 然后按下了Tab键,这时JShell显示了所有Math类下的静态参数及静态方法
- 随后我们输入了Math.l再次按下了Tab键,这时JShell再次为我们提示了Math类下首字母为l的静态方法,并且自动将我们的输入补全成Math.log
-
如果执行完测试语句想要退出JShell,只需要键入/exit便可退出JShell
jshell> /exit | 再见