• JVM类加载机制


    JVM类加载机制

    类加载机制是指.class文件加载到JVM,并形成Class对象的机制,之后应用就可对Class对象进行实例化并调用,类加载机制可在运行时动态加载外部的类、远程网络下载过来的class文件等。除了该动态化的优点外,还可通过JVM的类加载机制来达到类隔离的效果,例如Application Server中通常要避免两个应用的类互相干扰。

    JVM将类加载过程划分为三个步骤:装载、链接和初始化。装载和链接过程完成后,即将二进制的字节码转换为Class对象;初始化过程不是加载类时必须触发的,但最迟必须在初次主动使用对象前执行,其所作的动作为给静态变量赋值、调用()等。

    整个过程如图3.3所示。

    装载(Load)->链接(Link)[校验(Verify)->准备(Prepare)->解析(Resolve)]->初始化(Initialize)

    1. 装载(Load)

    装载过程负责找到二进制字节码并加载至JVM中,JVM通过类的全限定名(com.bluedavy. HelloWorld)及类加载器(ClassLoaderA实例)完成类的加载,同样,也采用以上两个元素来标识一个被加载了的类:类的全限定名+ClassLoader实例ID。类名的命名方式如下:

    对于接口或非数组型的类,其名称即为类名,此种类型的类由所在的ClassLoader负责加载;

    对于数组型的类,其名称为"["+(基本类型或L+引用类型类名;),例如byte[] bytes=new byte[512],该bytes的类名为:[B; Object[] bjects=new Object[10],objects的类名则为:[Ljava.lang.Object;,数组型类中的元素类型由所在的ClassLoader负责加载,但数组类则由JVM直接创建。

    2. 链接(Link)

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

    二进制字节码的格式校验遵循Java Class File Format(具体请参见JVM规范)规范,如果格式不符合,则抛出VerifyError;校验过程中如果碰到要引用到其他的接口和类,也会进行加载;如果加载过程失败,则会抛出NoClassDefFoundError。

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

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

    3. 初始化(Initialize)

    初始化过程即执行类中的静态初始化代码、构造器代码及静态属性的初始化,在以下四种情况下初始化过程会被触发执行:

    1)调用了new;

    2)反射调用了类中的方法;

    3)子类调用了初始化;

    4)JVM启动过程中指定的初始化类。

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

    JVM的类加载通过ClassLoader及其子类来完成,分为Bootstrap ClassLoader、Extension ClassLoader、System ClassLoader及User-Defined ClassLoader。这4种ClassLoader的关系如图3.4所示。

    Bootstrap Class Loader----java_home/jre/lib/rt.jar

    Extention Class Loader ----java_home/jre/lib/ext/*.jar

    System Class Loader    ----classpath

    UserDefined Class Loader

    1. Bootstrap ClassLoader

    Sun JDK采用C++实现了此类,此类并非ClassLoader的子类,在代码中没有办法拿到这个对象,Sun JDK启动时会初始化此ClassLoader,并由ClassLoader完成$JAVA_HOME中jre/lib/rt.jar里所有class文件的加载,jar中包含了Java规范定义的所有接口及实现。

    2. Extension ClassLoader

    JVM用此ClassLoader来加载扩展功能的一些jar包,例如Sun JDK中目录下有dns工具jar包等,在Sun JDK中ClassLoader对应的类名为ExtClassLoader。

    3. System ClassLoader

    JVM用此ClassLoader来加载启动参数中指定的Classpath中的jar包及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。

    例如一段这样的代码:

    
    
    1. public class ClassLoaderDemo {  
    2.  
    3.     public static void main(String[] args) throws Exception{  
    4.         System.out.println(ClassLoaderDemo.class.getClassLoader());  
    5.         System.out.println(ClassLoaderDemo.class.getClassLoader().getParent());  
    6.      System.out.println(ClassLoaderDemo.class.
    7. getClassLoader().getParent().getParent());  
    8.     }  
    9.  

    执行后显示的信息类似如下:

    
    
    1. (sun.misc.Launcher$AppClassLoader)  
    2. (sun.misc.Launcher$ExtClassLoader)  
    3. null 

    按照上面的描述,就可看到典型的System ClassLoader、Extension ClassLoader,而由于Bootstrap ClassLoader并不是Java中的ClassLoader,因此Extension ClassLoader的parent为null。

    4. User-Defined ClassLoader

    User-Defined ClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中(例如从网络上下载的jar或二进制)的jar及目录、还可以在加载之前对class文件做一些动作,例如解密等。

    JVM的ClassLoader采用的是树形结构,除BootstrapClassLoader外,其他的ClassLoader都会有parent ClassLoader,User-Defined ClassLoader默认的parent ClassLoader为System ClassLoader。加载类时通常按照树形结构的原则来进行,也就是说,首先应从parent ClassLoader中尝试进行加载,当parent中无法加载时,应再尝试从System ClassLoader中进行加载,System ClassLoader同样遵循此原则,在找不到的情况下会自动从其parent ClassLoader中进行加载。值得注意的是,由于JVM是采用类名加Classloader的实例来作为Class加载的判断的,因此加载时不采用上面的顺序也是可以的,例如加载时不去parent ClassLoader中寻找,而只在当前的ClassLoader中寻找,会造成树上多个不同的ClassLoader中都加载了某Class,并且这些Class的实例对象都不相同,JVM会保证同一个ClassLoader实例对象中只能加载一次同样名称的Class,因此可借助此来实现类隔离的需求,但有时也会带来困惑,例如ClassCastException。因此在加载类的顺序上要根据需求合理把握,尽量保证从根到最下层的ClassLoader上的Class只加载了一次。

    ClassLoader抽象类提供了几个关键的方法:

    loadClass

    findLoadedClass

    findClass

    findSystemClass

    defineClass

    resolveClass

    根据上面的描述,在实际的应用中,JVM类加载过程会抛出这样那样的异常,这些情况下掌握各种异常产生的原因是最为重要的,下面来看类加载方面的常见异常。

    1. ClassNotFoundException

    2. NoClassDefFoundError

    3. LinkageError

    4. ClassCastException

    new方法和newInstance方法的区别
    newInstance: 弱类型。低效率。只能调用无参构造。
    new: 强类型。相对高效。能调用任何public构造。

    类加载
    在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
    装载:查找和导入类或接口的二进制数据;
    链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
    校验:检查导入类或接口的二进制数据的正确性;
    准备:给类的静态变量分配并初始化存储空间;
    解析:将符号引用转成直接引用;
    初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
    Class.forName(className)
    Class.forName(className)方法,其实调用的方法是Class.forName(className,true,classloader);注意看第2个boolean参数,它表示的意思,在loadClass后必须初始化,我们可以清晰的看到在执行过此方法后,目标对象的 static块代码已经被执行,static参数也已经被初始化。
    ClassLoader.loadClass(className)
    再看ClassLoader.loadClass(className)方法,其实他调用的方法是ClassLoader.loadClass(className,false);还是注意看第2个 boolean参数,该参数表示目标对象被装载后不进行链接,这就意味这不会去执行该类静态块中间的内容
    总述
    class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
    而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块

  • 相关阅读:
    Jmeter csv文件进行参数化的两种方法
    Jmeter逻辑控制器: If控制器的解读
    Selenium问题集锦
    Jmeter BeanShell
    Jmeter进行接口流程测试
    服务器上部署Struts2的web项目报struts-default.xml:131:154的解决方法
    jmeter用Windows电脑分布式部署
    JMeter-一个接口的返回值作为输入传给其他接口:设置全局变量和非全局变量
    Flutter调研-Flutter基础知识、安装与demo
    MAC上安装maven以及配置Intellij IDEA
  • 原文地址:https://www.cnblogs.com/chendezhen/p/16086212.html
Copyright © 2020-2023  润新知