虚拟机类加载机制

目录

引言

类加载的周期

类加载器

类与类加载器

双亲委派模型

破坏双亲委派模型


引言

如前文所述:java中.class文件的一些知识

Class文件(本质是一串具有特定格式的二进制流)一旦生成,JVM如何加载这些文件呢?这就需要讲到虚拟机的类加载机制。

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


类加载的周期

notes:

  • 五个确定顺序步骤:其中颜色标注的步骤,顺序确定。类加载必须按照这个顺序进行。
  • 解析阶段:不一定如图所示,可能在初始化之后之后开始。原因:为了支持java语言的动态绑定(将一个方法调用同一个方法主体关联起来被称为绑定。若在程序执行前进行绑定,叫做前期绑定(静态绑定),而在运行时根据对象的类型进行绑定,叫做后期绑定,也叫做动态绑定或运行时绑定。Java中除了static方法和final方法(private方法属于final方法)之外,其它的方法都是后期绑定。 ---《Java编程思想》,具体参考如何理解java中的动态绑定和静态绑定?)。

类从被加载到虚拟机内存开始,到卸载出内存为止,其生命周期如下:

  • 加载

是“类加载”的一个阶段。虚拟机完成事件:

1) 通过类的全限定名获取定义此类的二进制字节流(获取方式多样);

  • 包;
  • 网络,典型引用Applet;
  • 运行时计算生成,如动态代理;
  • 其他文件生成;
  • 数据库读取。。。

2) 将该字节流代表的静态存储结构转化为方法区的运行时数据结构;

3)在内存中生成一个该类的.Class对象,作为该类在方法区内的各种数据访问入口。

有且只有5种情况必须立即对类进行“初始化”:

  • 验证

确保class文件中字节流符合JVM格式要求;

  • 准备

为变量(只包括类变量,即被static修饰的变量)分配内存,设置类变量初始值(通常是指数据类型的零型),这些变量使用的内存在方法区中进行分配。

  • 解析

虚拟机将常量池中的符号引用 --> 直接引用。

什么是符号引用和直接引用?


类加载器

类加载器的作用是将类的信息以一定格式传给JVM,使其能够对其进行操作。

 


类与类加载器

两个类相同 《==》这两个类是被同一个类加载器加载的。

 

 


双亲委派模型

双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。

  • 双亲委派模型好处

首先,通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。

另外,通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。

那么,就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。

工作流程:

代码实现逻辑:

 


破坏双亲委派模型

作者:Hollis
链接:https://zhuanlan.zhihu.com/p/343563937
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

知道了双亲委派模型的实现,那么想要破坏双亲委派机制就很简单了。

因为他的双亲委派过程都是在loadClass方法中实现的,那么想要破坏这种机制,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可。

loadClass()、findClass()、defineClass()区别

ClassLoader中和类加载有关的方法有很多,前面提到了loadClass,除此之外,还有findClass和defineClass等,那么这几个方法有什么区别呢?

  • loadClass() 就是主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中。
  • findClass() 根据名称或位置加载.class字节码
  • definclass() 把字节码转化为Class

这里面需要展开讲一下loadClass和findClass,我们前面说过,当我们想要自定义一个类加载器的时候,并且像破坏双亲委派原则时,我们会重写loadClass方法。

那么,如果我们想定义一个类加载器,但是不想破坏双亲委派模型的时候呢?

这时候,就可以继承ClassLoader,并且重写findClass方法。findClass()方法是JDK1.2之后的ClassLoader新添加的一个方法。

/**

     * @since  1.2

     */

    protected Class<?> findClass(String name) throws ClassNotFoundException {

        throw new ClassNotFoundException(name);

    }

这个方法只抛出了一个异常,没有默认实现。

JDK1.2之后已不再提倡用户直接覆盖loadClass()方法,而是建议把自己的类加载逻辑实现到findClass()方法中。

因为在loadClass()方法的逻辑里,如果父类加载器加载失败,则会调用自己的findClass()方法来完成加载。

所以,如果你想定义一个自己的类加载器,并且要遵守双亲委派模型,那么可以继承ClassLoader,并且在findClass中实现你自己的加载逻辑即可。

双亲委派被破坏的例子

双亲委派机制的破坏不是什么稀奇的事情,很多框架、容器等都会破坏这种机制来实现某些功能。

第一种被破坏的情况是在双亲委派出现之前。

由于双亲委派模型是在JDK1.2之后才被引入的,而在这之前已经有用户自定义类加载器在用了。所以,这些是没有遵守双亲委派原则的。

第二种,是JNDI、JDBC等需要加载SPI接口实现类的情况。

第三种是为了实现热插拔热部署工具。为了让代码动态生效而无需重启,实现方式时把模块连同类加载器一起换掉就实现了代码的热替换。

第四种时tomcat等web容器的出现。

第五种时OSGI、Jigsaw等模块化技术的应用。

为什么JNDI,JDBC等需要破坏双亲委派?

我们日常开发中,大多数时候会通过API的方式调用Java提供的那些基础类,这些基础类时被Bootstrap加载的。

但是,调用方式除了API之外,还有一种SPI的方式。

如典型的JDBC服务,我们通常通过以下方式创建数据库连接:

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");

在以上代码执行之前,DriverManager会先被类加载器加载,因为java.sql.DriverManager类是位于rt.jar下面的 ,所以他会被根加载器加载。

类加载时,会执行该类的静态方法。其中有一段关键的代码是:

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

这段代码,会尝试加载classpath下面的所有实现了Driver接口的实现类。

那么,问题就来了。

DriverManager是被根加载器加载的,那么在加载时遇到以上代码,会尝试加载所有Driver的实现类,但是这些实现类基本都是第三方提供的,根据双亲委派原则,第三方的类不能被根加载器加载。

那么,怎么解决这个问题呢?

于是,就在JDBC中通过引入ThreadContextClassLoader(线程上下文加载器,默认情况下是AppClassLoader)的方式破坏了双亲委派原则。

我们深入到ServiceLoader.load方法就可以看到:

public static <S> ServiceLoader<S> load(Class<S> service) {

        ClassLoader cl = Thread.currentThread().getContextClassLoader();

        return ServiceLoader.load(service, cl);

    }

第一行,获取当前线程的线程上下⽂类加载器 AppClassLoader,⽤于加载 classpath 中的具体实现类。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值