类加载机制

作者: 甚了 | 来源:发表于2017-02-21 00:26 被阅读49次

    类加载机制

    类加载机制是指 .class文件加载到JVM,并形成Class对象的机制。

    类加载机制可以在运行时动态的加载外部的类、远程网络下载过来 class 文件等。除了动态的优点wai外,还可以通过JVM的类加载机制来达到类隔离的效果。

    JVM将类加载划分为三个步骤:

    • 装载
    • 链接
    • 初始化

    装载和链接完成后就将二进制的字节码转换为 Class 对象;而初始化过程并不是加载类时必须触发的(为什么呢),但是最迟必须在初次主动使用对象前执行。

    类加载过程类加载过程

    装载

    装载过程负责找到二进制字节码并加载至JVM中,JVM通过类的全限定名类加载器完成类的加载,同样,也采用全限定名类加载器来标识一个被加载了的类:类的全限定名 + ClassLoader实例ID

    类名的命名方式如下:

    • 对于接口或非数组型的类,其名称即为类名,此种类型的类由所在的ClassLoader负责加载;
    • 对于数组型的类,其名称为“[”+(基本类型|L)+引用类型类名;)
    byte[] bytes = new byte[512];
    System.out.println(bytes.getClass().getName());
    
    Object[] objects = new Object[10];
    System.out.println(objects.getClass().getName());
    
    String[] strings = new String[10];
    System.out.println(strings.getClass().getName());
    # Output ---
    [B
    [Ljava.lang.Object;
    [Ljava.lang.String;
    
    

    链接

    一:校验 链接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量及解析类中调用的接口和类。

    校验过程中如果碰到要引用到其他的接口和类,也会进行加载;如果加载过程失败,则会抛出NoClassDefFoundError。

    二:准备 在完成了校验后,JVM初始化类中的静态变量,并将其值赋为默认值。

    三:解析 最后对类中的所有属性、方法进行验证,以确保其要调用的属性、方法存在,以及具备相应的权限(例如public、private域权限等)。如果这个阶段失败,可能会造成NoSuchMethodEr-ror、NoSuchFieldError等错误信息。

    初始化

    初始化过程即执行类中的静态初始化代码、构造器代码及静态属性的初始化。

    以下四种情况下初始化过程会被触发执行:

    1. 调用了new;
    2. 反射调用了类中的方法;
    3. 子类调用了初始化;
    4. JVM启动过程中指定的初始化类。

    在执行初始化过程之前,首先必须完成链接过程中的校验和准备阶段,解析阶段则不强制。

    JVM的类加载通过ClassLoader及其子类来完成:

    • Bootstrap ClassLoader 在代码中没有办法拿到这个对象,Sun JDK启动时会初始化此ClassLoader,并由ClassLoader加载$JAVA_HOME/jre/lib/rt.jar里所有class文件;
    • Extension ClassLoader JVM用此ClassLoader来加载扩展功能的一些jar包 $JAVA_HOME/jre/lib/ext/*.jar;
    • System ClassLoader(AppClassLoader) JVM用此ClassLoader来加载启动参数中指定的Classpath中的jar包及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader;
    • UserDefined ClassLoader 继承Class-Loader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中(例如从网络上下载的jar或二进制)的jar及目录、还可以在加载之前对class文件做一些动作(例如解密等)。
    public class ClassTest {
    
        @Test
        public void testClassLoader() {
            System.out.println(ClassTest.class.getClassLoader());
            System.out.println(ClassTest.class.getClassLoader().getParent());
            System.out.println(ClassTest.class.getClassLoader().getParent().getParent());
        }
    }
    
    # Output ---
    sun.misc.Launcher$AppClassLoader@3836b1bb
    sun.misc.Launcher$ExtClassLoader@ece88d2
    null
    
    
    ClassLoader继承关系ClassLoader继承关系

    JVM的ClassLoader采用的是树形结构,除BootstrapClass-Loader外,其他的ClassLoader都会有parent ClassLoader,UserDefined ClassLoader默认的parent ClassLoader为System ClassLoader。加载类时通常按照树形结构的原则来进行,也就是说,首先应从 UserDefined ClassLoader中尝试进行加载,当par-ent中无法加载时,应再尝试从System ClassLoader中进行加载,System ClassLoader同样遵循此原则,在找不到的情况下会自动从其parent ClassLoader中进行加载。

    JVM是采用类名+Classloader的实例来作为Class加载的判断的,因此加载时不采用上面的顺序也是可以的,例如加载时不去parent ClassLoader中寻找,而只在当前的ClassLoader中寻找,会造成树上多个不同的ClassLoader中都加载了某Class,并且这些Class的实例对象都不相同。

    当Java开发人员调用Class.forName来获取一个对应名称的Class对象时,JVM会从方法栈上寻找第一个ClassLoader,通常也就是执行Class.forName所在类的ClassLoader,并使用此ClassLoader来加载此名称的类。


    ClassNotFoundException 这是最常见的异常,产生这个异常的原因为在当前的ClassLoader中加载类时未找到类文件,对位于System ClassLoader的类很容易判断,只要加载的类不在Classpath中。而对位于UserDefined ClassLoader的类则麻烦些,要具体查看这个ClassLoader加载类的过程,才能判断此ClassLoader要从什么位置加载到此类。例如直接在代码中执行Class.forName(“com.blue-davy.A”),而当前类的classloader下根本就没有该类所在的jar或没有该class文件,就会抛出ClassNotFoundException。

    NoClassDefFoundError 该异常较之ClassNotFoundException更难处理一些,造成此异常的主要原因是加载的类中引用到的另外的类不存在,如下:要加载A,而A中调用了B,B不存在或当前ClassLoader没法加载B,就会抛出这个异常。当采用Class.forName加载A时,虽能找到A.class,但此时B.class不存在,则会抛出NoClassDefFoundError。

    public class A {  
    private B b=new B();
        
    }
    

    ClassCastException 这个异常比较难查的是两个A对象由不同的ClassLoader加载的情况,这时如果将其中某个A对象造型成另外一个A对象,也会报出ClassCastException。

    相关文章

      网友评论

        本文标题:类加载机制

        本文链接:https://www.haomeiwen.com/subject/hvrywttx.html