首页 > 代码库 > java类加载机制

java类加载机制

  类加载机制是指将class文件加载到JVM,并形成class对象的机制,之后应用就可以对class对象进行实例化并调用,类加载机制可在运行时动态加载外部的类、远程网络下载过来的class文件等。除了该动态化的特殊外,JVM的类加载机制会对不同的应用起到隔离作用,以免相互影响。

  JVM类加载分为三步:装载、链接和初始化。装载和链接是将二进制的字节码转换成class对象;初始化过程不是加载类时必须触发的,但是在初次主动使用对象前必须进行初始化。

 装载

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

 链接

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

  二进制字节码的格式机校验遵循java class file format规范,如果格式不符合,则抛出VerifyError;校验过程中如果碰到引用到其他的接口和类,也会进行加载;如果加载过程失败,则会抛出NoClassDefFoundError。

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

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

 初始化

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

  1.调用了new;

  2.反射调用了类加的方法;

  3.子类调用了初始化;

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

 

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

  JVM的类加载通过ClassLoader及其子类来完成,分为Bootstrap ClassLoader、Extension ClassLoader、System ClassLoader及User-Defined ClassLoader。

  Bootstrap ClassLoader

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

  Extension ClassLoader

  JVM用此ClassLoader来加载扩展功能的一些jar包,jre/lib/ext/*.jar。在Sun JDK中ClassLoader对应的类名为ExtClassLoader。 

  System ClassLoader

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

  User-Defined ClassLoader

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

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

  

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

 loadClass

  此方法负责加载指定名字的类,classLoader的实现方法为先从已经的类中寻找,如滑,则继续从parent ClassLoader中寻找;如果仍然没找到,则从system ClassLoader中寻找,最后再调用findClass方法来寻找;如果要改变类的加载顺序,则可覆盖此方法;如果加载顺序相同,则可通过覆盖findClass来做特殊的处理,例如解密、固定路径寻找等。当通过整个寻找类的过程仍然未获取Class对象时,则抛出ClassNotFoundException。

  findLoadedClass

 此方法负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调的为native的方法。

  findClass

 此方法直接抛出classNotFoundException,因此要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。

  findSystemClass

 此方法负责从system ClassLoader中寻找类,如未找到,则继续从Bootstrap ClassLoader中寻找,如果仍然未找到,则返回null。

 defineClass

 此方法负责将二进制的字节码转换为class对象,这个方法对于自定义加载类而言非常重要。如果二进制的字节码的格式不符合JVM Class文件的格式,则抛出ClassFormatError;如果生成的类名和二进制字节码中的不同,则抛出NoClassDefFoundError;如果加载的class是受保护的、采用不同签名的,或者类名是以java开头的,则抛出SecurityException;如果加载的class在此ClassLoader中已加载,则抛出LinkageError。

  resolveClass

 此方法负责完成class对象的链接,如果链接过,则会直接返回。

 当java开发人员调用Class.forName来获取一个对应名称的class对象时,JVM会从方法栈上寻找第一个ClassLoader,通常也就是执行Class.forName所在类的ClassLoader,并使用此ClassLoader来加载此名称的类。JVM为了保护加载、执行的类的安全,它不允许Classloader直接卸载加载了的类,只有JVM才能卸载,在SunJDK中,只有当Classloader对象没有引用时,此ClassLoader对象加载的类才会被卸载。 

java类加载机制