Java类加载器及其委托机制

概要:
简单的说,类加载器就是JVM用来把类加载到内存中的工具。

类加载器本质上也是Java类,所以,类加载器本身也需要被JVM来加载,那么谁来加载这些类加载器呢?在JVM中,系统默认有三个类加载器:BootStrap, ExtClassLoader, AppClassLoader。其中BootStrap是采用C++编写的一段二进制代码,它嵌套在JVM内核中,当JVM启动时,这个类就会被启动,JVM用它来加载其它的类加载器。

一、类加载器加载类的过程

Java程序运行的场所是内存,在命令行下执行:
Javac  ClassLoaderTest.java   将java文件编译成.class文件
Java   ClassLoaderTest       JVM会将ClassLoaderTest.class加载到内存中,并形成一个Class的对象ClassLoaderTest.class。

其中的过程就是类加载过程:
1、寻找jre目录,寻找jvm.dll,并初始化JVM;
2、产生一个Bootstrap Loader(启动类加载器);
3、BootstrapLoader自动加载ExtClassLoader(标准扩展类加载器),并将其父Loader设为Bootstrap Loader。
4、BootstrapLoader自动加载AppClassLoader(系统类加载器),并将其父Loader设为ExtClassLoader。
5、最后由AppClassLoader加载ClassLoaderTest类。

以上就是类加载的最一般的过程。

二、类加载器的运行委托机制

委托机制:
1、运行一个程序时,总是由AppClassLoader(系统类加载器)开始加载指定的类。
2、在加载类时,每个类加载器会将加载任务上交给其父,如果其父找不到,再由自己去加载。
3、BootstrapLoader(启动类加载器)是最顶级的类加载器了,其父加载器为null.
4、如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。
5、当所有父类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFoundException,不是再去找发起者类加载器的子类加载器

看下面一段代码:

package com.cndqe.test;

public class ClassLoaderTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		ClassLoader loader = ClassLoaderTest.class.getClassLoader();
		while(loader != null){
			System.out.println(loader.getClass().getName());
			loader = loader.getParent();
		}
		System.out.println(loader);
	}

}

运行结果:

sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null

从上面的结果可以看出,AppClassLoader加载当前线程的类,而AppClassLoader的父加载器是ExtClassLoader,而ExtClassLoader的父类加载器是BootStrap,打印null的原因是Bootstrap(启动类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式,于是就返回null


三、类加载器之间的父子关系和管辖范围

Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载。

BootStrap -> ExtClassLoader -> AppClassLoader(即通常所说的System ClassLoader)

它们的管辖范围依次是:
  BootStrap------>JRE/lib/rt.jar
  ExtClassLoader---------->JRE/lib/ext/*.jar
  AppClassLoader---------->CLASSPATH指定的所有jar或目录。


四、类加载的三种方式

1、命令行启动应用时候由JVM初始化加载
2、通过Class.forName()方法动态加载
3、通过ClassLoader.loadClass()方法动态加载

五、自定义类加载器

1.自定义的类加载器必须继承ClassLoader(抽象类),并且重写findClass()方法;(除了Bootstrap以外,所有的类加载器都要继承ClassLoader)
2.调用继承自ClassLoader的方法 loadClass(String name),方法返回一个name对应的Class对象。在loadClass()内部执行过程中,loadClass()方法内部采用委托机制,向父加载器上交任务,当父加载器找不到资源时,loadClass()内部会调用自定义类加载器所重写的findClass()方法。

3.将要加载的类读取成二进制数据的byte数组

4.defineClass(String name, byte[ ] , int  off,  int  len);功能:将一个byte数组转换成一个Class类的实例

自定义一个类加载器MyClassLoader,加载类Test,代码如下:

图为测试项目架构图:

Test.class是Test.java编译后的class文件,测试期间,为了让我们自定义的类加载器加载Test.java这个,我们写一个不带包名的Test.java编译后,在项目根目录下新建一个文件夹,存放编译后的Test.class文件

代码:

package com.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;

public class MyClassLoader extends ClassLoader {
	private String name;
	private String path = "E:\\gaotong\\workspace\\test\\testlib\\";

	public MyClassLoader(String name) {
		this.name = name;
	}

	public MyClassLoader(ClassLoader parent, String name) {
		super(parent);
		this.name = name;
	}

	/**
	 * 重写findClass()方法
	 */
	@Override
	public Class<?> findClass(String name) throws ClassNotFoundException {
		byte[] data = loadClassData(name);
		return this.defineClass(name, data, 0, data.length);
	}

	public byte[] loadClassData(String name) {
		InputStream is = null;
		ByteArrayOutputStream os = null;
		try {
			name = name.replace(".", "//");
			is = new FileInputStream(new File(path + name + ".class"));
			os = new ByteArrayOutputStream();
			int i = 0;
			while ((i = is.read()) != -1) {
				os.write(i);
			}
			return os.toByteArray();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (is != null) {
					is.close();
				}
				if (os != null) {
					os.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return null;
	}

	/**
	 * @param args
	 * @throws ClassNotFoundException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 */
	public static void main(String[] args) throws ClassNotFoundException,
			InstantiationException, IllegalAccessException {
		MyClassLoader loader = new MyClassLoader("myClassLoader");
		Class<?> clazz = loader.loadClass("Test");
		Date tt = (Date) clazz.newInstance();
		System.out.println(tt.toString());
		
		
		ClassLoader loader2 = tt.getClass().getClassLoader();
		while(loader2 != null){
			System.out.println(loader2.getClass().getName());
			loader2 = loader2.getParent();
		}
		

	}

}
	

import java.util.Date;

public class Test extends Date{

	@Override
	public String toString() {
		return "Hello,I am Test!";
	}
	
}


运行结果 :


Hello,I am Test!
com.classloader.MyClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader

六、Java WebProject下的类加载器加载过程

Tomcat自定义了一些类加载器,当Tomcat启动时,会自动把Tomcat定义的类加载器加入到JVM中,在Servlet中加载Classpath下的类,一般情况下,类加载器的委托顺序:

org.apache.catalina.loader.WebappClassLoader

org.apache.catalina.loader.StandardClassLoader

org.apache.Launcher$AppClassLoader

org.apache.Launcher$ExtClassLoader

null

参考资料1:http://hi.baidu.com/sonmeika/item/3323651fa2c722f865eabf17

参考资料2:http://hi.baidu.com/sonmeika/item/3323651fa2c722f865eabf17

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值