JVM -- ① JVM的组成

一、JVM总述

1、JVM理解

  1. JVM:Java Virtual Machine,也就是Java虚拟机
  2. 虚拟机:通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的计算机系统
  3. JVM是通过软件来模拟Java字节码的指令集,是Java程序的运行环境

2、JVM的位置

理解:JVM是运行在操作系统之上的,它与硬件没有直接的交互。
image.png

3、JVM的体系结构图

在这里插入图片描述

JVM主要包含两个子系统以及两个组件:

  1. 子系统 --> 类加载器(Class Loader)
  2. 子系统 --> 执行引擎(Execution Engine)
  3. 组件 --> 运行时数据区(Runtime Data Area)
  4. 组件 --> 本地接口(Natice Interface)

4、JVM的作用

  1. 为不同的硬件提供了一种编译Java技术代码的规范
    该规范使Java软件独立于平台,提高了Java语言的兼容性和可移植性
  2. 实现了以下功能:
    1. 通过 ClassLoader 寻找和装载 class 文件
    2. 解释字节码成为指令并执行,提供 class 文件的运行环境
    3. 进行运行期间的内存分配和垃圾回收
    4. 提供与硬件交互的平台

二、类加载器

1、作用:

根据给定的全限定类名,加载.class文件到Runtime Data Area中。

2、加载器分类:

  1. 启动类加载器(Bootstrap ClassLoader)

    1. 启动类加载器(引导类加载器/根类加载器)主要加载的是JVM自身需要的类
    2. 这个类加载是用C++语言实现的,是虚拟机自身的一部分;
    3. 它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存

    注意:

    1. 由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录 下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
    2. 由于启动类加载器涉及到本地方法的实现细节,所以开发者无法直接获取到类加载器的引用。具体可由启动类加载器加载到的路径可通过System.getProperty(“sun.boot.class.path”)查看。
  2. 扩展类加载器(Extension ClassLoader)

    1. 扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类
    2. 这个类是由Java语言实现的,是Launcher的静态内部类
    3. 它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库

    注意:

    1. 开发者可以直接使用标准扩展类加载器
    2. 具体可由扩展类加载器加载到的路径可通System.getProperty(“java.ext.dirs”)查看
  3. 系统类加载器(System ClassLoader)

    1. 系统类加载器也称应用程序加载器,是指 Sun公司实现的sun.misc.Launcher$AppClassLoader
    2. 这个类是由Java语言实现的,是Launcher的静态内部类
    3. 它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径

    注意:

    1. 开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器
    2. 通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器
  4. 用户自定义类加载器(User ClassLoader)
    用户自己定义的类加载器。

3、加载机制:双亲委派

  1. 概念:
    双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

  2. 优点:

    1. 用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
    2. 其次是考虑到安全因素,java核心API中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

三、执行引擎

  • 要了解执行引擎的工作原理,首先需要弄清楚程序运行的流程和结构。

1. 栈帧(Stack Frame)

  • 概念:栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的虚拟机栈的栈元素。
  • 内容:栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
    1. 局部变量表 --> 是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量编译期已经确定大小;

      • 注意:
        (1) 局部变量表的单位:以变量槽为最小单位,每个变量槽都可以存储32位长度的内存空间
        (2) 对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写。
    2. 操作数栈 --> 方法执行的过程中,字节码指令往操作数栈中写入/提取内容(方法开始时为空)。编译期已经确定大小;

      • 注意:一般情况下栈帧相互独立,可JVM遇到共享数据可能会优化,令栈帧重叠共用数据,减少参数的复制传递。
    3. 动态连接 --> 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有该引用是为了支持方法调用过程中的动态连接。

    4. 方法返回地址 --> 方法正常返回/异常退出,都会返回到该方法被调用的位置。

2. 线程调用方法(栈帧)工作原理

  • 编译器将源码转成字节码,执行引擎执行的是(被类加载器加载到方法区/元空间的)字节码内容。
  • 执行引擎的工作,是以线程为为单位进行的。每一个线程在创建的过程中都会伴随着一个栈,线程调用方法的本质就是上述栈帧的入栈与出栈过程。
  • 线程每调用一个方法,就会将该方法的栈帧置于栈顶,方法结束后则出栈。(例如创建线程调用start()方法,栈帧中会加入一个run()方法的栈帧)
    image.png

3. (线程)方法调用

(1) 分类:方法调用的主要任务就是确定被调用方法的版本,按照调用方式共分为两类:

    1. 解析调用: 是静态的过程,在编译期已经确定;
    2. 分派调用: 可能是静态/动态的

(2) 解析调用:

  • 在Class文件中,所有方法调用中的目标方法都是常量池中的符号引用,在类加载的解析阶段,会将一部分符号引用转为直接引用,也就是在编译阶段就能够确定唯一的目标方法,这类方法的调用成为解析调用
  • 两类方法:静态方法、私有方法;这两类方法都是不可能通过继承等方法重写,所以可以在编译期就确定
    • 静态方法:与类直接关联,不可重写
    • 私有方法:外部无法访问,无法继承
  • 符合上述两类方法的主要有:
    • 静态方法
    • 私有方法
    • 实例构造器
    • 父类方法
  • 虚拟机提供了以下几条方法调用指令:
    • invokestatic:调用静态方法,解析阶段确定唯一方法版本
    • invokespecial:调用 方法、私有及父类方法,解析阶段确定唯一方法版本
    • invokevirtual:调用所有虚方法
    • invokeinterface:调用接口方法
    • invokedynamic:动态解析出需要调用的方法,然后执行

(3) 分派调用:

  • 静态分派:所有依赖静态类型来定位方法执行版本的分派成为静态分派,发生在编译阶段,典型应用是方法重载
  • 动态分派:在运行期间根据实际类型来确定方法执行版本的分派成为动态分派,发生在程序运行期间,典型的应用是方法的重写
  • 单分派:根据一个宗量对目标方法进行选择。
  • 多分派:根据多于一个宗量对目标方法进行选择。

分派更加详细解释:方法调用分派详解

详细的执行引擎执行字节码步骤:执行引擎详解

四、运行时数据区

  • Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。
    在这里插入图片描述

1. 程序计数器:

  • 是当前线程所执行的字节码的行号指示器
  • 字节码解析器通过改变这个计数器的值,来选取下一条需要执行的字节码指令。

2. 栈:

  • 线程独享的
  • 两种分类:
    1. 虚拟机栈:存储栈帧,服务于Java方法
    2. 本地方法栈:存储本地方法(Native)的栈帧,服务于本地方法

3. 堆:

  • 所有线程共享
  • 存储内容:
    1. 实例对象
    2. 数组

4. 方法区

jdk7以前,方法区的实现是永久代,jdk8开始方法区的实现使用元空间取代了永久代。元空间和永久代最大的区别是:元空间不在虚拟机设置的内存中,而是使用本地内存

  • 所有线程共享其中内容

    1. 类信息
    2. 常量
    3. 静态变量
    4. 即时编译后的代码
  • 存储内容:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E96Gvv3f-1624160243480)(http://39.105.86.48:8090/upload/2021/06/image-d8b4151490824613a7cd130982cea5fc.png)]

    各部分详解:方法区各内容详解

五、本地接口

  • 与native libraries交互,是其它编程语言交互的接口。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值