Java JVM基础知识

本文详细介绍了Java的跨平台性、JVM的基本概念、JRE/JDK/JVM的关系,以及JVM的生命周期和工作过程。重点讲解了类加载机制,包括加载、验证、准备、解析和初始化的详细步骤,以及类加载器的工作原理和双亲委派模型。此外,还探讨了JVM执行引擎的内部结构,如解释器、编译器和垃圾回收机制。
摘要由CSDN通过智能技术生成

⭐写在前面⭐

📢今天我们进行 Java JVM基础知识 的学习,感谢你的阅读,内容若有不当之处,希望大家多多指正,一起进步💯!!!
♨️如果觉得博主文章还不错,可以👍三连支持⭐一下哦😀

🍀一、JVM基础知识

☘️1. Java的跨平台性

在这里插入图片描述

Java中的跨平台特征,指的是Java程序能够实现跨平台,而不是JVM,JVM是用C/C++开发的,是编译后的机器码,是不能跨平台的,不同的平台需要安装不同版本的JVM。

Java程序经过编译,可以在任何平台上执行,仅仅需要JVM这一个中间层,来做不同平台的环境差异的兼容,可以让Java程序在不同平台的运行,真正实现了“一次编码,到处执行”的目的。

☘️2. JVM概念

  • JVM即Java Virtual Machine,Java虚拟机
  • JVM是Java的核心和基础,在Java编译器和操作系统之间的虚拟处理器,利用软件方法来实现的抽象的计算机,基于下层操作系统和硬件平台,可以在JVM上执行Java的字节码程序。
  • JVM包括一套字节码指令集一组寄存器一个栈一个垃圾回收堆一个存储方法区域,JVM屏蔽了具体操作系统平台相关的信息,是Java程序只需要生成JVM虚拟机上运行的目标代码,就可以在多个平台上执行。

☘️3. JRE/JDK/JVM关系

在这里插入图片描述

  • JVM(Java virtual Machine Java虚拟机)是JRE的一部分,是一个虚拟出来的计算机,是通过在实际的计算机上仿真模拟实现各种计算机功能来实现的。
  • JRE(Java Runtime Environment Java运行环境)也就是Java平台,所有的Java程序都要在JRE下才能运行,普通用户只需要运行已开发好的Java 程序,安装JRE即可。
  • JDK(Java Development Kit Java开发工具包),是程序开发者用来进行编译,调试Java程序要用到的开发工具包,JDK的工具也是Java程序,也需要在JRE上才能运行。为了保持JDK的完整性和独立性,在JDK安装过程中,JRE也是安装的一部分,所有在JDK的安装目录下有一个jre目录,用户存放jre文件。
    在这里插入图片描述

🍀二、JVM生命周期

  • JVM是负责运行一个Java程序,当启动一个Java程序时,一个虚拟机实例也就诞生了,当程序关闭退出时,虚拟机实例也就随之消亡。
  • JVM实例通过调用某个初始类的main方法来运行一个Java程序而这个main方法必须是公有的(public)静态的(static)返回值为void并且接收一个字符串数组作为参数,任何拥有这个main方法的都可以作为Java程序的起点。

🍀三、JVM工作过程

☘️1. Java代码如何运行

在这里插入图片描述

☘️2. 工作过程介绍

在这里插入图片描述

JVM系统被划分了主要三个子系统

  1. 类装载子系统(Class Loader Subsystem)
  2. 运行时数据区域(Runtime Data Areas)
  3. 执行引擎(Execution Engine)

🌵(1) 类装载子系统

Java的动态类加载功能由类装载子系统来实现,可以加载、链接、第一次运行引用类时初始化类文件。

  • 加载:功能是加载类,共有三种类加载器BootStrap ClassLoaderExtension ClassLoaderApplication ClassLoader
  • 链接:链接阶段要做的是将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中,经由验证准备解析三个阶段。
    验证:字节码验证器将验证生成字节码是否正确,如果验证失败,将无法继续后续操作
    准备:对于所有的静态变量,内存将被分配默认值
    解析:将所有的符号内存引用都替换为来自方法区域的原始引用
  • 初始化:静态变量都将被赋予原始值,静态代码块将被执行。

🌵(2)运行时数据区域

  • 方法区:类级别数据、静态变量存储位置。线程共享
  • 堆区:对象及其对象实例变量和数组存储位置。线程共享
  • 虚拟机栈:线程私有的
  • 本地方法栈:保存本地方法的信息(JNI),线程私有
  • 程序计数器:线程私有的PC寄存器,在执行指令时,保存当前指令地址

🌵(3)执行引擎

分配给运行时数据区域的字节码将由执行引擎执行,执行引擎读取字节码并逐个执行
Interpreter
解释器解释字节码很快,但执行缓慢。解释器的缺点是,当一个方法被多次调用时,每次都需要新的解释。

JIT Compiler
JIT编译器消除了解释器的缺点。执行引擎将使用解释器的帮助来转换字节代码,但是当它发现重复的代码时,它使用JIT编译器,它编译整个字节码并将其更改为本机代码。这种本机代码将直接用于重复的方法调用,从而提高系统的性能。

Intermediate Code generator
生成中间代码。

Code Optimizer
负责优化上面生成的中间代码。

Target Code Generator
负责生成机器代码或本机代码。

Profiler
一种特殊的组件,负责查找hotspots,即该方法是否被多次调用。

垃圾回收
收集和删除未引用的对象。可以通过调用“System.gc()”来触发垃圾收集,但不会立即执行(执行时机由GC决定)。JVM的垃圾回收收集已创建的对象。

Java Native Interface (JNI)
JNI将与本地的方法库交互并为执行引擎提供所需的本地库。

Native Method Libraries
它是执行引擎所需的本地库的集合。

🍀四、类加载机制

☘️1. 类加载时机

虚拟机规范严格规定了只有6种情况必须立即对类进行“初始化”(class文件加载到JVM中

● 创建对象实例:new对象的时候,会对类的初始化,前提这个类没有被初始化
● 调用类的静态属性或者对静态属性赋值
● 调用类的静态方法
● 通过class文件反射创建对象
● 初始化一个类的子类,使用子类时候先初始化父类
● JVM启动时被标记为启动的类,比如main方法所在类

🍁注Java类的加载是动态的,并不会一次性将所有的类全部加载再执行,而是保证程序运行的基础类完全加载到JVM中,至于其他类,则在需要的时候才会加载,目的是为难了节省内存开销

不会进行初始化的情况:

● 在同一个类加载器下面只能被初始化类一次,如果已经初始化了就不必在初始化了
● 在编译的时候能确定下来的静态变量,不会对类进行初始化,比如final修饰静态变量

☘️2. 类加载器

负责将字节码文件(class)加载到内存中即运行时数据区域

🌵(1)类加载器介绍

在这里插入图片描述

JVM中内置了三个重要的类加载器,除了BootStrapClassLoader类加载器外,其他类加载器均由Java实现并全部继承自Java.lang.ClassLoader

各个加载器的职责:

  • BootStrapClassLoader
    负责加载jre/lib/rt.jar 里所有的class或者被-Xbootclasspath参数指定路径中的所有类,由C++实现,不是ClassLoader的子类。
  • Extension ClassLoader
    负责加载Java平台中扩展的一些jar包,包括jre/lib/ext下的jar包或者是-Djava.ext.dirs指定目录下的jar。
  • Application ClassLoader
    面向我们用户的加载器,负责加载当前应用的classpath下的所有jar包和类

🌵(2)JVM类加载的方式

类加载有三种方式:
● 1、命令行启动应用的时候由JVM初始化加载
● 2、通过Class.forName()方法动态加载
● 3、通过ClassLoader.loadClass()方法动态加载

Class.forName()和ClassLoader.loadClass()区别

● Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
● ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
● Class.forName(name,initialize,loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。


🌵(3)双亲委派模型

在这里插入图片描述

双亲委派模型的工作过程如下:

1、当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则返回原来已经加载的类。
2、如果没有找到,就去委托父类加载器去加载。父类加载器也会采用同样的策略,查看自己已经加载过的类中是否包含这个类,有就返回,没有就委托其父类去加载,直到委托到启动类加载器为止。因为如果父类加载器为空了,就代表使用启动类加载器作为父加载器去加载该类。(也就是看到的String类加载器为null)
3、如果启动类加载器加载失败,就会使用扩展类加载器来尝试加载,继续失败则会使用AppClassLoader来加载,继续失败就会抛出一个异常ClassNotFoundException。


🍁双亲委派模型的好处

  • 安全性:避免用户自己编写的类动态替换Java的一些核心类
  • 避免类的重复加载

☘️3. 类记载的详细过程

在这里插入图片描述
加载验证准备初始化卸载这五个阶段过程是固定的,在类加载的过程中必须按照这种顺序按部就班的进行,而解析阶段则不一样,在某种情况下可以在初始化后完成。

🌵(1)加载

加载阶段,虚拟机需要完成3件事:
● 通过一个类的全限定名获取定义此类的二进制字节流。
● 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
● 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据结构的访问入口。

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。

🌵(2)验证

确定该类是否符合java语言的规范,有没有属性和行为的重复,继承是否合理,总之,就是保证JVM能够执行。

验证阶段主要完成下面4个阶段的校验动作:
文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:确保解析动作能正确执行。

🌵(3)准备

准备阶段主要做的就是为由static修饰的成员变量分配内存,并设置默认的初始值。
默认初始值如下:

  • 八种基本数据类型默认的初始值是0
  • 引用类型默认的初始值是null
  • 有static final修饰的会直接赋值,例如:static final int x=10;则默认就是10

🌵(4)解析

解析的任务就是把常量池中的符号引用转换为直接引用,即JVM会将所有的类或接口名、字段名、方法名转换为具体的内存地址。
解析阶段是将虚拟机常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号进行。
符号引用: 简单的理解就是字符串,比如引用一个类,java.util.ArrayList 这就是一个符号引用,字符串引用的对象不一定被加载。
直接引用: 指针或者地址偏移量。引用对象一定在内存(已经加载)。

🌵(5)初始化

初始化这个阶段就是将静态变量(类变量)赋值的过程,即只有static修饰的才能被初始化, 执行的顺序就是:父类静态域或着静态代码块,然后是子类静态域或者子类静态代码块(静态代码块先被加载,然后再是静态属性)类初始化是类加载的最后一步,除了加载阶段,用户可以通过自定义的类加载器参与,其他阶段都完全由虚拟机主导和控制。到了初始化阶段才真正执行Java代码。类的初始化的主要工作是为静态变量赋程序设定的初值 。如static int a = 100;在准备阶段,a被赋默认值0,在初始化阶段就会被赋值为100。​

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WYSCODER

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值