网上能找到讲class.forname和classloader区别的一堆文章,但多数不是自己想要的,结合别人和自己的分析,重新整理下class.forname和classloader二者区别。
说到class.forname和classloader二者,必须先要了解java类的装载过程。
1 类加载机制
1.1 双亲委派
简单来说,当一个类被类加载器加载的时候,它会先判断自己的父级能不能能够加载该class文件,它父级拿到之后也会同样的先判断自己的父级是否可以加载,直到到达顶级的启动类加载器,如果在这个过程中父级可以加载就父级加载,如果到最后父级们都不能加载自己也不能加载,就会抛出异常。
类加载器:
- 启动类加载器
- 扩展类加载器
- 应用程序类加载器
- 自定义加载器
1.2 双亲委派机制的作用
- 防止重复加载同一个
.class
。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。 - 保证核心
.class
不能被篡改。通过委托方式,不会去篡改核心.clas
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。不同的加载器加载同一个.class
也不是同一个Class
对象。这样保证了Class
执行安全。
2 类的生命周期
2.1 加载
类的加载主要干三件事
- 通过类的全限定名来获取定义此类的二进制字节流
- 将这个类字节流代表的静态存储结构转为方法区的运行时数据结构
- 在堆中生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口。
方法区不仅仅是存放方法,它存放的是类的类型信息。
2.2 链接
链接将会执行校验、准备、解析三个步骤,其中解析步骤是可选的。
2.2.1 校验
此阶段主要确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。
- 文件格式验证:基于字节流验证。
- 元数据验证:基于方法区的存储结构验证。
- 字节码验证:基于方法区的存储结构验证。
- 符号引用验证:基于方法区的存储结构验证。
2.2.2 准备
为类变量(static变量)分配内存,并将其初始化为默认值,即在方法区中分配这些变量所使用的内存空间。此时为默认值,在初始化的时候才会给变量赋值。比如下面一行代码,准备阶段完成后,a初始化为默认值0;初始化阶段完成后a的值是100。
public static int a = 100;
2.2.3 解析
解析阶段的工作就是把类型中的符号引用转换为直接引用。
- 符号引用:与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
- 直接引用:
-
直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
-
相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
-
一个能间接定位到目标的句柄
-
如果有了直接引用,那引用的目标必定已经在内存中存在。
解析主要有以下四种:
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
2.3 初始化
初始化主要就是执行类变量赋值和静态代码块。初始化阶段是执行类构造器<client>方法的过程。<client>方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证<client>方法执行之前,父类的<client>方法已经执行完毕。如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成<client>()方法。
java中,对于初始化阶段,有且只有以下五种情况才会对要求类立刻“初始化”(加载,验证,准备,自然需要在此之前开始):
- 使用new关键字实例化对象、访问或者设置一个类的静态字段(被final修饰、编译器优化时已经放入常量池的例外)、调用类方法,都会初始化该静态字段或者静态方法所在的类。
- 初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化。
- 使用java.lang.reflect包的方法进行反射调用的时候,如果类没有被初始化,则要先初始化。
- 虚拟机启动时,用户会先初始化要执行的主类(含有main)
- jdk 1.7后,如果java.lang.invoke.MethodHandle的实例最后对应的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,并且这个方法所在类没有初始化,则先初始化。
3 class.forname和classloader
3.1 class.forname和classloader的调用关系
Class.forName(String name)->
Class.forName(className, true, ClassLoader.getClassLoader(caller))->
Class.forName0(String name, boolean initialize, ClassLoader loader)->
ClassLoader.loadClass()
3.2 class.forname和classloader的区别
- class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
- Class.forName得到的class是已经初始化完成的,Classloder.loaderClass得到的class是还没有链接的。
参考