Java中的类加载过程

类加载概念

类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。

类加载过程

类从被加载到虚拟机内存开始,到卸载出内存为止,它的生命周期包括以下7个阶段:
在这里插入图片描述

  • 加载: 通过类的全限定名(包名+类名),获取该类的.class文件的二进制字节流。将二进制字节流所代表静态存储结构,转化为方法区运行时的数据结构。在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。(这个阶段需要用到加载器,双亲委派模型与此有关。)
  • 连接: 验证、准备、解析3个阶段统称为连接
  • 验证: 确保class文件中的字节流包含的信息,符合当前虚拟机的要求,保证被加载的class类的正确性。(毕竟class文件的产生途径不清楚,如果完全信任的话,很可能载入了有错误的字节码流导致系统崩溃)验证阶段大致会完成四个阶段的检验动作:文件格式校验、元数据验证、字节码验证、符号引用验证。
  • 准备: 为类中的静态字段分配内存,并设置默认的初始值,比如int类型初始值是0。被final修饰的static字段不会设置,因为final在编译的时候就分配了。
  • 解析: 这个阶段是虚拟机将常量池的符号引用直接替换为直接引用的过程。(符号引用是以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用的时候可以无歧义地定位到目标即可。直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄,直接引用是和虚拟机实现的内存布局相关的。如果有了直接引用,那引用的目标必定已经存在在内存中了。)
  • 初始化: 是类加载过程的最后一阶段。准备阶段时,静态变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据代码编写的实际情况去初始化类变量和其它资源。简单来说就是执行类的构造器方法
  • 使用: 使用类或者创建对象。
  • 卸载: 如果有下面的情况,类就会被卸载:1. 该类所有实例都已经被回收,也就是java堆中不存在该类的任何实例。2. 加载该类的ClassLoader已经被回收。3. 类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类。

类加载器

在类加载流程的第一步“加载”阶段,通过一个类的全限定名来获取描述该类的二进制字节流,实现这一功能的代码叫做类加载器

Java中的任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有独立的类名称空间

判断两个类是否相等,必须在这两个类是被同一加载器加载的前提下才有意义。否则即使Class文件相同,被同一个虚拟机加载,但是使用不同的类加载器,那么两个类也是不同的。

双亲委派模型

如果一个类被不同的加载器加载,那么虚拟机会认定是不同的类,这样Java体系最基础的行为也无从保证,应用程序会变成混乱。
所以有了双亲委派模型,双亲委派一共分了四层的加载器。除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,子类使用“组合”关系来复用父类加载器的代码,而不是继承。
在这里插入图片描述

  • 启动类加载器: 加载Java核心类库,无法被java程序直接引用。负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的类。
  • 扩展类加载器: 用来加载Java的扩展库。负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。
  • 应用类加载器: 根据Java的类路径来加载类,负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。
  • 自定义加载器: 用户自行扩展。可以让用户加载任何来源的class文件。

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。 每一个层次的类加载器都是如此,因此所有的类加载请求都会传送到最顶层的启动类加载器中,只有当父加载器无法完成这个加载请求时,子加载器才会尝试自己去完成加载。

为什么需要双亲委派模型?

前面已经提到:每个类加载器都有属于自己的命名空间。 一个类如果被不同的类加载器加载,将被认为是不同的类,这将给程序带来混乱。
所以需要在默认情况下,一个限定名的类只会被一个类加载器加载并解析使用,这样在程序中它就是唯一的,不会产生歧义。所以需要双亲委派模型来是实现这种需求。

  • 避免类的重复加载。
  • 保证Java程序安全稳定运行,Java核心API定义类型不会被随意替换。

为了防止内存中出现多个相同的字节码,因为如果没有双亲委派的话,用户就可以自己定义一个java.lang.String类,那么就无法保证类的唯一性。

不同的类加载器,除了读取二进制流的动作和范围不一样,后续的加载逻辑是否也不一样?

我们认为除了Bootstrap ClassLoader,所有的非Bootstrap ClassLoader都继承了java.lang.ClassLoader,都由这个类的defineClass进行后续处理。

遇到限定名一样的类,这么多类加载器会不会产生混乱?

越核心的类库被越上层的类加载器加载,而某限定名的类一旦被加载过了,被动情况下,就不会再加载相同限定名的类。这样,就能能够有效避免混乱。

打破双亲委派模型的例子

SPI机制:各产商的服务通过SPI机制注册到JDK提供的接口上,SPI机制的ServiceLoader扫描配置文件得到类的全限定名,ServiceLoader使用当前线程context中的类加载器(默认为Application Classloader),所以第三方类能被正常加载。

对JDK代码包中的加载使用的是Bootstrap ClassLoader,但调用JDK中的接口时,接口所在的类会引起第三方类库的加载,会调用Application Classloader,这就不符合自下而上的委派加载顺序,而是上层类加载器委派下层类加载器去加载类的情况,这就产生了对双亲委派模型的破坏。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值