类加载机制

类加载机制

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

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

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

  • 装载
  • 链接
  • 初始化

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

类加载机制_第1张图片
类加载过程

装载

装载过程负责找到二进制字节码并加载至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

类加载机制_第2张图片
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。

你可能感兴趣的:(类加载机制)