1、Java 中,在调用类的静态成员,或新建该类的对象等之前,类一定要先装入 Java 虚拟机中,这是勿庸置疑的。但虚拟机怎样把类装载进来的呢?要经过三步:装载(Load),链接(Link),初始化(Initializ)。其中链接又可分为校验(Verify),准备(Prepare),解析(Resolve)三步。一、装载(Load)ClassLoader 就是用来装载的。通过指定的 className,找到二进制码,生成 Class 实例,放到 JVM 中。ClassLoader 从顶向下分为 Bootstrap ClassLoader、Extension ClassLoader、System Cl
2、assLoader 以及 User-Defined ClassLoader(分叉,可以多个)。如下图。这是 Tomcat 装载器的例子:装载过程从源码清析可见:protected synchronized Class loadClass(String name, boolean resolve)throws ClassNotFoundException/ 先检查是否已被当前 ClassLoader 装载。Class c = findLoadedClass(name);if (c = null) try if (parent != null) / 如果没被当前装载,则递归的到父中装载。c = p
3、arent.loadClass(name, false); else / 装载器树已到顶,还没找到的话就到 Bootstrap 装载器中找。注意:虽然Bootstrap 是所有加载器的根,但它是 C+实现的,不可能放到子的“parent“中,因此,第二层装载器是所有的根了。c = findBootstrapClass0(name); catch (ClassNotFoundException e) / 如果祖先都无法装载,则用当前的装载。子类可在 findClass 方法中调用defineClass,把从自定义位置获得的字节码转换成 Class。c = findClass(name);if (
4、resolve) / Links the specified class.resolveClass(c); / 注return c;注:1. resolveClass(c)方法的注释是链接类,而不只是解析,从该即可看出。调用 resolveClass 时语义上是去链接,是否真的链接了我不是很清楚,但可以肯定的是没有初始化。当 A 类中有 static B b=new B()时,最晚会在初始化时去装载 B。如果改成 static B b=null,那么把 B.class 删掉后,即使 A 已经链接,初始化过了,但也不会报错,也就是 A 所引用的 B 类没有被加载过。解析时难道没有真的去装入它所引
5、用的 B 类?还是链接时,没有执行解析的步骤? 问题的关键就是 1.对“解析”的理解,解析时是否会去装载 B 类?2. JVM 在链接时是否执行了解析?(毕竟有资料说,解析是可选步骤)二、链接链接就是把 load 进来的 class 合并到 JVM 的运行时状态中。链接 是三个阶段中最复杂的一个。可以把它分成三个主要阶段: 校验。 对二进制字节码的格式进行校验,以确保格式正确、行为正确。 准备。 准备类中定义的字段、方法和实现接口所必需的数据结构。比如会为类中的静态变量赋默认值(int 等:0, reference:null, char:u0000)。 解析。 装入类所引用的其他所有类。可以用
6、许多方式引用类: 超类 接口 字段 方法签名 方法中使用的本地变量三、初始化Initialization of a class consists of executing its static initializers and the initializers forstatic fields (class variables) declared in the class. Initialization of an interface consists of executing the initializers for fields (constants) declared there.类的初
7、始化包括:执行静态区块和静态方法的初始化。比如下面这两种代码都会被执行,包括 new B()。static.static B b=new B();接口中不允许有 static initializer(也就是 static.),所以对于接口,只会执行静态字段的初始化。初始化前,装载,链接一定已经执行过!类初始化前,它的直接父类一定要先初始化(递归),但它实现的接口不需要先被初始化。类似的,接口在初始化前,父接口不需要先初始化。什么情况下,类的初始化会被触发?A class or interface type T will be initialized immediately before the
8、 first occurrence of any one of the following: T is a class and an instance of T is created. T is a class and a static method declared by T is invoked. A static field declared by T is assigned. A static field declared by T is used and the field is not a constant variable (4.12.4). T is a top-level c
9、lass, and an assert statement (14.10) lexically nested within T is executed.当使用类的字段时,即便可以通过子类或子接口访问该字段,但只有真正定义该字段的类会被触发初始化。如下例。class Super static int taxi = 1729; class Sub extends Super static System.out.print(“Sub “); class Test public static void main(String args) System.out.println(Sub.taxi);只会输
10、出“1729”,不会输出“Sub“,也就是说,Sub 其实没有被初始化。四、Class.forName()与 ClassLoader.loadClass()这两方法都可以通过一个给定的类名去定位和加载这个类名对应的 java.long.Class 类对象,区别如下:1. 初始化Class.forName()会对类初始化,而 loadClass()只会装载或链接。可见的效果就是类中静态初始化段及字节码中对所有静态成员的初始工作的执行(这个过程在类的所有父类中递归地调用). 这点就与 ClassLoader.loadClass()不同. ClassLoader.loadClass()加载的类对象是
11、在第一次被调用时才进行初始化的。你可以利用上述的差异. 比如,要加载一个静态初始化开销很大的类, 你就可以选择提前加载该类(以确保它在 classpath 下), 但不进行初始化, 直到第一次使用该类的域或方法时才进行初始化2. 类加载器可能不同Class.forName(String) 方法(只有一个参数), 使用调用者的类加载器来加载, 也就是用加载了调用 forName 方法的代码的那个类加载器。当然,它也有个重载的方法,可以指定加载器。 相应的, ClassLoader.loadClass()方法是一个实例方法(非静态方法), 调用时需要自己指定类加载器, 那么这个类加载器就可能是也可能不是加载调用代码的类加载器(调用代用代码类加载器通 getClassLoader0()获得)