zoukankan      html  css  js  c++  java
  • java 类的加载机制

    类加载器

    类的加载是由类加载器完成的,类加载器包括:启动类加载器(BootStrap)、扩展类加载器(ExtClassLoader)、应用程序类加载器(AppClassLoader)和自定义类加载器(java.lang.ClassLoader的子类)。
    启动类加载器

    一般用本地代码实现,负责加载JVM基础核心类库,即 JAVA_HOMElib 目录下的类。
    扩展类加载器

    继承自启动类加载器,加载 libext 下的类,或者被 java.ext.dirs 系统变量指定的类。
    应用程序类加载器

    继承自扩展类加载器,加载 ClassPath 中的类,或者系统变量 java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。
    自定义类加载器

    继承自 ClassLoader 类。

    为什么要自定义类加载器

        一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。

        另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。

    类加载机制
    全盘负责

    当一个类加载器负责加载某个 Class 时,该 Class 所依赖的和引用的其他 Class 也将由该类加载器负责载入,除非显式指定另外一个类加载器来载入。
    双亲委派模型

    如果一个类加载器收到了 Class 加载的请求,它首先不会自己去尝试加载这个 Class ,而是把请求委托给父加载器去完成,依次向上。因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的 Class 时,即无法完成该加载,子加载器才会尝试自己去加载该 Class 。


    这样做的好处是:
    1. 避免同一个类被多次加载
    2. 安全,Java 核心 API 中定义的类不会被随意替换
    3. 每个加载器只能加载自己范围内的类
    缓存机制

    所有加载过的 Class 都会被缓存,当程序中需要使用某个 Class 时,类加载器先从缓存区寻找该 Class ,只有当缓存区不存在时,系统才会去读取该 Class 对应的二进制数据,并将其转换成 Class 对象,存入缓存区。

    这就是为什么修改了 Class 后,必须重启JVM,程序的修改才会生效。
    类加载器中的四个重要方法
    loadClass(String name, boolean resolve)

    protected Class<?> loadClass(String name, boolean resolve)
          throws ClassNotFoundException
      {
          synchronized (getClassLoadingLock(name)) {
              // 先从缓存查找该class对象,找到就不用重新加载
              Class<?> c = findLoadedClass(name);
              if (c == null) {
                  long t0 = System.nanoTime();
                  try {
                      if (parent != null) {
                          //如果找不到,则委托给父类加载器去加载
                          c = parent.loadClass(name, false);
                      } else {
                      //如果没有父类,则委托给启动加载器去加载
                          c = findBootstrapClassOrNull(name);
                      }
                  } catch (ClassNotFoundException e) {
                      // ClassNotFoundException thrown if class not found
                      // from the non-null parent class loader
                  }
                  if (c == null) {
                      // 如果都没有找到,则通过自定义实现的findClass去查找并加载
                      c = findClass(name);
                      // this is the defining class loader; record the stats
                      sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                      sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                      sun.misc.PerfCounter.getFindClasses().increment();
                  }
              }
              if (resolve) {//是否需要在加载时进行解析
                  resolveClass(c);
              }
              return c;
          }
      }


    流程:
    缓存 -> 父类加载器 -> 没有父类 -> 启动类加载器 -> 自己的 findClass() 方法
    findClass(String name)

    由自己负责加载类的方法。

    在自定义类加载器时,需要重写该方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的 Class 对象。
    defineClass(byte[] b, int off, int len)

    将 byte 字节流解析成 JVM 能够识别的 Class 对象。
    resolveClass(Class≺?≻ c)

    解析 Class 对象,即将字节码文件中的符号引用转换为直接引用。

    符号引用与直接引用

        符号引用:即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。

        直接引用:可以理解为一个内存地址,或者一个偏移量。

        举个例子,现在调用方法 hello(),这个方法的地址是 1234567 ,那么 hello 就是符号引用,1234567 就是直接引用。

    类加载过程

    类加载分为三个步骤:加载,连接,初始化


    加载

    根据一个类的全限定名(如 java.lang.String )来读取该类的二进制字节流,解析成 JVM 能够识别的 Class 对象。
    连接
    验证

    确保 Class 文件的字节流中包含信息符合虚拟机要求,不会危害虚拟机的安全。

    主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。
    准备

    为类的静态变量分配内存并且设置初始值,这里的初始值指的是不同类型的默认值,如 int 默认值为0,引用的默认值为 null。

    而 final 修饰的静态常量,因为 final 在编译的时候就会分配了,所以此时的值为代码中设置的值。

    注意

        类的静态变量会分配在方法区中,而实例变量是随着对象一起分配到 Java 堆中。

    解析

    将常量池内的符号引用替换为直接引用。
    初始化

    将静态变量和静态方法块按顺序从上到下初始化,即为准备阶段的静态变量重新赋值,设置为代码中指定的值。

    执行构造函数。

    如果该类具有父类,先初始化父类。
    流程图


    子类继承父类时的执行顺序



    ---------------------
    作者:路比船长
    来源:CSDN
    原文:https://blog.csdn.net/u013534071/article/details/80254247
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    windown 下最简单的安装mysql方式
    mac 重置mysql密码
    开发过程中用到的软件
    Springboot 热部署问题。亲测可用。
    时间转换~
    java 流转换BASE64的一些问题
    SpringMvc 使用Thumbnails压缩图片
    SpringMVC Get请求传集合,前端"异步"下载excel 附SpringMVC 后台接受集合
    Mac 笔记本 开发日记
    RabbitMQ入门:路由(Routing)
  • 原文地址:https://www.cnblogs.com/ldq2016/p/10442796.html
Copyright © 2011-2022 走看看