JVM类加载

一、JVM执行流程

程序在执行之前先要把java代码转换成字节码(class文件),JVM 首先需要把字节码通过一定的方式类加载器(ClassLoader) 把文件加载到内存中 运行时数据区(Runtime Data Area) ,而字节码文件是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine)将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能,这就是这4个主要组成部分的职责与功能。
综上,JVM主要通过以下4个部分来执行Java程序:
1.类加载器
2.运行时数据区
3.执行引擎
4.本地库接口

二、JVM执行时数据区

在这里插入图片描述

2.1堆(线程共享)

作用:程序中创建的所有对象都保存在堆中
堆里面分为两个区域:新生代和老生代,新生代放新建的对象,当经过一定 GC 次数之后还存活的对象会放入老生代。新生代还有 3 个区域:一个 Endn + 两个 Survivor(S0/S1)。
垃圾回收的时候会将 Endn 中存活的对象放到一个未使用的 Survivor 中,并把当前的 Endn 和正在使用的 Survivor 清除掉。
在这里插入图片描述

2.2Java虚拟机栈(线程私有)

在这里插入图片描述
1.局部变量表:存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用。
2.操作栈:每个方法会生成一个先进后出的操作栈。
3.动态连接:指向运行时常量池的方法引用。
4.方法返回地址:PC 寄存器的地址。

2.3本地方法栈(线程私有)

本地方法栈和虚拟机栈类似,只不过 Java 虚拟机栈是给 JVM 使用的,而本地方法栈是给本地方法使用的

2.4程序计数器(线程私有)

程序计数器的作用:用来记录当前线程执行的行号的。

2.5方法区(线程共享)

方法区的作用:用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的。

三、JVM类加载

3.1类的生命周期

在这里插入图片描述
所以,类加载主要分为以下步骤:
1.加载
2.连接
2.1验证
2.2准备
2.3解析
3.初始化

3.2类加载的过程

3.2.1加载

加载是将类的class文件读入到内存,并将这些静态数据转换成方法区中的运行时数据结构,并在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。

3.2.2连接

连接阶段负责把类的二进制数据合并到JRE中(意思就是将java类的二进制代码合并到JVM的运行状态中)。
连接阶段又分为以下三个阶段:
1.验证:
确保加载的类信息是否符合JVM规范,有没有安全方面的问题。主要验证是否符合Class文件格式规范,并且是否能被当前的虚拟机加载处理。
2.准备:
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。

比如此时有这样一行代码:
public static int value = 123;
它是初始化 value 的 int 值为 0,而非 123。
3.解析:
虚拟机常量池的符号引用替换成直接引用的过程,也就是初始化变量的过程。 符号引用:是一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。 直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄,该引用是和内存中的布局有关的,并且一定加载进来的。

3.2.3初始化

初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实不然。 比如这样的一句代码:private static int a=10;,在准备阶段给a赋值是int类型的默认初始值0,到初始化这一阶段才会把a真正的值10赋给它。

四、类加载器的介绍

在这里插入图片描述
1.启动类加载器
它用来加载Java的核心类,是用原生代码来实现的,并不继承自java.lang.ClassLoader.由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。负责加载扩展类加载器和系统类加载器,并为他们的指定父类加载器。
2.扩展类加载器
由Java语言编写的,派生于ClassLoader类,上层类加载器为启动类加载器。它负责加载JRE/lib/ext目录下的类。
3.应用程序类加载器
加载我们写的应用程序。
4.自定义类加载器
根据自己的需求定制类加载器。

五、双亲委派模型

5.1工作原理

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

也就是每个儿子都很懒,每次有活交给父亲去干,直到父亲说这件事我也干不了的时候,儿子才自己想办法。
在这里插入图片描述

5.2双亲委派模型优点

1.避免类的重复加载:比如A类和B类都有一个父类C类,那么A启动时就会将C类加载起来,那么B类进行加载的时候就不需要重复加载C类了。
2.安全性:使用双亲委派模型可以保证Java核心API不被篡改。假设通过网络传递一个名为java.lang.Integer的类,通过双亲委派莫辛纳甘传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已经被加载了,并不会重新加载网络传递过来的java.lang.Integer类,而是直接返回已加载过的Integer.class,这样便可以防止核心API库被随便篡改。

5.3打破双亲委派模型

双亲委派模型不能向下委派,不能不委派。
打破:
SPI机制
SPI机制是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里定义的类。这一机制为很多框架扩展提供了可能,比如在JDBC中就使用到了SPI机制。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值