Java类加载机制浅谈

来源:互联网 发布:手机刷机救砖软件 编辑:程序博客网 时间:2024/05/21 20:28

Android的热修复技术在去年因为大厂们的支持和分享所以出现了一些不同的解决方案,如QQ空间补丁方案、阿里AndFix以及微信Tinker,我们查阅相关资料后会发现QQ和微信的解决方案其实原理是一样的,都是基于Google推出的的Multidex方案,以ClassLoader的方式完成问题类的替换,谈到ClassLoader我们就不得不了解下Java的类加载机制了。

  • 类的加载过程
    JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)链接又分为三个步骤
    这里写图片描述
    1. 装载:
        查找并加载类的二进制数据;
    2. 链接:
        验证:确保被加载类信息符合JVM规范、没有安全方面的问题。
        准备:为类的静态变量分配内存,并将其初始化为默认值。
        解析:把虚拟机常量池中的符号引用转换为直接引用。
    3. 初始化:
        为类的静态变量赋予正确的初始值。
        常量池:Java 中,虚拟机会为每个加载的类维护一个常量池【不同于字符串常量池,这个常量池只是该类的字面值(例如类名、方法名)和符号引用的有序集合。 而字符串常量池,是整个JVM共享的】这些符号(如int a = 5;中的a)就是符号引用,而解析过程就是把它转换成指向堆中的对象地址的相对地址。在准备阶段初始化默认的值就是0。
          
  • 类的初始化工作

    1. 创建类的实例,也就是new一个对象
    2. 访问某个类或接口的静态变量,或者对该静态变量赋值
    3. 调用类的静态方法
    4. 反射(Class.forName(“com.xxx.android.activity.xx”))
    5. 初始化一个类的子类(会首先初始化子类的父类)
    6. JVM启动时标明的启动类,即文件名和类名相同的那个类(包含static void main(String[] args)的那个类)
  • 类的加载器
    我们在ide里面写的各种Java文件编译成class文件后,类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,最终被jvm放在位于堆区中的Class对象,执行这个过程的是由类加载器完成的。这里我们普通说的是把.class文件中二进制数据读入内存,其实还有以下几张方式,很多技术都是在这里切入,因为它并没有限定二进制流从哪里来,例如:

    1. 从zip包中来->加载jar中的类
    2. 从网络中来->Applet
      ……….

    ClassLoader:是Java层几乎所有类加载器的父类,根本它的功能基本上可以分为四大类:

    1. 启动类加载器(Bootstrap ClassLoader):

     这个类加载器负责将<JAVA_HOME>\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并  不继承于java.lang.ClassLoader,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分.

    2.扩展类加载器(Extendsion ClassLoader):

     这个类加载器负责加载<JAVA_HOME>\lib\ext目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器.

    3. 应用程序类加载器(Application ClassLoader):

     应用程序类加载器负责加载用户类路径中的文件。用于加载我们自己定义编写的类。

    4. 用户自定义类加载器(Custom ClassLoader):

     属于应用程序根据自身需要自定义的ClassLoader,例如QQ空间补丁方案等等。
  • 类的双亲委派模型

    这里写图片描述

    结论:

    简单来说就是,先检查需要加载的类是否已经被加载过,如果没有则请求委派上一级进行判断,逐次递归向上一级委派,这个过程是从下到上;如果走到顶级Bootstrap ClassLoader判断还是没有加载过,那么就从Bootstrap开始往下级尝试进行加载,这个过程是从上到下。
    如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。

    好处:

    1.以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。

    2.安全,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

    JVM如何判定两个类你是否相等:

    JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。

日常开发中我们基本接触不到需要自己定义类加载器的需求,因为jvm自带的几大类加载器已经完全可以满足我们的开发需求,但是需要我们动态加载外部的class文件的时候就会用到,Google官网为了解决Android4.x系统中65k方法数限制,推出Multidex解决方案,将一个完整的APK中的Dex拆分成好几个dex,通过PathClassLoader 这个加载器来加载,QQ空间开发正是利用了这一点在Google定义的这个PathClassLoader 类加载器的加载过程中做了一些文章,通过网路下载到修复的.class文件替换了原来同类名的.class文件从而在线修复了bug。当然还有其他热修复解决方案例如阿里百川HotFix 等,读者可以自己查资料学习,这里就不说了。

0 0
原创粉丝点击