1、概述
java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
2、类的生命周期
类从加载到虚拟机内存中开始,到卸载处内存为止,其整个生命周期如下图所示:
上图中,类的加载、验证、准备、初始化和卸载的顺序是确定的,解析的阶段则有可能发生在类的初始化阶段之后进行,这是为了支持java语言的运行时绑定。
在每个阶段,都实现了相应的功能:
加载阶段:获取类的二进制字节流,并转换为方法区的运行时数据结构,在java堆中生成一个代表该类的java.lang.Class对象,作为方法区这些数据的访问入口。
验证阶段:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全。比如作Class文件结构的验证等。
准备阶段:为类变量分配内存并设置类变量初始值,内存的分配在方法区中进行,注意这里不包括实例变量。比如:
public static int count=5;
准备阶段过后,count的初始值为0,而非5,5的赋值是在初始化阶段。需要注意如果是final类型的,则在该阶段初始为相应值。
解析阶段:在该阶段,会进行类、接口、方法以及字段的解析工作。
初始化阶段:开始执行字节码文件,执行类构造器<client>()方法。
<client>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,收集顺序由语句在源文件中出现的顺序决定的。
<client>()方法与类的构造方法<int>()不同,它不需要显示调用父类构造器,虚拟机会自动的先执行父类的 <client>()方法。
3、类加载器
在类的生命周期的加载阶段,是开发期可控性最强的阶段,因为在该阶段,可以使用系统提供的类加载器,也可使用用户自定义加载器。
首先要明白,比较两个类是否相同的前提条件是:两个类使用同一个类加载器。否则,即使同一个class文件,但加载器不同,结果两个类肯定不相同。
下图是类加载器的双亲委派模型:
启动类加载器(Bootstrap ClassLoader),加载的类库包括<JAVA_HOME>\lib目录中的。
扩展类加载器(Extension ClassLoader),由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>\lib\ext目录中的类库。
应用程序类加载器(Application ClassLoader),由sun.misc.Launcher$AppClassLoader实现,负责加载classpath上所指定的类库,如果程序中没有自定义类加载器, 则应用程序类加载器为程序默认的类加载器。
双亲委派模型的工作过程如下:
如果一个类加载器收到了类加载的请求,首先它不会亲自去尝试加载这个类,而是把该请求委派给父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
看如下的代码:
package cn.com.yy;
/**
* 类加载器
* @author yy
* @time 2014-10-1 上午9:42:10
*/
public class TestClassLoader {
public static void main(String[] args) {
TestClassLoader obj = new TestClassLoader();
Class c = obj.getClass();
ClassLoader loader = c.getClassLoader();
System.out.println(loader);
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
//默认的类加载器
ClassLoader defaultLoader = ClassLoader.getSystemClassLoader();
System.out.println(defaultLoader);
}
}
执行结果为:
sun.misc.Launcher$AppClassLoader@1c0ec97
sun.misc.Launcher$ExtClassLoader@ecb281
null
sun.misc.Launcher$AppClassLoader@1c0ec97
可以看出,默认的类加载时应用程序类加载器。当使用loader.getParent().getParent()时,返回的是null,因为Bootstrap ClassLoader是用C语言实现的,找不到一个确定的返回父ClassLoader的方式,因此返回null。
下面的源代码是jdk1.7中的的java.lang.ClassLoader的loadClass方法:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
首先检查请求的类是否被加载过,如果没有被加载过,继续执行,如果父类加载器存在,则调用父类加载器的loadClass,否则调用启动类加载器findBootstrapClassOrNull。如果父类加载失败,跑出ClassNotFoundException异常,再调用自己的额findClass方法进行加载。
因此,当实现自己的类加载器时,推荐重写findClass方法里面的逻辑,而非loadClass方法。