JVM虚拟机
java文件都是.java
编译之后才是.class
千万不要把.class认为是java的类文件
编译的过程是由jdk完成的,和jvm是没有关系的
对就是javac编译器将Java代码翻译成字节码
.class文件就是字节码文件,字节码可以在jvm上运行
我们都知道在 Windows 系统上一个软件包装包是 exe 后缀的,而这个软件包在苹果的 Mac OSX 系统上是无法安装的。类似地,Mac OSX 系统上软件安装包则是 dmg 后缀,同样无法在 Windows 系统上安装。
为什么不同系统上的软件无法安装,这是因为操作系统底层的实现是不一样的。对于 Windows 系统来说,exe 后缀的软件代码最终编译成 Windows 系统能识别的机器码。而 Mac OSX 系统来说,dmg 后缀的软件代码最终编译成 Mac OSX 系统能识别的代码
而与其他语言不同,Java 语言并不直接将代码编译成与系统有关的机器码,而是编译成一种特定的语言规范,这种语言规范我们称之为字节码。无论 Java 程序要在 Windows 系统,还是 Mac OSX 系统,抑或是 Linux 系统,它首先都得编译成字节码文件,之后才能运行。但即使编译成字节码文件了,各个系统还是无法明白字节码文件的内容,这时候就需要 Java 虚拟机的帮助了。Java 虚拟机会解析字节码文件的内容,并将其翻译为各操作系统能理解的机器码
很多初学者关于 Java 虚拟机有一个误区,他们会觉得 Java 虚拟机只能运行 Java 代码。但实际上 Java 虚拟机运行的是字节码文件。换句话说,如果你用 php 语言写一段代码,并自己用特定编译器能生成符合字节码规范的字节码文件,那么 Java 虚拟机也是可以运行的。
Java 虚拟机做的事情就是:按照 Java 虚拟机规范去读取 Class 文件,并按照规定去解析、执行字节码指令,仅此而已
如果你够牛逼,你完全可以写一个编译器,将 PHP 语言代码编译成符合 Java 虚拟机规范的字节码文件,那么 Java 虚拟机也是可以执行的。
准确地说,Java 虚拟机与字节码文件(Class文件)绑定
其实 Java 虚拟机就是一个字节码翻译器,它将字节码文件翻译成各个系统对应的机器码,确保字节码文件能在各个系统正确运行。
jdk、jre和jvm的关系
jdk包含jre,jre包含jvm
JVM的组成
类加载器负责程序员和jvm交互,把.class加载到jvm中
运行时数据区是由方法区、栈、堆等共同组成的一个区域
执行引擎负责jvm和操作系统的交互
JVM编译器
在 JVM 中有三个非常重要的编译器,它们分别是:前端编译器、JIT 编译器、AOT 编译器
前端编译器,最常见的就是我们的 javac 编译器,其将 Java 源代码编译为 Java 字节码文件(我们一般称 javac 编译器为前端编译器,因为其发生在整个编译的前期)。
JIT 即时编译器,最常见的是 HotSpot 虚拟机中的 Client Compiler 和 Server Compiler,其将 Java 字节码编译为本地机器代码。
AOT 编译器则能将源代码直接编译为本地机器码。
这三种编译器的编译速度和编译质量如下:
- 编译速度上,解释执行 > AOT 编译器 > JIT 编译器。
- 编译质量上,JIT 编译器 > AOT 编译器 > 解释执行。
而在 JVM 中,通过这几种不同方式的配合,使得 JVM 的编译质量和运行速度达到最优的状态。
JVM内存结构(即运行时数据区)
其实 Java 虚拟机的内存结构并不是官方的说法,在《Java 虚拟机规范》中用的是「运行时数据区」这个术语。但很多时候这个名词并不是很形象,再加上日积月累的习惯,我们都习惯用虚拟机内存结构这个说法了
Java 虚拟机的内存结构可以分为公有和私有两部分:
- 公有指的是所有线程都共享的部分,指的是 Java 堆、方法区、常量池。
- 私有指的是每个线程的私有数据,包括:PC寄存器、Java 虚拟机栈、本地方法栈
JVM 类加载机制
当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析、运行等整个过程,这 个过程我们叫:Java 虚拟机的类加载机制。
JVM 虚拟机执行 class 字节码的过程可以分为七个阶段:加载、验证、准备、解析、初始化、使用、卸载。
加载
把代码数据加载到内存中验证
JVM 对字节码流的校验,只有符合 JVM 字节码规范的文件才能被 JVM 正确执行准备(重点)
开始为类变量分配内存并初始化解析
JVM 针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类引用进行解析。这个阶段的主要任务是将其在常量池中的符号引用替换成直接其在内存中的直接引用初始化(重点)
到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。在这个阶段,JVM 会根据语句执行顺序对类对象进行初始化使用
JVM 开始从入口方法开始执行用户的程序代码卸载
当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存
JVM 垃圾回收机制
到底谁是垃圾?
如果一个对象不可能再被引用,那么这个对象就是垃圾,应该被回收。
根据这个思想,我们很容易想到使用引用计数的方法来判断垃圾。在一个对象被引用时加一,被去除引用时减一,这样我们就可以通过判断引用计数是否为零来判断一个对象是否为垃圾。这种方法我们一般称之为「引用计数法」。
上面的这种方法虽然简单,但是其存在一个致命的问题,那就是循环引用。
A 引用了 B,B 引用了 C,C 引用了 A,它们各自的引用计数都为 1。但是它们三个对象却从未被其他对象引用,只有它们自身互相引用。从垃圾的判断思想来看,它们三个确实是不被其他对象引用的,但是此时它们的引用计数却不为零。这就是引用计数法存在的循环引用问题。
而现今的 Java 虚拟机判断垃圾对象使用的是:GC Root Tracing 算法。其大概的过程是这样:从 GC Root 出发,所有可达的对象都是存活的对象,而所有不可达的对象都是垃圾