本文转自:类的生命周期 Class.forName和ClassLoader..loadClass比较
类生命周期概况
JVM为java程序提供运行时环境(runtime environment),管理类和对象的生命周期是JVM的重要工作之一。
类的生命周期从类被加载、连接和初始化开始到类被卸载结束。
只有当类在生命周期中时,才能被使用,比如调用类的静态方法或者创建类的实例。
类的加载、连接和初始化
类的生命周期从加载、连接和初始化开始。并且严格按照如下步骤进行:
1. 加载:查找,并且加载类的二进制数据
2. 连接
1. 验证:确保类被正确加载
2. 准备:为类的静态变量分配内存,并且初始化为默认值
3. 解析:把类中的符号引用转化为直接引用
3. 初始化:为类的静态变量赋予正确的初始值。
加载
类加载的过程如下:
1. 将类对应.class文件中的二进制数据读到内存中,把它存放在运行时数据区的方法区内;
2. 在堆取创建一个Class实例,此实例
a) 描述了方法区内的数据结构
b) 提供了访问类在方法区内数据结构的接口,如下图所示:
可以简单的理解为:类加载的产品是一个Class类的实例。
连接
连接指将已经读入内存的二进制数据合并到虚拟机的运行时环境中去。包括验证、准备、解析三个步骤。
验证
被加载的.class文件并不一定是java编辑器生成的(比如黑客可以制作自定义结构的.class文件),所以jvm才需要对.class文件进行验证,保证被加载的类有正确的内部结构,并且与其他类协调一致。如果jvm检查到错误,那么抛出Error对象。以此来提高程序的健壮性。
准备
准备阶段为类的静态变量分配内存并且赋默认值。
解析
解析阶段,jvm把类的二进制数据中的符号引用替换为直接引用。比如,dog.run();这行代码,run这个方法是被dog这个符号(变量名)引用,解析过程将dog这个符号替换为一个指针地址(直接引用)。
初始化
执行指静态变量生命和静态代码块。
卸载
当类被加载、连接、初始化后,生命周期就开始了,java程序可以正常使用该类(实例化,调用方法等),当描述此类的Class对象不可触及(触不可及为对象生命周期中的概念),Class对象将结束生命周期,此类方法区内的数据将被卸载,此类的生命周期结束。
分割线-----------------------------------------------------------
Class.forName(className)实际上是调用Class.forName(className, true, this.getClass().getClassLoader())。注意第二个参数,是指Class被loading后是不是必须被初始化。
ClassLoader.loadClass(className)实际上调用的是ClassLoader.loadClass(name, false),第二个参数指出Class是否被link。
一般情况下,这两个方法效果一样,都能装载Class。但如果程序依赖于Class是否被初始化,就必须用Class.forName(name)了。
例如,在JDBC编程中,常看到这样的用法,Class.forName("com.mysql.jdbc.Driver"),如果换成了getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),就不行。
为什么呢?打开com.mysql.jdbc.Driver的源代码看看,
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
原来,Driver在static块中会注册自己到java.sql.DriverManager。而static块就是在Class的初始化中被执行。所以这个地方就只能用Class.forName(className)。