jvm的类加载机制

JVM中,类从被加载到虚拟机内存中开始,到卸载出内存为止
它的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载7个阶段

类的生命周期

加载(Loading):

  • 通过一个类的全限定名来获取定义此类的二进制字节流
    二进制来源:zip包中获取、网络中获取、运行时计算生成、由其他文件生成、代理类的二进制流

  • 将二进制字节流代表的静态存储结构转化为方法区的运行时数据结构

  • 在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类各种数据的访问入口

验证(Verification)

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

准备(Preparation)

  • 正式为类变量分配内存并设置初始值

解析(Resolution)

  • 把间接引用(符号引用)转换为直接引用
    符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。例如:在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
    直接引用:
    (1)直接指向目标的指针(比如,指向“类型”[Class对象]、类变量、类方法的直接引用可能是指向方法区的指针)
    (2)相对偏移量(例如:指向实例变量、实例方法的直接引用都是偏移量)
    (3)一个能间接定位到目标的句柄
    ps.直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了

初始化(Initialization)

  • 执行(),初始化类变量、静态代码块

使用(Using)

卸载(Unloading)

Java类加载器

  • 启动类加载器(Bootstrap ClassLoader)
  • 扩展类加载器(Extension ClassLoader)
  • 应用程序类加载器(Application ClassLoader)
  • 自定义类加载器(User ClassLoader)

双亲委派模型

  • 双亲委派模型,就是加载类的时候,先请求其父类加载器去加载,如果父类加载器无法加载类,再尝试自己去加载类。如果都没加载到,就抛出异常
    在这里插入图片描述

  • 双亲委派模型缺陷:
    双亲委派模型中,子类加载器可以使用父类加载器已经加载的类,而父类加载器无法使用子类加载器已经加载的。这就导致了双亲委派模型并不能解决所有的类加载器问题。比如:Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JNDI、JAXP 等,这些SPI的接口由核心类库提供,却由第三方实现,这样就存在一个问题:SPI 的接口是 Java 核心库的一部分,是由BootstrapClassLoader加载的;SPI实现的Java类一般是由AppClassLoader来加载的。BootstrapClassLoader是无法找到 SPI 的实现类的,因为它只加载Java的核心库。它也不能代理给AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题。

如何破坏双亲委派模型

  • 先说一下为什么打破双亲委派:
    举一个例子:数据库驱动Driver接口,Driver定义在jdk当中,但最终实现的是各个数据库服务商。比如说,mysql的MYSQL CONNECROR,所以这就有个问题,DriverManger要加载各个Driver接口实现类,然后进行管理,但是DriverManager是由启动类加载器进行加载的,而这个启动类加载器默认值加载JAVA_HOME下面的lib,但我们真正要加载的是各个实现类,需要有系统类加载器进行加载,这个时候就需要启动类加载器委托系统类加载器去加载Driver实现类,从而破坏了双亲委派
  • 打破方法
    1. 自定义类加载,重写loadclass方法
      其实双亲委派的机制都是通过这个方法实现的,这个方法可以指定类通过什么类加载器来进行加载,所有如果修改了他的加载规则,那么就打破了原有的双亲委派机制
      重写loadClass()方法也就有可能连带把findClass(方法也重写)
    2. 使用线程上下文类加载器
      双亲委派模型的第二次“破坏”是由这个模型自身的缺陷所导致的,而双亲委派也很好的解决了各个类加载器的基础类统一问题。基础类之所以“基础”,是因为他们总被用户代码所调用,但是如果基础类又要重新调用用户代码,如何考虑?
      ——例子说明一下,如jndi是java的标准服务,它的代码是由启动类加载器进行加载的,但是jndi的作用就是进行资源的集中管理和查找,它需要调用由开发人员开发在classpath下的类代码,但是启动类加载器不会进行加载。所以引入线程上下类加载器,通过java.lang.Thread类的setContextClassLoader()方法进行设置。如果创建线程是还未设置,它会从父线程继承一个,如果在应用程序全局范围内没有设置,那么这个线程上下类加载器就是应用程序类加载器。那么这样JNDI服务使用这个线程上下类加载器去加载所需的spi代码,也就是父类加载器请求子类加载器去完成类加载的动作,这个实际是打通了双亲委派的逆向层次结构,从而破坏原有的双亲委派模型
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值