Sun Microsystems公司于1995年5月推出的Java程序设计语言和Java开发平台。Java是一种面向对
象的编程语言,它的前身是詹姆斯·高斯林(James Gosling,人称java之父)等人于1990年代初开
发的一种编程语言,最初被命名为Oak。
1998年12月4日,Sun公司在发布的JDK1.2版本中,将Java技术体系拆分为3个方向(平台):
1. J2SE (Java 2 Platform, Standard Edition),面向桌面应用开发
2. J2EE (Java 2 Platform, Enterprise Edition),面向企业级开发
3. J2ME (Java 2 Platform, Micro Edition),面向手机等移动终端开发
注意,JDK中会自带一个JRE的,也可以专门独立的只安装JRE,但开发人员一定是安装JDK
SDK (software development kit),软件开发包,主要包含函数库或者工具等
JDK (java development kit),java程序开发工具包,面向java程序的开发者
JRE (java runtime enviroment),java程序运行环境,面向java程序的使用者
API (application program interface),应用程序编程接口
API Documentation,API说明文档,描述API中的类、方法等使用的方式
注意,JDK中会自带一个JRE的,也可以专门独立的只安装JRE,但开发人员一定是安装JDK
Linux中安装,把下载好的压缩包,直接解压并解归档即可。
例如,解压到当前目录下,也可以解压到指定目录中:
Windows中安装,直接双击安装包,一直下一把即可,默认安装位置:C:\Program Files\Java ,会在此目录中自动创建JDK的目录,并且目录的名字中携带JDK的版本号。
注意,安装过程中也会默认安装一个外部的jre,目录名字为jre1.8.0_74
注意,JDK按照目录里面,也自带了一个默认的内部的JRE,点击JDK目录后即可看到此JRE的目录
注意,电脑中可以按照多个不同版本的JDK,不会影响的,之后使用的时候,指定具体的版本即
可。
需要配置三个环境变量,JAVA_HOME、PATH、CLASSPATH,其中PATH是必须,否找不到java的相
关命令所在的文件路径
以Windows系统为例说明:
JAVA_HOME,指定JDK的安装目录,例如,JAVA_HOME=C:\Program Files\Java\jdk1.8.0_74
PATH,把JDK中java命令所在目录配置到原有的PATH中,可以配置到PATH的最前面。例如,
PATH=%JAVA_HOME%\bin;...;...;....; 这里也可以不引用JAVA_HOME的变量值。
CLASSPATH,指定将来要运行加载的class文件所在位置,这个路径将来可能随时变换,可以先配置为当
前路径,将来使用的时候再具体配置即可。例如,CLASSPATH=.
注意,在Windows中,可以使用where命令,查看java名字所在位置 例如,where java
配置完成之后,在命令窗口中,使用命令进行查看:
java -version
注意,如果是Windows系统,配置好了之后,需要重新打开一个新的命令窗口进行测试
注意,如果是Linux系统,配置好了之后,可以先source命令让.bashrc文件生效,或者重写打开新
的命令窗口
oracle官方文档中提供的JDK结构图:
这里以Windows中安装后的目录为例说明:
关于src.zip文件:
我们将来在代码中,所调用的JavaSE-api中的代码,大多数的源码就存放在这个压缩包中。
这里面都是java文件,这些java文件编译后生成的class文件,都存放在一个jar中,C:\Program
Files\Java\jdk1.8.0_74\jre\lib\rt.jar
我们的代码在运行的之前,JVM会先从这个rt.jar中加载一些我们需要使用的类,例如String、
Object、System等
sun公司程序员--编写基础的代码--》*.java --压缩-- 》src.zip --编译--》 *.class --归档--》rt.jar
一定要记得src.zip和rt.jar中的内容是什么,和它们俩者之间的关系。
将来在我们编写的代码中,要使用/调用JavaSE-API中所提供的代码,它的源代码存放在src.zip
中,编译后的字节码存放在rt.jar中,这些类、接口的介绍和使用说明,都在JavaSE-API说明文档
中,它就像是一本说明书,来指导我们该如何调用这些别人提供给我们的基本代码。
我们平时所说的“java”,其实是一个综合的描述,它包含了一系列的很多东西。
它是一门编程语言
包含基本的语法、特性、思想等
它是开发环境
开发java程序时,需要一些的java开发工具
它是应用环境
开发好的java程序要运行,需要它提供一些的运行环境
它是部署环境
将来在其他平台下开发出的java程序,都需要它提供一个最基础的部署环境
也就是说,只有是进行和java相关的一系列相关活动,就必须要先安装一个JDK,以保证这些最基
础的环境、工具、语言支持等。
java语言的优点:
更纯粹的面向对象编程,加速开发的过程。
一次编写/编译,到处运行,代码可以跨平台
多线程的支持
代码中没有指针管理、内存管理,使得编程人员可以更加专注于系统的业务功能的开发
java 内存管理
java:面向对象编程语言
手动开辟内存 new
不需要关心内存的释放 GC垃圾回收器
C:面向过程编程语言
malloc() :开辟空间 free() :释放空间
C++(半面向对象编程语言)
new 开辟内存空间,delete():释放空间
注意:说Java是纯面向对象编程语言是错误的,因为它还保留int,double了等基本数据类型
GC垃圾回收器
垃圾:无用的对象内存,没有任何指向的内存。
GC是一个优先级很低的线程实现回收
gc什么时候回收
内存不够用
系统闲置的时候
GC回收算法(知道就行)
标记清扫法
不会整理内存
内存搬移法
会整理内存,但是耗时
字节码的验证机制,保证代码的安全性
开源及强大的生态环境,社区活跃,第三方类库选择丰富
JVM是Java Virtual Machine(Java虚拟机)的缩写,它是一个虚构出来的计算机规范结构,是通过
在实际的计算机上仿真模拟各种计算机功能来实现的。
JVM是java中最核心的一个东西,它在计算机的内存中,虚拟并提供了java代码可以在其中运行的基础环境。
在JDK7中对JVM规范所给出的内存管理结构如下:
和我们程序中关系比较紧密的是堆区、栈区还有方法区,后面我们会经常提及这个几个内存区域
我们将来编写好的java代码,编译成了class文件,这些class文件就需要加载到JVM中,再进行运行
JVM就是java代码和计算机之间的一个桥梁:
java代码编译后,计算机并不能直接运行,必须需要经过JVM进行解释后,再进行运行。
所以,java其实并不算是真正的编译语言。
注意,JVM本质上是一个规范,每个公司都可以按照这个规范实现自己的JVM虚拟机
我们现在默认使用的JVM就是oracle公司提供的一个实现,这个JVM叫做HotSpot,从上面的历史介绍
中,也可以找到这个HotSpot虚拟机的来历。
当使用java命令来运行程序的时候,会先启动JVM,这个JVM在JDK中对应了一个ddl或so文件:
如果是Windows系统下,这个文件在:%JAVA_HOME%\jre\bin\server\jvm.dll
如果是Linux系统下, 这个文件在:$JAVA_HOME/jre/lib/amd64/server/libjvm.so
在java语言中,编程人员不需要在代码中控制内存的开辟和释放
java代码中,开辟要使用的内存空间,使用new关键字即可完成。
使用完之后,对内存的释放,在JVM中,由垃圾回收器(Garbage Collection,GC)来完成。
不同类型的GC,在JVM中,会根据不同的算法,对不同的内存区域内标记为垃圾的空间,进行回收释
放。在这个过程中,是不需要编程人员干预的,它自己会主动的完成。
在代码中,我们也可以调用JavaSE-API提供的方法,通知GC现在去进行垃圾回收的工作:
java.lang.System.gc();
java.lang.Runtime.gc();
注意,虽然可以主动通知,但是最后GC并不一定会真的的立刻执行,因为这个垃圾回收的过程什
么时候执行,最终还是要根据GC的具体算法和当前内存的使用情况来确定的
编写好的java代码,编译成class文件(字节码)后,再被JVM加载到内存中的时候,是需要做字节
码验证的
编写并编译好的java代码,加载到JVM内存中:******这张图很重要
加载到内存的途径有很多,这里描述的是通过网络,也可以是通过本地磁盘
一个class文件被加载到JVM内存之后,首先要经过字节码验证,主要包含:
检查当前class文件的版本和JVM的版本是否兼容
检查当前代码是会破坏系统的完整性
检查当前代码是否有栈溢出的情况
检查当前代码中的参数类型是否正确
检查当前代码中的类型转换操作是否正确
验证通过过,再确定哪些代码是解释执行的,哪些代码是JIT即时编译执行的:
解释执行
class文件内容,需要让JVM进行解释,解释成计算机可以执行的代码。整体效果就是JVM解释一行
代码就执行一行代码。所以如果java代码全是这样的运行方式的话,效率会稍低一些。
JIT(Just In Time)即时编译
执行代码的另一种方式,JVM可以把java中的 热点代码 直接编译成计算机可以运行的代码,这样
接下来再调用这个热点代码的时候,就可以直接使用编译好的代码让计算机直接运行,可以提高运
行效率。
编译Hello.java中,编写的代码:
编译成功后,生成对应的class文件,也就是字节码文件,其实就是0101代码(二进制)
运行Hello.class文件中,编译生成的字节码:
字节码,是二进制的0101代码,但是计算机不能直接运行,需要JVM进行解释后再执行
java命令,会先启动JVM虚拟机,然后再进行加载、验证、解释/JIT、运行等一系列的过程
JVM要求的运行规则是,java命令后面加上要运行的类的名字即可,这个类的字节码一定是在class
文件中的,同时要求这个类中,需要有一个程序入口,否则运行失败。
javac
编译命令
java
运行命令
javadoc
生成API文档命令
javap
反解析命令,可以解析出class字节码文件的内容
jar
打包命令
在程序中,要区分一些东西,一般会采用【命名空间】的设计方式
在java中,package其实就是类的命名空间,用来唯一标识这个类的,同时也把类似功能的类组织
到一个包中
JavaSE-API中常用的包有:
java.lang
最常用的一个包,里面的类可以在我们的程序中直接使用,不需要import导入
java.awt、javax.swing、java.awt.event
这三个包属于同一类型的,它们包下面的类都是用来做图形化界面的(GUI)
注意:javax开头的包,是sun公司后面又扩展进来的包,刚开始是没有的
java.io
这个包下的类主要用于输入输出流的操作。也就是读数据/写数据
java.net
这个包下的类主要用于网络编程。例如把计算机A和计算机B之间建立网络连接,然后进行信息传
输。
java.util
这个包下的类都是一些常用工具类,可以帮我们在代码中完成一些辅助的功能,例如表示日期、使
用集合存储数据、使用工具类操作数组等
Hello.java
public class Hello{
public static void main(String[] args){
System.out.println("hello world");
}
}
以上面代码为例,展开以下问题的讨论:
问题一:
Hello是我们自己定义的一个类,为什么写java代码的时候要写一个类?
因为在java中,类是组织代码的基本的单元,在类中,可以编写方法,在方法里面,才是真正需要JVM去
执行的一行行的代码,我们平时所说的代码执行,就是这的这些方法中的代码。
java语言中规定,在大多数情况下,需要把执行的代码写在方法中,方法写到类中,所以在写代码的时
候,一般都是先定义一个类,类中定义一个方法,然后把要执行的代码写到方法中。
那么这里所说的类中和方法中,具体的表现形式,就是一对花括号{},如果这对花括号是属于类的,那
么括号里面就表示类中,如果这对花括号属于方法的,那么括号里面就表示方法中。
问题二:
Hello类中的方法,为什么名字要叫做main?
main方法是java中一个特殊的方法,它是作为java程序的唯一入口而存在的。
例如,一个项目中写了好几个百个类,每个类中的方法加起来一共又有上千个方法,那么当JVM加载了
这么多个类和方法的时候,JVM应该从什么地方开始运行程序呢?
java中就使用了一个固定的方法来作为程序的入口,也就是无论写了多少个类,多少个方法,JVM一定是
从这个固定的程序入口方法开始执行代码的,为了能够让JVM很好的识别这个入口方法,这个方法的编
写形式就是固定的:
//这是java中的程序入口方法,一切代码的执行,都要从这里开始
public static void main(String[] args){
//方法中,就可以编写一行行需要运行的代码了
}
所以,我们在Hello类中定义一个main方法的目的就是,让JVM运行Hello这个类的时候,可以直接从类中
的程序入口方法,也就是main开始运行代码,又由于main方法是固定的写法,所以JVM很容易就识别出
来,然后去运行它,main方法被运行了,那么main中的代码就会被一行行的顺序执行了。
思考,如果一个类中没有编写固定形式的main方法,那么使用java命令去运行这个类,会发生什
么?为什么?在这情况下,如果还想使用这个Hello类,该怎么办?
1. 如果Hello中没有固定格式的main方法,使用 java Hello 运行的时候,会报错
2. 原因是因为JVM在Hello类中找不到指定的程序入口,也就是main方法
3. 这个情况下, 还要使用Hello这个的话,就需要在定义其他的一个类,例如Test类,在Test类定义程
序入口main,并且在main方法中调用Hello类中的属性或其他方法,这时候就使用到了Hello类中的
代码了
问题三:
System.out.println("hello world"),这句代码具体是什么意思?
System是JavaSE-API中所提供的一个类,
out是System类中的一个属性,
println是out中的一个方法,
所以,System.out是访问System类中的一个属性,System.out.println是调用out中的一个方法,
而println("hello world")方法的作用,是将字符串"hello world"输出到命令行或控制台中。
可以在API说明文档中,找到这些相关的说明
问题四:
在Hello中,我们为什么可以使用System这个类?
在自己的类中,想使用别人/自己提前写的另一个类,需要以下要求:
这里就以Hello类中使用System类的代码为例说明:
//这是java中的程序入口方法,一切代码的执行,都要从这里开始
public static void main(String[] args){
//方法中,就可以编写一行行需要运行的代码了
}
1 2 3 4 5 6
1. System这个类所在的.java文件已经编写完成,并且已经编译成.class文件
System.java文件在src.zip中,编译好的System.class文件在rt.jar中
2. 这个.class文件所在位置,是JVM可以自动加载的指定路径,这样就可以保证JVM可以把这个.class
文件中的字节码(也就是System这个类的代码),加载到JVM的内存中
JVM在启动的时候,会自动读取rt.jar中所需要的class文件,当然也包括System.class文件
3. 在Hello中,使用import(导入)关键字,将要使用的System类,引入到Hello中,如果System类是
在java.lang包中进行定义的,或者System类和Hello类是在同一个包下面定义的,那么import语句
可以省去不写
当前Hello类中,是没有声明package(包)的,那么这时候Hello类就算是在默认包中,而System
类,是在java.lang包下定义的,所以当前情况下, Hello类中不使用import导入,也可以直接使用
System类。
问题五:
使用java Hello运行这个类的时候,JVM是通过什么找到Hello.class文件的?
通过CLASSPATH环境变量配置的路径,来查找的Hello.class文件的,如果配置的路径不对,那么运行Hello
的时候就会报错,告诉我们找不到这个类。
注意,我们之前配置JDK的三个环境变量的时候,把CLASSPATH配置的路径就是点(.),表示当前
路径
问题六:
能否看到JVM去启动运行Hello类之前,确实从rt.jar中加载到了System这个类?
可以,只需要在运行Hello类的时候,加一个参数即可:
java -verbose Hello
verbose参数可以将JVM启动运行的时候加载的信息输出出来,由于内容太多,这里可以做一个输出重定
向:
java -verbose Hello > a.txt
注意,这个操作在Windows里面也是一样的
通过文件中记录的输出内容,就可以看到JVM在运行Hello这个类之前,整个加载的过程和顺序。
注意,这里JVM其实就是给我们自己编写的类Hello,准备运行的环境。
rt.jar中rt就是runtime的缩写,表示运行时环境的意思。
问题六:
Hello这个类的名字和Hello.java的名字有什么必然关系么?
1. 如果文件中的类是public关键字修饰的,那么这个类的名字和java文件的名字就一定要一样
2. 如果文件中的类不是public关键字修饰的,那么这个类的名字和java文件的名字不一样也可以
3. 类名和java文件名字的首字母大写,这是编程规范,大家都是默认遵循这个要求,其实这个字母小
写也没有任何问题
所以,Hello这个类不一定在Hello.java中,但是成功后,Hello这个类一定在Hello.class中
在java中,定义包的关键字是package
在程序中,要区分一些东西,一般会采用【命名空间】的设计方式,这是大多数语言都会采用的方式。
在java中,如果来区分俩个名字一样的类?例如,张三定义了一个类Hello,李四定义了一个类Hello,当
把张三和李四的代码合并一起的时候,会出现俩个都叫Hello的类,那么这个时候该如果区分这个类?
可以使用package(包)来进行区分,例如张三定义的Hello这个类可以放在zhangsan这个名字的包下,
李四定义的Hello这个类可以放在lisi这个包下,如下:
package zhangsan;
public class Hello{
}
package lisi;
public class Hello{
}
注意,在程序中, package zhangsan; 对应的是一个名字叫zhangsan的文件夹,而 package lisi; 对
应的是一个名字叫lisi的文件夹,这样俩个代码合并在一起,也完全可以区分开名字相同的俩个Hello类。
但是一般程序中,定义包的时候,不会直接用zhangsan、lisi这样的名字,而是都会遵从一些包的命名规
则的:
1. package其实就是类的命名空间,用来唯一标识这个类的,避免和的类的名字重复
2. 一般情况,一个公司、组织、社团中所定义的包的名字,就是他们官网的域名(倒过来),因为
域名一定是全球唯一的,不可能有俩个一样的域名。
例如,http://commons.apache.org/ 这官网下的代码中的包,都是 package
org.apache.commons; 开头的。
例如,你个人写的代码,可以是以 package com.jim; 开头的,假设你的名字叫 jim
3. 类加上了包名,编译之后的效果
这样的类,在编译之后,都必须要有和包名对应的文件夹。
例如, package com.briup.demo;
package zhangsan;
public class Hello{
}
1 2 3 4
package lisi;
public class Hello{
}
1 2 3 4
这里是三个包,包和包之间用点(.)隔开,编译完之后,需要有对应的三个文件夹分别是
com/briup/demo ,最后在demo目录中,才有编译生产的class文件
一个指定package的类,编译后该如何运行?
例如:
package com.briup.test;
public class Hello{
public static void main(String[] args){
System.out.println("hello world");
}
}
运行一个类的时候,JVM加载这个类的规则是什么?
1. 如果运行的Hello类,没有指定包,Hello类一定对应的是Hello.class ( 固定要求)
那么当运行 java Hello 的时候,JVM会从CLASSPATH中指定的路径中查找,是否有Hello.class这
个文件,如果有那么就加载到内存,然后运行,如果没有那么就报错。
这个情况下,CLASSPATH中就要配置Hello.class文件所在的路径
2. 如果运行的Hello类,指定了包,例如是package com.briup.test; Hello类一定对应的是
com/briup/test/Hello.class(固定要求)
那么当运行 java com.briup.test.Hello 的时候,JVM会从CLASSPATH中指定的路径中查找,
是否有com/briup/test/Hello.class这个文件,如果有那么就加载到内存,然后运行,如果没有那么
就报错。
注意,这个时候JVM从CLASSPATH的路径中,会先找com这个文件夹,然后依次找下去。因为如果
有包存在的时候,这个包就是这个类不可分割的一部分。
这个情况下,CLASSPATH中就要配置com文件夹所在的路径
3. 如果运行的Hello类,被打包到一个jar中,比如是me.jar
那么当运行 java Hello 的时候,JVM会从CLASSPATH中指定的路径中查找,是否有me.jar,如果
有那么就从me.jar中将Hello.class载到内存,然后运行,如果没有那么就报错。(这是Hello没指定
包的情况)
那么当运行 java com.briup.test.Hello 的时候,JVM会从CLASSPATH中指定的路径中查找,
是否有me.jar,如果有那么就从me.jar中将com/briup/test/Hello.class加载到内存,然后运行,如果没有那么就报错。(这是Hello指定包的情况)
注意,这时候,是要把jar文件的路径和jar文件的名字,都配置到CLASSPATH中
java中有jar命令,可以将一个或多个class文件,打包到一个指定的jar文件中(xxx.jar)
例如,jre中的rt.jar,就是将src.zip中的java源代码编译成class文件后,又将这些class文件打包到rt.jar中
的。
java中的类,想要运行就必须把类对应的class文件加载到内存,JVM中真正负责加载class文件内容
的是类加载器
在java中,负责把class文件加载到内存的是类加载器(ClassLoader)
JavaSE-API中,有这么一个类: java.lang.ClassLoader ,它就表示JVM中的类加载器。
JVM启动后,默认会有几种类加载器:
启动类加载器 bootstrapClassLoader,非java语言实现
作用:加载指定路径中jar里面的class文件
路径1:C:\Program Files\Java\jdk1.8.0_74\jre\lib\
路径2:C:\Program Files\Java\jdk1.8.0_74\jre\classes\ ( 如果有这个目录的话)
例如:rt.jar
扩展类加载器 ExtClassLoader,java语言实现,是ClassLoader类型的对象
作用:加载指定路径中jar里面的class文件( 只能是jar中存在的class)
路径:C:\Program Files\Java\jdk1.8.0_74\jre\lib\ext\
例如:ext中默认存在的jar,或者用户放到ext目录下的jar包
应用类加载器 AppClassLoader,java语言实现,是ClassLoader类型的对象
作用:加载指定路径中class文件或者jar里面的class文件
路径:CLASSPATH中配置路径,这个是用户自己配置的
例如:.:bin:hello.jar
我们最常使用的就是应用类加载器,因为它可以通过CLASSPATH中的路径,去加载程序员自己编写
并编译的class文件到内存中。
我们也可以把自己最常用的jar包,放到ext目录中,让扩展类加载器去自动加载这个jar中的class文
件到内存中,这样我们的代码就可以直接使用到这个jar中的类了
但是其实,大多数情况下,即使我们需要用到其他jar中的代码,也一般会把jar所在的路径配置到
CLASSPATH中,让应用类加载器进行加载,这样会更加方便统一管理项目中使用的所有jar
关于启动类加载器,它不是java语言编写的,我们一般也不要去动它的路径或者jar,它是负责在
JVM启动的时候,把JRE环境中最重要的一些library加载到内存,一旦出问题,JVM就无法正常运
行。
多个类加载器之间共同协作,然后把需要使用或运行的类加载到内存去执行,它们直接共同合作的
方式就是双亲委托机制。
现在要加载Hello.class文件中的类
首先加载任务就交给了AppClassLoader
然后AppClassLoader把这个任务委托给自己的父加载器,也就是ExtClassLoader
然后ExtClassLoader把这个任务委托给自己的父加载器,也就是bootstrapClassLoader
然后bootstrapClassLoader就尝试去加载Hello这个类,但是在指定路径下并没有找到
然后任务又交回给了ExtClassLoader,ExtClassLoader尝试加载Hello这个类,但是在指定路径中没到
然后任务又交回给了AppClassLoader
最后AppClassLoader从CLASSPATH中指定的路径里面找到并加载了这个Hello类,完成类加载的过程