ClassLoader

博客地址:liuzhengyangjava

Java中的ClassLoader

先抛出几个问题git

  • 类初始化 static{} 块会在何时执行
  • Class在方法区内的结构
  • 不一样类加载器加载一个类,类初始化块会被执行几回,不一样ClassLoader加载的类存放在哪里,是否指向同一个Class实例
  • 类A触发了类B的加载,那么类B的加载器是什么
  • 如何实现热部署,即在运行时改变类的行为,或类替换
  • Class.forName(name) 作了什么

回答这些问题仍是要参考JDK的代码实现,另外还要依靠Java语言规范和Java 虚拟机规范github

本篇文章用类来泛指类和接口bootstrap

背景介绍

类加载器是用于加载Class的机制,Class能够以文件的形式或者是二进制流的形式存在,通常 按照最多见的Class文件来称呼。数据结构

<!-- -->ide

ClassLoader负责加载类,java.lang.ClassLoader类是一个抽象类, 能够经过一个类的二进制名称,来定位类在哪里,并生成class的数据定义。 最多见的策略是将类的名称转换成文件名并从文件系统中读取Class文件.测试

每一个Class对象都会有指向定义它的ClassLoader的引用,经过Class#getClassLoader() 能够得到。url

类的二进制名(binary name)是Java语言规范规定的,常见的有:spa

java.lang.String
test.loader.Test$Test2  // 静态内部类

Class文件能够存在jar包中,能够以目录形式存放。设计

加载、连接、初始化过程

  • 若是要加载一个类的时候发现类没有被加载过(就是JVM中没有这个类的二进制表示)就会使用类加载器尝试加载这个二进制表示。
  • 连接阶段设计验证、准备和解析。
  • 验证用于验证字节码格式是否正确。
  • 准备阶段会进行static字段的建立,并将这些字段初始化成默认值。这个阶段不会执行任何代码。static 字段的初始化会在初始化阶段执行。
  • 一些JVM实现会在准备阶段预先计算一些附加数据结构来使以后的类操做更加有效。一个特别的结构就是方法表或其余的数据结构,能够在让任意的调用在实例上的方法无需搜索父类。类的二进制表示经过符号引用引用其余类、字段、方法构造器等。对于字段和方法,符号引用包括字段、方法所在类的名字以及字段和方法本身的名称。在符号引用使用前必需要进行解析,解析阶段会对符号引用进行检查,并一般会替换成直接引用。若是解析失败,会抛出场景的NoSuchMethodError等错误。

Java中常见的ClassLoader

Java的类加载结构有bootstrap class loader用来加载$JAVA_HOME/jre/lib下载的 rt.jar中的文件,其中是Java的核心类.Bootstrap classloader是JVM中的实现, 若是要在ClassLoader中表示其为父类,用null表示。 另外有ExtClassLoader加载lib/ext文件夹下的jar包 AppClassLoader是用来加载ClassPath目录下的jar包和Class文件。 常说这三者是父类关系,并非Java中的集成关系,而是ClassLoader中定义的 parent.

委托机制

ClassLoader文件在第一次加载类的时候会先委托其父加载器加载,若是加载失败再本身加载。 这样,一些关键的类,如String等就不会遭到篡改。可是在J2EE中,一个Web容器下 可能有多个应用,每一个应用加载时有子类优先的需求,这时就须要覆盖默认的逻辑。 代码逻辑在ClassLoader类的loadClass(String name, boolean resolve) 方法中.ClassLoader#loadClass调用的是ClassLoader#loadClass(name, false) resolve表示的是是否进行连接步骤。 去掉一些不相关代码,loadClass方法的逻辑以下

类加载锁
synchronized (getClassLoadingLock(name)) {
            //  查看是否已经被加载过,会调用一个native方法判断
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                // 若是没加载过,则会进入加载过程
                try {
                    // 若是parent不是null,则说明是Java中的类加载器
                    if (parent != null) {
                        // 调用parent的loadClass方法递归向上加载
                        c = parent.loadClass(name, false);
                    } else {
                        // 若是是null,说明是Bootstrap class loader,
                        // 则使用Bootstrap加载器加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                // 都没加载成功,则本身进行加载
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    // 调用findClass,会找到class的字节流而后使用
                    // defineClass()来定义类
                    c = findClass(name);
                }
            }
            if (resolve) {
                // 若是要连接,则进行连接,其中有验证、准备和符号引用解析过程
                resolveClass(c);
            }
            return c;
        }

另外,Class的静态方法Class.forName(className)也可以完成类的加载工做, 返回一个Class对象。会对类进行初始化,使用调用Class.forName的方法所在的类的类加载器 来加载。 Class.forName(className, initialize, loader) 方法会经过参数控制由是否进行初始化和由哪一个类加载器加载。

ClassLoader中的loadResource方法

咱们在写程序时,常常会在classpath下放置一些配置文件,在运行时读取配置文件的内容 能够经过Class.getResource(), 例如

Test.class.getResourceAsStream("/config.properties")
这个方法会委托Test类的加载器来进行加载资源

类加载器与类

当咱们判断两个Class对象是不是相等时,或判断是不是集成关系时,须要看它们的类加载器是不是同一个。 类加载器和类,组成了Class对象的标识。 测试代码

自定义的类加载器,修改默认的委托机制。
    private static class MyClassLoader extends URLClassLoader {
        public MyClassLoader(URL[] urls) {
            super(urls);
        }

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return defineClass(name);
        }
    }
    public static class Class1 {
        static {
            System.out.println("Initialize class 1");
            Class2.doSay();
            Class<Class2> class2Class = Class2.class;
            System.out.println("Class2 Loader is " + class2Class.getClassLoader());
        }
        public static void say() {

        }
    }
    public static class Class2 {
        static {
            System.out.println("Initialize Class2");
        }
        public static void doSay() {
            System.out.println("Say");
        }
    }

    public static void main(String[] args) throws Exception {
        URL path = ClassLoaderTest.class.getResource("/");
        URL rtPath = Object.class.getResource("/");

        MyClassLoader myClassLoader = new MyClassLoader(new URL[]{path, rtPath});
        Class<?> class1 = myClassLoader.loadClass("classloader.ClassLoaderTest$Class1");
        Class<?> aClass = Class.forName("classloader.ClassLoaderTest$Class1", true, myClassLoader);
        Class<?> aClass2 = Class.forName("classloader.ClassLoaderTest$Class1");

        System.out.println(class1);
        System.out.println("Class 1 classLoader is " + class1.getClassLoader());
        System.out.println(aClass);
    }

可以看出,类A触发类B的初始化时,会用类A的加载器去加载。

何时回触发类的初始化

  • 建立类的实例,经过new 或者Class.newInstance
  • 类的初始化方法被调用 invokestatic
  • 类的static字段被绑定 putstatic
  • 类的static字段被使用,而且不是常量
  • 一个类被初始化的时候,它的父类会被先初始化 咱们看到,用两个加载器加载一个类,初始化块被执行了两次。
相关文章
相关标签/搜索