Java的跨平台性
在深入了解JVM之前,我们先来看看Java程序,我们都知道Java程序是跨平台性的,那么它究竟是怎么实现跨平台的呢?
答案就是通过JVM来实现的,那么JVM又是什么呢?
JVM是Java Virtual Machine(Java虚拟机)的缩写,是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
值得注意的是:
- Java程序可以依靠 JVM 进行跨平台,但是不是 JVM 跨平台。(原因是不同操作系统的 JVM 版本不同)。
- java源文件 通过编译生成 java.c字节码文件 再通过 JVM 将字节码文件 翻译 成特定平台下的机器码 进行运行,而Java程序并没有发生任何改变。
- JVM 是通过 C\C++ 开发的,是Java程序与不同操作系统之间的中间层。
JVM
对于JVM来说,它是 Java 的核心和基础,在Java编译器和 os平台之间的虚拟处理器,它是一种利用软件方式实现的抽象的计算机基于下层的操作系统和操作平台,可以在上面执行Java的字节码程序。
- JVM 有自己完善的硬件架构,如处理器,堆栈,寄存器等,还具有相应的指令系统。
- Java语言最重要的就是跨平台运行。使用 JVM 就是为了支持与操作系统无关,实现跨平台。
我们在使用Java这门语言时,一定听说过JRE、JDK以及JVM这三个名词,那么他们又有什么关系呢?
- JRE (Java Runtime Environment) Java运行环境。也就是Java平台。所有的Java程序都要在 JRE 下才能运行。(普通用户只需要运行已开发好的Java程序,安装 JRE 即可。)
- JDK (Java Development kit) 是程序开发者用来编译,调式Java程序所用的开发工具包。(JDK 的工具也是Java程序,也需要 JRE 才能运行。为了保持 JDK 的独立性和完整性,在 JDK 的安装过程中,JRE 也是安装的一部分。所以,在 JDK 的安装目录下有一个名为 jre 的目录,用于存放 JRE 文件。)
- JVM (Java Virtual Machine) Java虚拟机。是 JRE 的一部分。他是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
实际上他们的关系也就是包含的关系,用图解也就是:
JVM 生命周期
启动和消亡:
- JVM 负责运行一个Java程序。当启动一个Java程序时,一个虚拟机实例也就诞生了。当程序关闭退出时,这个虚拟机实例也就随之消亡。
JVM 运行起点:
- Java虚拟机实例通过调用某个初始类的 main() 方法来运行一个Java程序。
- 而这个 main() 方法必须是共有的(public)、静态的(static)、返回值为 void,并且接受一个字符串数组作为参数。任何拥有这样一个 main() 方法的类都可以作为Java程序运行的起点。
JVM 两种线程:
- 守护线程和非守护线程。守护线程通常是由虚拟机自己使用的,比如执行垃圾收集任务的线程。但Java程序也可以把创建的线程标记为守护线程。而Java程序中的初始线程 - - main()的线程是非守护线程。只要还有任何非守护线程在运行,那么这个Java程序也在继续执行。当该程序中所有的非守护线程都终止时,虚拟机实例将自动退出。假若安全管理器允许,程序本身也能够通过调用 Runtime 类或者 System 类的 exit() 方法来退出。
类加载机制
我们都知道Java源文件,通过编译器,能够生产相应的 .class 文件,也就是字节码文件。而字节码文件又通过Java虚拟机中的解释器,编译成特定机器上的机器码。 即如下:
- 1、Java源文件 -> 编译器 -> 字节码文件
- 2、字节码文件 -> JVM -> 机器码
每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是Java为什么能够跨平台的原因了,当一个程序从开始运行,这时虚拟机就开始实例化了,多个程序启动就会存在多个虚拟机实例。程序退出或者关闭,则虚拟机实例消亡,多个虚拟机实例之间数据不能共享。
类加载时机
虚拟机规范则是严格的规定了有且只有5种情况必须立即对类进行“初始化”(class文件加载到JVM中);
主动初始化的6种方式:
- 1、创建对象实例:new对象的时候,会对类的初始化,前提是这个类没有被初始化。
- 2、调用类的静态属性或为静态属性赋值。
- 3、调用类的静态方法。
- 4、通过class文件反射创建对象。
- 5、初始化一个类的子类:使用子类的时候先初始化父类。
- 6、Java虚拟机启动时被标记为启动类的类:比如main()方法所在的类。
注:Java类的加载是动态的,它并不会一次性将所有的类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到JVM中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
不会进行初始化的情况:
- 1、在同一个类加载器下面只能初始化类一次,如果已经初始化了就不必要初始化了。
- 2、在编译时能确定下来的静态变量(编译常量),不会对类进行初始化。比如 final 修饰的静态变量。
类加载器
关于类加载器,前面已经做过介绍,这里就不再进行介绍了。
详情请见:类加载器
双亲委派模型
关于双亲委派模型,前面已经做过介绍,这里就不再进行介绍了。
详情请见:双亲委派模型
类加载详细过程
加载器加载到 JVM 中,接下来其实又分了好几个步骤:
- 1、加载,查找并加载类的二进制数据,在Java堆中也创建一个 java.lang.Class 类的对象。
- 2、连接,连接又包含三块内容:验证、准备、解析。
- (1)验证,文件格式、元数据、字节码、符号引用验证。
- (2)准备,为类的静态变量分配内存,并将其初始化为默认值。
- (3)解析,把类中的符号引用转换为之间引用。
- 3、初始化,为类的静态变量赋予正确的初始值。