title: JVM必知必会
1. JVM是啥?
JVM(Java Virtual Machine) Java虚拟机
虚拟机有两种:
系统虚拟机: VirtualBox ,VMware 是对硬件的模拟
程序虚拟机:jvm ,是为了执行单个程序设计的
我们都知道java具有跨平台特性,为什么?
write once , run anywhere
解释 —>
java源程序先经过javac编译器编译成二进制的.class字节码文件(java的跨平台指的就是.class字节码文件的跨平台,.class字节码文件是与平台无关的),.class文件再运行在jvm上,java解释器(jvm的一部分)会将其解释成对应平台的机器码执行,所以java所谓的跨平台就是在不同平台上安装了不同的jvm,而在不同平台上生成的.class文件都是一样的,而.class文件再由对应平台的jvm解释成对应平台的机器码执行 。
JVM是最强大的虚拟机,是比 java更优秀的产品;请注意,JVM不仅仅面向Java
JVM直接运行的是字节文件,是二进制格式。意思是,不管是什么语言,只要你可以变成字节码,更准确地说:只要你给我的是Jvm字节码(Java字节码这个名词,其实可以换成Jvm字节码), 那么Jvm都可以运行
例如: Java,Jruby, Groovy,JavaScript, Kotlin 等等
上面的语言都会被编译成字节码文件从而通过jvm在机器上执行,这些字节码文件必须遵守一定的规则
也正因为如此, 我们才有了多语言混合编程,上面几个语言,终究会编译成相同规则的字节码文件, 不同语言之间可以相互调用了。
试想一下: 如果我们自己开发个语言,我们自己写点语法在向下运行时,我们只用考虑到如何让咱的语言转换成 字节码就ok了, 我们借助JVM,来运行我们的语言。 事实上 Groovy, Kotlin 也都是这样弄的
1.1 JVM位置
我们第一次下载JDK的时候,肯定之根据不同的操作系统来下载不同的jdk , 本质是不同版本jdk的
jvm不同,jvm是运行在操作系统上的,意思就是, mac下载的jdk, 肯定和 windows jdk不一样,因为 他俩的JVM不同(别忘了,jvm是要运行字节码文件,你给他字节码,他帮你运行。他一定是要和硬件打交道的,这也解释了,为啥他能帮助我们跨平台,他下面和操作系统的接口是不同的,但是上层和字节码的交互是相同的,这就叫跨平台,跨硬件)
1.2 JVM 的整体结构
说了这么多, 我们最好在整体上了解一下JVM的解释运行的整个流程:
先看第一个图:
我们主要以第二张图为例:
(先是 java文件通过编译器成为ClassFile!,图上就不画了,此处的编译器也就是我们常常听说的编译器,我们把这个编译器成为前端编译器, 因为是比较上层的一个编译器)
ClassFiles : 字节码文件, jvm的原材料,诡异的是,每个字节码文件都是一个 类
! 这个类可不是你的java 程序里面的类啊,不要搞混。 这一块在内存中的字节码,就是一个类,是存放在物理磁盘上的 文件, claassFiles被称为DNA元数据模板,放在方法区。
类装载器子系统: 把 字节码 加载到 内存
当中
以下三个加载器是 父子继承关系:
启动类加载器 :又叫根加载器, rt.jar通跟加载器加载到内存中,rt.jar是java的底子,有很多基本的类
拓展类加载器: 所有在1.0版本之后拓展的java类,都叫拓展的java类,都是用拓展类加载器加载的。
应用程序类加载器: 咱们自己写的类,使用这个加载器加载
请看懂如下代码
Object object = new Object();
System.out.println(object.getClass().getClassLoader());
//null 根加载器,也叫BootStrap加载器
testCode testcode = new testCode();
System.out.println(testcode.getClass().getClassLoader());
// sun.misc.Launcher$AppClassLoader@18b4aac2 应用程序类加载器
System.out.println(testcode.getClass().getClassLoader().getParent());
//sun.misc.Launcher$ExtClassLoader@1b6d3586 拓展类加载器
System.out.println(testcode.getClass().getClassLoader().getParent().getParent());
//null 根加载器,也叫BootStrap加载器
双亲委派机制:
如果你要用一个类,一定要从上向下找,先找根加载器, 再拓展类加载器,再应用程序类加载器
从下面的代码你就知道,明明我们自己手写了一个java.lang.String类, 含有main方法,但是由于是从从上往下找的,在第一层的 根加载器中的 rt.jar 就存在一个java.lang.String了,因此一定会使用那个,而不是我们是这个,
这也正好保证了,我们写的这些代码不会污染上层的java类, 这也就是沙箱安全
例子代码:
package java.lang;
/**
* 错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
* public static void main(String[] args)
* 否则 JavaFX 应用程序类必须扩展javafx.application.Application
*/
public class String {
public static void main(String[] args) {
System.out.println("这是我写的");
}
}
无论多少个线程,有且仅有一份的
方法区:
方法区就是模板工厂
方法区绝对不是存放方法的的地方,存放 类的结构信息
, 也就是模板
。
方法区装的是类的模板
, 应用程序类(也就是自己写的类)的模板啦等
存放的是模板!模板! 而不是
一个一个类的实例, 实例在堆中
栈管运行,堆管存储
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QFsmervO-1579602465244)(https://s2.ax1x.com/2020/01/21/1FxJqx.png)]
堆存放的是一个一个类的实例!就是new 之后的实例!
新生区:
养老区:
永久存储区: rt.jar 在这儿
Java7:
Java8
无论多少个线程, 每个线程都有一份的
Java栈:
栈负责运行 , 但仅仅存储8个基本变量,对象引用变量,实例方法
也就是学习 java栈 时常说的“栈”
也就是跑各种方法的地方,线程结束生命周期也就结束了。
栈没有垃圾回收机制 ,一个线程一块栈,线程结束代表栈消失
本地方法栈: 就是放 native 方法的地方 !!!
只要是 标有native的java源码,意思就是 这块代码要和底层操作系统打交道
但是java只是一个语言,她并不具能力操控硬件系统,因此, 很多native的存在就是表示终止
这意味着她要求助于外部语言
因此! 但凡是标有native的java源码,意味着要调用和java无关的系统级别库,比如c函数库啦等
因此native方必须存放在native栈里面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tvx4V5yU-1579602465249)(https://s2.ax1x.com/2020/01/21/1Fdi1f.png)]
程序计数器:
寄存器
执行引擎:
真真正正地把字节码 转换成 机器码
包含三部分
1. 解释器
2.JIT即时编译器(和上面对应,我们叫后端编译器)
3.垃圾回收器
本地方法接口
本地方法库
1.3 JVM的架构模型
指令集的架构模型分为两种:
1. 基于栈的指令集架构
指令集小, 指令多
实现简单,适用于资源受限的设备(嵌入式设备: 打印机,机顶盒)
0地址指令:无地址,仅有操作数,只牵扯栈顶
性能不如寄存器
2. 基于寄存器的指令集架构
和栈架构相反
那么:
由于跨平台特性,Java的字节码文件都是根据栈来设计的 ,因为不同的
cpu
的寄存器是不同的, 为了做到跨平台,我们必须保证,字节码文件能够做到不同硬件的通用,因此使用栈作为架构模型。
1.4 JVM的生命周期
虚拟机启动
JVM的启动是 通过引导类加载器(bootstrap class loader) 创建一个初始类(intial class)来完成的,这个类是根据虚拟机的具体实现指定的。
虚拟机的执行
是为了执行java程序
执行一个java程序的进程,本质上是一个执行jvm进程
虚拟机的退出
一个java程序结束,意味着一个jvm进程的结束