class.forName()除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。
先了解下类加载机制,
5个过程。
a.加载:java类运行时候会生成一个class字节码文件,加载的过程就是去我们的操作系统寻找这个class文件。而获取.class文件的方式,可以通过jar包、war包、网络中获取、JSP文件生成等方式。
b.链接:又分为3个小过程。
1.验证:验证被加载后的类是否有正确的结构,类数据是否会符合虚拟机的要求,确保不会危害虚拟机安全。
2.准备:为类的静态变量(static filed)在方法区分配内存,并赋默认初值(0值或null值)。如static int maxNum = 10;
静态变量maxNum 就会在准备阶段被赋默认值0;而对于一般的成员变量是在类实例化时候,随对象一起分配在堆内存中。静态常量(static final filed)会在准备阶段赋程序设定的初值,如static final int maxNum = 50; 静态常量a就会在准备阶段被直接赋值为50,对于静态变量,这个操作是在初始化阶段进行的。
3.解析:解析是将符号引用替换为直接引用,解析动作针对类或接口,字段,类或接口的方法进行解析。
首先是用类加载器加载这个类。在加载的过程中逐步解析类中的字段和方法。
符号引用是以字面量的实形式明确定义在常量池中,直接引用是指向目标的指针,或者相对偏移量。
c.初始化:类初始化是类加载的最后一步,除了加载阶段,用户可以通过自定义的类加载器参与,其他阶段都完全由虚拟机主导和控制。主要工作是为静态变量赋程序设定的初值。如static int maxNum = 10;在准备阶段,a被赋默认值0,在初始化阶段就会被赋值为10。
当初始化一个类的时候,如果发现其父类没有进行过初始化,则首先触发父类初始化。
d.使用:在需要使用的地方调用
e.卸载:使用完了,java虚拟机进行清理。
类的加载的时机,有以下5中情况
1 遇到new、getstatic、putstatic或invokestatic这四条字节码指令
2 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
4 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
5当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则会先触发其初始化。
上面介绍了类加载的几个过程,再具体看下Class.forName和classloader的区别。用代码演示。
先定义一个类
package com.test.a;
public class User {
private static int a = 5;
private static int b;
private static int c = testC();
static {
System.out.println("执行静态代码块");
System.out.println("执行静态变量a="+a);
}
public static void testA() {
System.out.println("执行静态代方法testA");
}
public static void testB() {
b = 10;
System.out.println("输出静态变量b="+b);
System.out.println("执行静态代方法testB");
}
public static int testC() {
System.out.println("执行静态代方法testC");
return 20;
}
}
分别使用Class.forName和classloader加载,查看结果
package com.test.a;
public class TestClassLoad {
public static void main(String[] args) {
try {
//Class.forName加载
String userPath = "com.test.a.User";
Class<?> forName = Class.forName(userPath);
System.out.println("-------------------forName加载--------------------"+forName);
//classLoader加载
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
classLoader.loadClass(userPath);
System.out.println("-------------------classLoader加载--------------------");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
可以看到classloader只是做了加载,没有对类初始化,而Class.forName加载了静态变量和静态代码块,但是并没有加载静态方法。只有在静态变量调用了静态方法赋值的情况下才会加载静态方法。
看下Class.forName源码分析,他实际上也是调用了classloader去加载,但因为他的第二个参数指定了true,所以会去初始化类
要想不初始化,调用他的重载方法Class.forName(userPath,false,ClassLoader.getSystemClassLoader());将第二个参数设置为false,指Class被加载后是不是必须被初始化。