java的热部署/热更新(2)类加载机制

类加载机制

JVM角度类加载实现

我们先复习下JVM角度类加载器的简要知识。C代表类实体,N代表类名称,L代表类加载器。C1<N1,L1>代表被N1类名称的加载器被L1初始加载生成的C1类。

  • 无论是从什么地方进入类加载,考虑到类加载器可以不断代理,最后都有一个真实的类加载器。JVM会记录这个类加载器。这个类加载器被称为类的 initiating loader
  • 类加载器的实现往往有查找缓存这一步。底层的实现往往是java.lang.ClassLoader#findLoadedClass0这个native方法。注意,只有类的initiating loader能使用该方法找到他。
  • 如果C1<N1,L1>引用了C2<N2,L2>,那么要保证
    • L1可以找到C2(也就是说L1能把N2委托给L2加载)
    • C1引用的C2方法,其method descriptor中设计的N,必须在L1和L2的加载下生成相同的C(即实际由共同的类加载器加载)

具体可以参考。
https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.3.4
https://docs.oracle.com/javase/specs/jvms/se10/html/jvms-5.html

在文档中,规范提出了三个基本原则

  • Given the same name, a good class loader should always return the same Class object.
    对于相同的类名称,一个类加载器必须每次返回相同的类对象

  • If a class loader L1 delegates loading of a class C to another loader L2, then for any type T that occurs as the direct superclass or a direct superinterface of C, or as the type of a field in C, or as the type of a formal parameter of a method or constructor in C, or as a return type of a method in C, L1 and L2 should return the same Class object.

也就是我们上文说的原则

  • If a user-defined classloader prefetches binary representations of classes and interfaces, or loads a group of related classes together, then it must reflect loading errors only at points in the program where they could have arisen without prefetching or group loading.

涉及预先的情况。

知道这些,我们才能在JDK提供最简单双亲委派类加载机制后设计更复杂的类加载机制。

类加载机制最困难的就是类的状态性。更直白的说,就是类的static的字段。

JDK中类加载实现

自定义类加载器可以覆盖ClassLoader中的loadClass。包括内部BuiltinClassLoader也是继承该类。

Class<?> loadClass(String cn, boolean resolve)

其中,其中boolean resolve表示是否解析该类。在JDK9后目前resolveClass的默认实现为空。用户可以自行定义。如果你不需要自行定义,可以忽略该参数。

如果不覆盖,则必须实现findClass。在UrlClassLoader中,findClass首先会getResource获取对应的类文件,解析class文件,然后调用底层函数

  static native Class<?> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len,
                                        ProtectionDomain pd, String source);

    static native Class<?> defineClass2(ClassLoader loader, String name, java.nio.ByteBuffer b,
                                        int off, int len, ProtectionDomain pd,
                                        String source);

另外,希望大家注意Class.forName的实现和loadClass不同。

   private static native Class<?> forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class<?> caller)
        throws ClassNotFoundException;

改逻辑会永远会检查缓存是否有该类。
如果缓存中,则不进入实际的loadClass。

常见的类加载方案

tomcat

我们看tomcat,是一个简单的顶层类优先方案。
在这里插入图片描述
这个类加载方案看起来很完美。但是不可避免的有一个问题,就是部分底层类是共用的。在tomcat的环境下,就是一些java基础类库。会在不同的webapp下共用。这当然不是什么问题。java基础类库遵循无状态的设计原则。webAppA不会因为对某个底层class的状态改动而导致webAppB得到和其单独运行时不同的结果。

但是当我们设计我们热部署的时候,往往不满足这个假设。特别是在这个错误的单例模式泛滥的年代。大量字段被设计成了static字段。比如一个DataSource类里面用static字段维持着对若干个mysql的链接,按照mysql的某种alias进行查找。如果两个热部署实体拥有相同的alias的不同mysql链接,就会造成相互影响。诚然,我们可以要求这个DataSource类的作者修改代码,但是在实际工作中,我们往往没有规范和审查他人写代码的权力和时间。

osgi

在这里插入图片描述
简述这个逻辑,仍然是一个基础类由框架加载器加载,其他类遵循先找其他bundle,再找本bundle的逻辑。和tomcat区别不大。当然也解决不了我们上面的问题。

JDK9引入的机遇模块的热加载机制

一个实用的类加载机制

在这里插入图片描述

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值