类加载机制
类加载机制是指 .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等错误信息。
初始化
初始化过程即执行类中的静态初始化代码、构造器代码及静态属性的初始化。
以下四种情况下初始化过程会被触发执行:
- 调用了new;
- 反射调用了类中的方法;
- 子类调用了初始化;
- 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继承关系
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。
网友评论