JAVA虚拟机属于java的核心特性。
课程安排
JVM全称是Java Virtual Machine,中文译名Java虚拟机。
虽然需要解释,不如C与C++。但是JAVA支持 跨平台 !
由于JVM需要实时解释虚拟机指令,不做任何优化性能不如直接运行机器码的C、C++等语言。
即时编译
名称 | 作者 | 支持版本 | 社区活跃度(github star) | 特性 | 适用场景 |
---|---|---|---|---|---|
HotSpot(Oracle JDK版) | Oracle | 所有版本 | 高(闭源) | 使用最广泛,稳定可靠,社区活跃 JIT支持 Oracle JDK默认虚拟机 | 默认 |
HotSpot(Open jDK版) | Oracle | 所有版本 | 中(16.1k) | 同上 开源,Open jDK默认虚拟机 | 默认 对JDK有二次开发需求 |
GraalVM | Oracle | 11, 17,19企业版支持8 | 高(18.7k) | 多语言支持高性能、JIT、 AOT支持 | 微服务、云原生架构 需要多语言混合编程 |
Dragonwell jDK 龙井 | Alibaba | 标准版8,11,17 扩展版11,17 | 低(3.9k) | 基于OpenjDK的增强 高性能、bug修复、安全性提升 JWarmup、ElasticHeap、 Wisp特性支持 | 电商、物流、金融领域对性能要求比较高 |
Eclipse OpenJ9(原IBM J9) | IBM | 8,11,17,19,20 | 低(3.1k) | 高性能、可扩展JIT、AOT特性支持 | 微服务、云原生架构 |
C:\Users\HP>java -version
java version "19.0.2" 2023-01-17
Java(TM) SE Runtime Environment (build 19.0.2+7-44)
Java HotSpot(TM) 64-Bit Server VM (build 19.0.2+7-44, mixed mode, sharing)
对于HotSpot(Oracle JDK版) 最常用,以它为主要内容讲解!!
我现在编译了一个字节码文件
java虚拟机已经把字节码 加载与执行都处理完了,那还学什么!
字节码文件中保存了源代码编译之后的内容,以二进制的方式存储,无法直接用记事本打开阅读。
●文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。
●软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。
文件类型 | 字节数 | 文件头 |
---|---|---|
JPEG (ipg) | 3 | FFD8FF |
PNG (png) | 4 | 89504E47 (文件尾也有要求) |
bmp | 2 | 424D |
XML (xml) | 5 | 3C3F786D6C |
AVI (avi) | 4 | 41 564920 |
Java字节码文件(.class) | 4 | CAFEBABE |
●版本号的作用主要是判断当前字节码的版本和运行时的JDK是否兼容。
比如:
两种方案:
名称 | 作用 |
---|---|
Magic魔数 | 固定为0xCAFEBABE,不会改变 |
主版本号、副版本号 | 编译字节码文件的JDK版本 |
访问标识 | 标识是类还是接口、注解、枚举、模块标识public final abstract |
类、父类、接口索引 | 通过这些索引可以找到类、父类、接口的信息 |
保存了字符串常量、类或接口名、字段名主要在字节码指令中使用
字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。
名字也是常量,内容如果不一样就按常规方法存,但是名字和内容一样,其实常量池中已经有这个常量了,常量池中一般不重复,所以不用中转。
名字也作为一个值来储存,比如A = “abc” 什么以上 在一个地址 地址里面存了A 另外一个地址存"abc" 这需要2个地址 如果abc = “abc” 那么 abc 直接就存在那个地址里面
●字节码中的方法区 域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中。
我们打开字节码文件
注意 : 对于iload_1这个 是把数值复制了一份 而不是删掉
public class Demo1 {
public static void main(String[] args) {
int i = 0;
i =i++;
System.out.println(i); //0
}
}
答案是0,我通过分析字节码指令发现,i+ +先把0取出来放入临时的操作数栈中,接下来对i进行加1,i变成了1,最后再将之前保存的临时值0放入i,最后i就变成了0。
在IDEA安装插件!
build——recompile
必须选择自己的文件 且查看最新的要重新编译!
如果我们的程序已经在运行中,我们可不可以查看字节码文件
类的生命周期描述了一个类加载、使用、卸载的整个过程
对于使用来说 我们最熟悉——new 或者反射等。
有的可能分的比较细,7个阶段,是因为把连接拆成了验证、准备、和解析。
加载阶段:
不同渠道是什么?
类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到方法区中。
类加载器在加载完类之后, Java虚拟机会将字节码中的信息保存到内存的方法区中。
生成一个InstanceKlass对象,保存类的所有信息,里边还包含实现特定功能比如多态的信息。
推荐使用JDK自带的hsdb工具查看Java虚拟机内存信息。工具位于JDK安装目录下lib文件夹中的sa-jdijar中。
java -cp sa-jdi.jar sun.jvm.hotspot.HSDB
连接阶段:
连接阶段——验证
验证的主要目的是检测Java字节码文件是否遵守了《Java虚拟机规范》中的约束。这个阶段一般不需要程序员参与。
主版本号不能高于运行环境主版本号,如果主版本号相等,副版本号也不能超过。
连接阶段——准备阶段:
●准备阶段为静态变量(static) 分配内存并设置初始值。
●注意:本章涉及到的内存结构只讨论JDK8及之后的版本,8之前的版本后续章节详述。
为什么要默认值?
但是!final 修饰呢?又是另外一种情况!
这个例子就说明,他没有赋值初值为 0 的过程!
连接阶段——解析阶段:
●解析阶段主要是将常量池中的符号引用替换为直接引用。
●符号引用就是在字节码文件中使用编号来访问常量池中的内容。
初始化阶段
●初始化阶段会执行静态代码块中的代码,并为静态变量赋值。
●初始化阶段会执行字节码文件中clinit部分的字节码指令。
以下几种方式会导致类的初始化:
第二个测试用例:
结果打印:初始化了…
第三个测试用例:
我们找到main所在的类:Demo5 初始化! 然后用到了new Demo6 初始化6
好!我们现在看看这两道大题能不能轻松拿下!
public class Demo1 {
public static void main(String[] args) {
System.out.println("A");
new Demo1();
new Demo1();
}
public Demo1(){
System.out.println("B");
}
{
System.out.println("C");
}
static {
System.out.println("D");
}
}
DACBCB
因为实例代码块会在构造方法之前执行
类的生命周期初始化阶段
clinit指令 在特定情况下不会出现,比如:如下几种情况是不会进行初始化指令执行的。
1.无静态代码块且无静态变量赋值语句。
2.有静态变量的声明,但是没有赋值语句。
3.静态变量的定义使用final关键字,这类变量会在准备阶段直接进行初始化。
类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。
类载器分为两类
类加载器的设计JDK8和8之后的版本差别较大,JDK8及之前的版本中默认的类加载器有如下几种:
类加载器的分类启动类加载器
●启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟机提供的、使用C+ +编写的类加载器。
●默认加载Java安装目录/jre/lib下的类文件,比如rt.jar,tools.jar, resources.jar等。
通过启动类加载器去加载用户jar包:
类加载器的分类默认类加载器
●扩展类加载器和应用程序类加载器都是JDK中提供的、 使用Java编写的类加载器。
●它们的源码都位于sun.misc.Launcher中, 是一个静态内部类。继承自URLClassLoader。 具备通过目录或者指定jar包将字节码文件加载到内存中。
通过扩展类加载器去加载用户jar包:
类加载器的分类应用程序类加载器
验证一下:
双亲委派机制有什么用!
双亲委派机制指的是:当一个类加载器接收到加载类的任务时,会自底向上查找是否加载过,再由顶向下进行加载。
我们可以在代码中展示一下这个类:
首先,在Java中如何使用代码的方式去主动加载一个类呢?
虽然说,我们的双亲委派机制很牛!但是我们在一些情况下,我们需要打破这个机制才能实现我们想要的功能。
自定义类加载器并且重写loadClass方法,就可以将双亲委派机制的代码去除
Tomcat通过这种方式实现应用之间类隔离,《面试篇》 中分享它的做法
一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,比如Servlet类,Tomcat要保证这两个类都能加载并且它们应该是不同的类。
如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载了。
public Class<?> loadClass(String name)
protected Class<?> findClass(String name)
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
protected final void resolveClass(Class<?> c)
spi全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。
spi的工作原理: