JVM类加载机制

JVM类加载机制

关于JVM的类加载机制,可以理解为:把编译完成的 .class文件,从文件硬盘加载到内存中(元数据区)这样的一个过程,最终要获取到类对象。这个过程,可以大致理解为以下几个步骤:

加载:把 .class 文件找到,打开文件,读文件,把文件内容读到内存中;

验证:检查 .class 文件格式对不对,.class文件是一个二进制文件,此处的检查是根据官方提供的 JVM 虚拟机规范,它会说明描述 .class 的规范;

准备:给类对象分配内存空间(在元数据区占用一个空间) ,内存初始化成全0,为类中定义的静态变量分配内存,并设置初始值也为0;

解析: 初始化字符串常量,把符号引用转成直接引用;

符号引用转成直接引用:对于字符串常量来说,是需要有一块内存空间,来存这个字符的实际内容的,还需要有一个引用,来保存这个内存空间的起始地址,而在类加载过程中,字符串常量是存储在 .class 文件中的,而不是内存上的,此时的这个 "引用" 记录的不是字符串常量的真正地址,而是它在文件中的一个偏移量,这个偏移量就理解为 "符号引用";

在类加载之后,才真正的把这个字符串常量放在内存中,此时才是真正拥有内存地址,此时的内存地址就理解为 "直接引用" ;

初始化: 针对类对象里面的内容进行初始化,加载父类,执行父类静态代码块,父类静态变量赋值,子类静态代码块,子类静态变量赋值,父类初始化模块,父类普通变量,父类构造器,子类初始化模块,子类普通变量,子类构造器...

一个类什么时候会被加载呢?

并不是Java程序一运行,就把所有的类都加载了,而是等到了真正用到的时候,才会去加载,属于是懒汉模式,一般会有以下三种情况:

  1. 构造类的实例;
  2. 调用了这个类的静态方法,或者使用到了这个类的静态属性;
  3. 加载子类的时候,通常会先加载其父类;
    一般是用到了才会进行类加载,并且加载过一次后,后续再使用也不需要进行重复加载了;
    双亲委派模型

双亲委派模型,描述的是JVM类加载机制中的 加载 过程,也就是找 .class 文件的过程;

JVM 默认提供了三个类加载器:

  1. BootstrapClassLoader:负责加载标准库中的类;
  2. ExtensionClassLoader:负责加载 JVM 扩展库中的类;
  3. ApplicationClassLoader: 负责加载用户提供的,也就是用户代码中的类;
    在这三个类加载器中,存在一个 "父子关系" --- 每个 ClassLoader 类中会有一个 parent 属性来标识自己的父加载器:
    ApplicationClassLoader 的 parent 是 ExtensionClassLoader;
    ExtensionClassLoader 的 parent 是 BootstrapClassLoader;
    类加载器的加载过程

当加载一个类的时候,是从 ApplicationClassLoader 开始的,但它会把当前的加载任务交给父亲去做,也就是让 ExtensionClassLoader 去加载,然后 ExtensionClassLoader 也把任务交给父加载器,也就是 BootstrapClassLoader 去加载,此时 BootstrapClassLoader 的 parent 是 null 了,也就只能自己去加载,此时 BootstrapClassLoader 就要搜索自己负责的标准库中的类,如果能找到,就加载,如果找不到,就再返回交给 ExtensionClassLoader 去加载,同样的道理,如果 ExtensionClassLoader 在自己负责的 JVM 扩展库中找到了对应的类,就加载,如果找不到,就再返回给 ApplicationClassLoader 去加载,如果找到了就加载,如果到最后都没有找到,那么就会抛出 类找不到 这样的一个异常;

就大致是一个这样的执行过程:

之所以以这样的一个加载顺序,让标准库先加载,然后再扩展库,最后再是用户写的类,是为了防止用户可能会写出一些标准库或者扩展库已经存在的类的,就比如 java.lang.String,而这个是标准库中已经存在的类的,按照上述的加载顺序,JVM 最后加载出来的就是标准库中的类,而不是用户自己写的;

另一方面,类加载器也是可以自定义的,以上三个是 JVM 自带的,用户自定义的类加载器,也可以加入到上述的流程中使用,自己写的类加载器,可以遵守双亲委派模型,也可以选择不遵守,例如 Tomcat,去加载 webapp 这里就是单独的类加载器,不遵守双亲委派模型;

相关推荐
Chrikk1 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*1 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue1 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man1 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟1 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity2 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天2 小时前
java的threadlocal为何内存泄漏
java
caridle3 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^3 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋33 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx