美文网首页
深入理解JVM之类加载

深入理解JVM之类加载

作者: 田真的架构人生 | 来源:发表于2017-08-15 09:54 被阅读0次

    JVM将类加载过程分为三个步骤:加载、链接(验证、准备、解析)、初始化。


    类加载流程

    一,加载(loading)
    有两种时机会触发类加载:
    1,预加载。虚拟机启动时加载,加载的是JAVA_HOME/lib/rt.jar下的class,这里面都是一些常用的Class,如java.lang.String、java.util.List等。
    2,运行时加载。虚拟机需要用到一个Class时,先去内存中查找是否被加载,如果没有就会按照类的全限定名来加载这个类。
    JVM通过类的全限定名(com.sun.HelloWorld)及类加载器(ClassLoader实例)完成类的加载,相应地,也是用这两个元素来标识一个被加载了的类:类的全限定名 + ClassLoader实例ID。
    加载的时候,通过class文件二进制流,将类信息、静态变量、字节码、常量这些.class文件中的内容放入方法区中。然后在内存中生成一个代表这个.class文件的java.lang.Class对象,在HotSpot虚拟机中,这个Class对象是放在方法区中的。

    二,验证
    确保class文件内容符合虚拟机的规范,比如验证“魔数”是否为0xCAFEBABE、Class文件编译版本号是否符合当前JVM等。JDK能向下兼容版本号,但不能向上兼容。即JDK1.6能运行在JDK1.5环境编译的class,但是反过来不行。如果格式不符则抛出VerifyError。
    如果碰到要引用其它接口或类,也会进行加载,如果加载过程失败,则会抛出NoClassDefFoundError。

    三,准备
    为类变量在方法区分配内存并初始化。需要注意的是:这个阶段初始化的变量指的是不被final修饰的static变量,如”public static int i = 100;”,i在准备阶段过后是0而不是100,给i赋值为100的动作将在初始化阶段才进行;但是”public static final int i = 100;”就不一样了,在准备阶段,就会给i赋值为100。

    四,解析
    将常量池中的符号引用替换为直接引用,有哪些符号引用?
    类和接口的全限定名
    字段的名称和描述符
    方法的名称和描述符

    五,初始化
    初始化过程即执行类中的静态初始化代码、构造代码及静态属性的初始化。虚拟机会保证类的初始化在多线程环境中被正确地加锁、同步。以下几种情况会触发初始化过程:
    1,调用new
    2,反射调用了类中的方法
    3,子类调用了初始化
    4,JVM启动过程中指定的初始化类
    另外,有几种不会触发类初始化的情况:
    1,子类引用从父类继承下来的静态字段,不会导致子类初始化(只会初始化父类)
    2,通过数组定义引用类,不会触发此类的初始化(new User[10])
    3,引用静态常亮(static final常量在编译阶段就已进入类的常量池)

    四,类加载器
    JVM的类加载通过ClassLoader及其子类来完成,分为Bootstrap ClassLoader、Extension ClassLoader、System ClassLoader及User-Defined ClassLoader,除了Bootstrap外,剩下3个都是有父子层级关系的。
    Bootstrap,此类不是ClassLoader的子类,代码中无法拿到这个对象,JDK启动时会初始化该ClassLoader,并由它加载$JAVA_HOME/jre/rt.jar中所有class的加载,这些class是java规范定义的所有接口及实现(java.lang, java.util等)。
    Extension加载$JAVA_HOME/jre/ext/*.jar。
    System加载启动参数中指定的Classpath中的jar,简言之,用户写的class会由它加载,Sun JDK中对应的实现类为AppClassLoader
    User-Defined ClassLoader是开发人员自行实现的ClassLoader,可用于加载非classpath中的jar,如从网络上下载的jar或二进制。
    之前说过,ClassLoader是有父子层级关系的,当加载class时,首先会去已加载的class中查找,如果有,直接返回,否则判断parentClassLoader是否存在,存在,调用parent . loadClass,不存在,调用findBootstrapClassOrNull。如果最后还是找不到,则调用findClass,该方法默认直接throw new ClassNotFoundException,需要ClassLoader子类自己覆盖。
    但加载时也可以不采用以上顺序,可以直接在当前的ClassLoader中寻找,如Class.forName(),JVM会直接通过当前ClassLoader(也就是执行Class.forName所在类的ClassLoader,caller.getClassLoader0() native方法)加载Class。
    由于JVM是采用类全限定名+ClassLoader实例来判断是否加载了某个类,所以,如果直接从当前classLoader直接加载的话,
    会造成树上多个不同的classloader中都加载了某class(某处Class.forName,某处直接new),且这些Class的实例对象都不同。JVM会保证同一个ClassLoader实例中只能加载一次同样名称的Class,因此可以借助此来实现类隔离的需求。
    但也会有问题,例如ClassCastException,因此在加载类时,尽量保证从根到最下层的ClassLoader上的Class只加载一次。
    当ClassLoader在整个树中都没找到Class对象,则抛出ClassNotFoundException

    类加载的常见异常:
    1,ClassNotFoundException:ClassLoader加载类时未找到该类。
    2,NoClassDefFoundError:在加载的类中引用到其它的类不存在,或者加载引用的类失败。
    3,ClassCastException:两个对象的Class有不同的ClassLoader加载。

    相关文章

      网友评论

          本文标题:深入理解JVM之类加载

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