JVM——类加载机制

类文件结构

  • class文件是一组以8个字节为基础单位的二进制字节流(可能是磁盘文件,也可能是类加载器直接生成的)各个数据项严格按照顺序排列,中间没有任何符号间隔
  • class文件采取一种类似于c语言结构体的储存结构,只有两种数据类型:无符号数和表
  • 无符号数属于基本数据类型,以u1、u2、u4和u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串
  • 表是由多个无符号数获取其他表作为数据项构成的复合数据类型,习惯用“_info”结尾
  • 无论是无符号数还是表,当需要描述同一个类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时称这一系列连续的某一类型的数据为某一类型的集合。
    在这里插入图片描述

类的加载

类加载机制的定义: 虚拟机将描述类信息的.class文件(二进制字节流)读取到内存,并对数据进行校验、转换解析和初始化(即将类信息放在方法区中,并在堆中创建.class对象,用来封装在方法区中的类的数据结构),最终形成可被虚拟机直接使用的java类型的过程。

类加载时机

运行期进行加载,即类型的加载、连接、初始化过程都在程序的运行期完成。

编译期:编译器将源代码翻译为机器能识别的代码,java呗编译为Jvm认识的字节码文件

运行期:java代码运行过程

JVM并不是在首次主动使用到这个类的时候才去进行类的加载,JVM规范允许类加载器预期即将使用到这个类时进行加载,如果在预期加载过程中遇到了.class文件缺失或者错误,类加载器必须在程序首次主动使用该类时才会报告错误,如果这个类一直没有被程序主动使用,那么一直不会报错。

在何处加载.class文件

类的加载过程使用类的加载器实现,同时我们也可以通过继承ClassLoader基类自主实现类的加载器,不同的加载器可以从不同来源加载类的二进制数据,通常来源有以下几种:

  • 从本地系统中直接加载
  • 通过网络下载.class文件
  • 从zip,jar等归档文件中加载.class文件
  • 从专有的数据库中提取.class文件
  • 将java源文件动态编译为.class文件

类的生命周期

  • 类的生命周期:
    在这里插入图片描述
  • 解析在某些情况下可以在初始化阶段开始之后,为了支持java的动态的绑定
  • 需要注意的是这几个阶段是按顺序开始的,并不是按顺序进行或者结束的,通常是互相交叉的混合进行,通常在一个阶段执行的过程中调用或激活另一个阶段

加载

这一阶段主要是将类的class文件加载进内存,并为之创建一个java.lang.class对象的过程。
此阶段的可控性比较强(可以由用户自己定义的类加载器进行加载)加载阶段一般由类加载器完成,一般完成三件事情:

通过类的全限定名获得其定义的二进制字节流

将这个字节流所代表的静态结构转化为方法区的运行时数据结构

在内存中生成代表这个类的class对象,作为方法区中这个数据的访问入口

验证

这一阶段主要是对加载进内存的class文件进行验证,确保文件信息不会威胁到虚拟机的安全。主要分为以下四种验证过程:

  • 文件格式验证

验证字节流是否符合class文件格式规范,能够被当前虚拟机加载处理,如:主次版本号是否在当前虚拟机处理范围之内,常量池中是否有不支持的类型等等

  • 元数据验证(元数据:数据的数据,一般用来描述代码之间的关系)

对字节码描述的信息进行语义分析,保证其描述的信息符合java语言规范,如:验证这个类是不是有父类,方法字段与父类是否有冲突等

  • 字节码验证

整个验证过程中最复杂的阶段,在元数据验证阶段完成对数据类型的验证后,这个验证阶段主要对类的方法做出分析,保证类的方法不会威胁到虚拟机的安全。

  • 符号引用验证

验证的最后阶段,主要发生在将符号引用转化为直接引用的过程中,确保解析动作可以完成。

准备

此阶段主要为类的变量分配内存并设置初始默认值(如果为final修饰的静态变量则直接为设置的初始值),内存都在方法区进行分配。以下为几种列举类型的初始值:
在这里插入图片描述

需要注意的是:此时是为类变量分配内存而不是实例变量,实例变量随着对象的初始化时进行分配


解析

此过程主要将常量池中的符号引用转化为直接引用,在方法区中进行。
特点:时间不可预料,有可能和初始化交换位置

为什么此阶段的时间具有不可预料的特质?

JVM的实现大多是延迟加载,如在加载A类的过程中,类A中引用了类B,虚拟机并不会将类B加载进来,而是等到执行的时候才去加载,而类B在类A中的表现形式被记录在符号表中,而解析的过程就是将符号表中的类B表现形式换为直接引用,这也是时间上发生不可预料的原因。

初始化

此阶段JVM才会真正执行java中的代码。一般在以下情况发生时会触发类的初始化:

  • 创建类的实例,也就是new一个对象
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(Class.forName(“…”))
  • 初始化一个类的子类(会首先初始化子类的父类)
  • JVM启动时标明的启动类,即文件名和类名相同的那个类 (一般main方法所在类)

不会导致类的初始化的情况:

  • 使用类的final修饰的静态常量(基本数据类型和字符串类型)
  • 创建类的数组不会进行类的初始化
  • 访问类的.class对象

通过以上情况我们可以实现类的懒惰初始化的单例模式:

public class Person {
    private Person(){}
    private static class Name {
         static final Person person = new Person();
    }
    public Person getInstance() {
        return Name.person;
        //只有在此时调用Name类的静态变量,才会进行初始化,创建单例对象
    }
}


类加载器

类加载器分为三类:启动类加载器、扩展类加载器、应用程序类加载器

启动类加载器:加载java的核心类(位于JAVA_HOME/jre/lib目录下的类),用原生代码来实现,并不继承自java.lang.ClassLoader,无法直接访问,用C++代码编写

扩展类加载器:加载位于JAVA_HOME/jre/lib/ext目录下的类

应用程序类加载器:加载classpath目录下的类

双亲委派模型

调用loadclass时,查找类的规则

比较两个类是否相等,只有当两个类是由同一个类加载器加载的前提下才有意义。

在这里插入图片描述

  • 除了启动类加载器之外,其他类加载器均有父类加载器
  • 当一个类收到加载请求时,就会先交给父类加载器,所有的加载最终都会给启动类加载器,当父类加载器无法加载时,子类加载器才会尝试自己去加载。

为什么使用双亲委派机制加载?

  • 保证java基本类的进行,比如我们自定义了Object类,并定义加载器为自定义加载器,这时就会使得系统有多个同名类,使得基础的java类系统混乱,而使用双亲委派模型,无论我们在怎样的类加载环境中,Object类永远都是由启动加载器加载的,都是同一个类。

双亲委派模型的实现

(1)检查类是否被加载
(2)没有加载的话,调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。
(2)如果父类加载依然失败的话,抛出ClassNotFoundException异常后,调用自身加载器的findClass()方法进行加载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值