1. 字节码以及JVM:
1) Java之所以是跨平台的是因为经过编译器编译出来的字节码文件是与平台无关的,而这个字节码文件就用来放在Java虚拟机JVM上跑;
2) 字节码和JVM的关系:就如同C语言编译出来的最终文件.exe之于机器之间的关系,字节码相当于放在机器上跑的二进制文件,而该”机器“就是我们的JVM虚拟机了;
!JVM不是真实的机器,而是人通过软件手段模拟出来的机器,通过编程来模拟机器的各个部件,就如同Linux上的Bochs一样,虚拟机里有人模拟出来的寄存器、指令集、栈等等物理机器所拥有的东西,当然也包含了一些新颖的性能,比如垃圾回收器;
!JVM是和平台有关的,而所有JVM都向Java编译器javac提供完全相同的编程接口,因此具体来讲只有编译器javac才是真正和平台无关;
3) 解释型语言:拿Java字节码在JVM上跑就是”解释“,为什么不说是运行?毕竟JVM不是真实的物理机器,为了和物理机器上跑二进制程序相区别还是叫解释比较好!
2. JDK、JRE和JVM:
1) 我们首先来看一下JRE和JVM之间的关系:JRE是指Java运行时环境,那什么是运行时环境呢?从字面意思上看就是一个程序要运行它所需要的环境了,我们先拿普通的Windows上的程序.exe来一窥究竟,那么我们在Windows上运行一个.exe需要什么东西来支持呢?首先这里”支持“的概念就是所谓的运行环境了,也就是运行.exe所需要的环境了,首先一台机器是肯定少不了的,依次类推要运行Java程序(即Java字节码,就相当于.exe二进制文件)也必须要一台机器,而这个机器必定就是Java虚拟机JVM了,那其次需要什么呢?当然是需要操作系统的支持了,运行程序肯定需要现加载进内存吧(也就是加载器)?肯定要将程序各模块链接起来吧(也就是链接器)?加载器、链接器等当然都是操作系统所提供的功能了!以此类推,在JVM上运行Java字节码同样也需要加载器、链接器等功能,则会当然也必须JRE必修提供的东西,因此JRE=JVM+类似Java操作系统的东西(Java程序链接器、加载器等),这不是跟.exe的运行环境(物理机器+OS)是一样一样的!
!因此JRE包含JVM,而JVM以外的部分就相当于一个”Java OS“,里面提供了类加载器、字节校验器、大量的Java基础类库(就相当于Windows操作系提供的API库一样);
2) JDK和JRE之间的关系:JRE相比是几乎所有要上网的电脑都会安装的组件,因为很多网页上的程序都是用Java开发的,在用户浏览网页时浏览器会自动缓存Java程序并启动内置的JRE去运行它们,因此如果只是为了运行Java程序则只要安装一个JRE就够了,但是如果你还要开发Java程序的话只有JRE肯定是不够的吧!首先你写出来的源程序要编译吧?那么就肯定需要编译器了!编好的程序要调试吧?那调试器肯定也是必要的!当然写出来的程序肯定是要跑跑看的,所以JRE肯定也是必须的,因此JDK(Java开发包)里面肯定需要有开发工具(编译器、调试器等),当然也需要JRE;
!小结:JDK = Java开发工具 + (JRE = 类OS组件 + JVM)
3. JDK安装包:
1) 安装组件:在进入JDK安装程序时会选择安装组件,有Development Tools(包含了JRE和开发命令工具)、Source Code(Java核心类库的源码,相当于Windows的MFC类库源码,在写Java程序时往往需要include进去,当然在Java中是import),还有一个就是Public JRE,即公共JRE;
!在JDK安装好后会有一个单独的JDK安装目录,比如D:\Java\jdk1.8.0_05\,这个里面的JRE是私有的JRE,那这个是谁私有的呢?就是Java软件开发者私有的,这个JRE即使专门用来给开发者调程序的,而在安装JDK时的那个共有JRE就是供电脑上普通用户使用的JRE,这个JRE的目录在JDK安装目录的外面,用来跑一般的Java程序的,比如网页上的Java程序或者本地的Java程序;
2) JDK安装包:
i. bin:存放java、javac等开发工具命令;
ii. db:存放Java DB组件;
iii. include:存放和平台相关的头文件,比如在Java中使用Windows的API等需要的支持;
iv. jre:开发人员私有的JRE;
v. lib:Java核心类库文件,很多bin目录下的命令其实都是简单的调用lib中的库文件;
!其实bin下的很多工具本身都是用Java编写的,比如javac,其本身就是简单地调用了lib\sun\tools\javac\Main.class类而已,bin下大多命令都是简单包装了lib中的文件;
vi. src.zip:Java核心类库源码,即安装JDK时的那个Source Code中的东西;
vii. javafx-src.zip:Java FX核心类库的源码;
!其余都是一些README之类的说明文档;
4. 将Java工具命令导入Path环境变量中:
1) 我们通常安装的一些Windows应用程序都会提供命令行下的相应命令,比如安装VS6.0则可以在命令行中使用cl来编译C++程序,而我们安装JDK也同样想在命令行下使用javac、java等命令,但是不是很幸运,安装JDK时并不会自动将这些命令设置成可以直接在命令行下使用,因此我们必须手动设置;
2) 命令即程序:我们在命令行下使用的命令其实都是一个个应用程序,都放在系统的某个目录中,那么当我们在命令行下使用这些命令时并没有指定这些命令所在的路径,这是为什么呢?那就是这些命令的路径都保存在一个叫做Path的环境变量中了(Linux下是PATH,Windows不区分大小写,Linux对大小写敏感),在使用命令的时候系统就会到Path环境变量中指定的那些目录中去找有没这个命令,如果有就会执行该命令,而Path中路径下的命令就是内部命令,内部命令是指那些不需要指定命令所在路径的命令(因为它们的路径可以根据Path找到),还有一种就是外部命令了,外部命令所在的路径没有在Path中注册过,因此在执行命令时必须加上路径前缀,否则系统根本不知道该命令在哪里;
3) 我们要使用javac、java等工具(执行时不加路径前缀)就必须将它们的bin目录加入到Path中:
!Windows下配置Path:我的电脑右键 -> 属性 -> 高级系统设置 -> 环境变量 -> 在系统环境变量的Path的(双击Path即可)末尾处添加JDK的bin目录的绝对路径,注意!Windows中Path项的分隔符是分号';';
!Linux下配置PATH:cd ~; vim .bash_profile; PATH=.:$PATH:JDK的bin目录的绝对路径,注意!Linux中PATH的分隔符是冒号':';
!注意,Windows是不区分大小写的,Path和PATH无异,而Linux是大小写敏感的,PATH和Path完全不同;
5. 编译运行以及配置CLASSPATH:
1) Java源程序的后缀必须是.java,否则javac命令无法识别(Java是一门规则非常严格的语言);
2) 编译命令:javac -d destDir srcFile,其中-d指定编译输出的字节码存放的路径,srcFile则是源文件的路径;
!注意,javac没有-o选项,Java不允许指定输出文件名,因为Java有严格的输出文件名规则,输出文件名都是自动生成的,因此只需要指定输出文件所在路径即可;
!输出文件(即字节码)必定是以.class作为后缀,文件名和源程序中定义的类名相同,因此如果源文件定义了多个类则必定会生成多个.class字节码文件;
!-d可以省略,省略时默认输出在当前目录中;
3) 运行命令:java Java类名,注意!Java类名不能包含.class后缀,并且不能指定该.class文件所在的路径!这是怎么回事呢?不能包含.class后缀可能勉强理解,但是不指定路径该如何找到该.class文件呢?我们一一解答:
!首先是不能包含.class后缀,这是JVM的一个设计,.class后缀是给操作系统看的,类名是给JVM看的,就如同你在cmd下运行一个程序,可以输入其.exe后缀也可以不输入,不输入则看上去更像是一个程序,其实对于JVM也是如此;
!运行的那个.class文件在哪里找呢?就在Java默认的环境变量CLASSPATH中找咯!因此通常需要将当前路径.假如到CLASSPATH中去,而在安装JDK时CLASSPATH是不会自动设置的,因此还是需要开发者自己设置该环境变量,在Windows下用上面讲过的方法打开”环境变量“选项卡,在系统环境变量中添加一个CLASSPATH,路径肯定要有.,同时呢也需要包含JDK的lib目录中的dt.jar和tools.jar两个包,因为运行.class文件不仅仅需要.class文件本身,还需要链接器、加载器等工具,而这些工具(也是以Java类文件的形式呈现)就包含在dt.jar和tools.jar这两个包里;
4) 更加灵活地指定classpath:如果运行Java程序只能通过环境变量CLASSPATH不就太不灵活了,java指令的另一种用法就是java -classpath dir1;dir2;dir3... Java类名,注意分隔符是分号‘;',这样就可以更加灵活地指定classpath了,这通常在较大的工程里使用(往往需要Makefile文件);
!!能被运行的那个.class文件里面必须包含main函数,这也是Java程序唯一的入口,和C语言的main差不多;
!小谈用户环境变量和系统环境变量:系统环境变量供电脑上的所有用户使用,而用环境变量只供当前用户自己使用,比如前面的讲过的Path,为了不影响其它用户的电脑的使用,应当将JDK相关路径定义在用户的Path和CLASSPATH中(用户变量中这些原本是不存在的,需要自己创建),因为各个用户的选择不同,从事的工作也可能不同,因此可能会有不同的需求,所以最好是各自定义各自的环境变量;
!环境变量检查规则:一定是先检查系统环境变量再检查用户环境变量,就拿之前讲过的Path来说,如果系统变量和用户变量中都包含了java这条命令,则由于系统是先检查系统变量的,检查到系统Path中的java后就不再检查用户Path了,因此不会造成冲突;
!其实这是操作系统的内容,这里只是简单地一提;
6. Java源文件命名规则:
1) 源文件名必须和文件里定义的public类的类名相同,否则编译会报错!(Java变态严格哦!都是为了更安全更高效的进行软件开发,因此Java是一个商业性极高的开发语言);
2) 而一个源文件中最多只能定义一个public类;
3) 在源文件中没有定义任何public类的情况下文件名才可以不和类名相同,但是不建议随意起文件名,最好还是和文件中最主要的那个类的类名相同(最主要的类是指,在该文件这个类调用其它类,作为主调类的身份出现);
4) 为了增强可读性、逻辑性和安全性,建议一个文件只定义一个类,不同的类使用不同的源文件定义;
5) Java是大小写敏感的语言,不管是源文件、文件名等等都是严格的大小写敏感的,在Windows中文件名是大小写不敏感的,但是如果要让Java的命令识别Java文件(.class、.java),则被识别的文件的文件名和后缀等都是大小写敏感的;
7. JVM的垃圾回收机制简介:
1) Java的内存分配和回收由JRE在后台提供的一个线程GC(Garbage Collection,即垃圾回收器)来检测和控制(类似OC的ARC引用计数机制),程序员无法精确控制该过程;
2) 程序中创建的对象实例都保存在Java运行时的堆区,当对象的引用和实例脱钩时实例内存将被标记为”垃圾”被回收,之后可以被分配给其它实例;
3) GC的两个显著优点:首先是能提高编程效率,可以完全无视内存的管理,其次是提高了程序的安全性,降低诸如C\C++的内存泄露风险;
4) GC最严重的缺点:需要为GC单独开辟一个超级线程管理内存的使用,同时GC还负责对内存碎片的整理(需要挪动已经开辟的内存将空闲的碎片内存整合到一起),这些都相当消耗资源,这也是导致Java程序要比编译型语言写出来的程序慢好多的原因;
5) 可以被回收的对象:只有JVM堆内存的空间可以被回收,其它资源比如数据库链接、磁盘I/O等无法回收,仍然需要包装底层的本地代码(如C\C++等)进行回收;
6) 程序员无法精确控制垃圾回收:当一片内存被标记为垃圾时并不会立马被回收并释放,具体的机制取决于不同的JVM的实现(GC算法有很多不同版本),有可能在CPU空闲时执行也可能在内存消耗出现极限时执行,程序员最多只能向JVM提“建议”,比如显式调用System.gc()等函数向JVM建议执行垃圾回收动作,但是JVM具体执不执行将另有决断;