深入类加载机制

本文详细介绍了Java类加载器的层次结构,包括引导加载器、扩展类加载器和应用程序类加载器,以及自定义类加载器的实现。深入剖析了双亲代理机制的工作原理和作用。

     学习Java语言的朋友都知道,Java中有许多类,而且他们的来源有很多,可以是网络上的,可以是jdk自带的,也可以是我们自己下载的.jar包(数据库的驱动jar(mysql-connector-java-5.1.28-bin.jar)或者是分析、编辑和创建字节码的.jar包(javassist-3.15.0-GA.jar))等,都可是说是通过类加载机制从外部文件中直接将已经编译好的Java字节码文件加载到我们的Java程序中,也就是说,我们未经创建便得到了一个功能强大的类。下面,让我们看看类类加载器的层次结构吧:

 

 

1.引导加载器:该加载器可以说是所有类加载器的父类(注意:这个说法严格上来说是不对的),该类加载器是由C++语言编写的,所以该加载器没有父类,用来加载Java语言的核心库(rt.jar),那么什么是Java核心库呢?(我们经常用的String类,Integer类...等等),再次不必深究。

2.扩展类加载器:扩展类加载器继承自引导类加载器,扩展类加载器的主要功能加载(ext.jar)类库,该类库使用java语言编写的。

3.应用程序类加载库:该加载器继承自扩展类加载器,主要功能是加载我门日常在编译器(idea,eclipse等)里面编写的类,这些类的加载,都是由该类加载器完成的。

4.自定义加载器:开发者可根据自己的不同需求继承ClassLoader类实现其自定义的类加载器,实现特定的类加载方式。

 

要深入了解类加载机制我们就必须了解类加载的机制,接下来我来介绍类加载中的一个重要机制,“双亲代理(委托)机制”!

1.双亲代理机制是什么?:双亲代理机制,是代理机模式中的一种,我们在加载一个类的时候,首先得到该类信息的是最底层的类加载器,“应用程序类加载器”,该加载器得到类“名称”(我在这里的理解就是类名)后不会立即加载,而是上抛给其parent,就这样一层层的上抛,当上抛至“引导类加载器”时如果该加载器中有和其一样的类名,则该类直接加载“自己”的该类到堆区,如果“引导加载器”中不存在与该类相同类类名,则一层层向下抛,如果在那一层发现与其相同的类名,则直接加载“该类加载器中的这个类”到堆区,如果直至应用程序类加载器还没有发现与该类同名的类,则抛出异常(注意:我们自己定义的类,在应用程序类加载器中都能找到相同的类名);

2.双亲代理机制的作用是什么?:试想一下,你自己定义了一个java.lang.String类,那么你让Java核心库中了(java.lang.String)怎么办?你会把核心库中的String类覆盖掉吗?这当然时不现实的。Java核心库中的类是不可被覆盖的,也就是说,双亲代理机制,保护了Java中自带的类(不仅仅是Java核心库中的类)。也就是说,你可定义一个java.lang.String类,但是你永远都用不了该类,因为该类存在于Java核心库中(rt.jar)而引导加载器负责加载该类,所以你不论怎么调用,它到加载的时Java核心库中的String类。

 

接下来我们来聊一聊自定义类加载器:

 

1.自定义类加载器不必实现双亲委派机制(当然也可以实现);

2.自定义类加载器灵活性很高,随编程者的意愿,来加载类。

3.自定义类加载器应当继承自ClassLoader类,并重写其findClass()方法。

 

下面是我手写的一个文件类加载器:

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

public class MyClassLoader extends ClassLoader{
	private String rootDir;
	
	public MyClassLoader(String dir) {
		this.rootDir=dir;
	}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		
		Class<?> c=null;
		
		
		//查看此类是否已经加载完毕
		c=findLoadedClass(name);
		
		//如加载完毕,直接返回
		if(c!=null) {
			return c;
		}else {//没有加载,使用代理模式,调用其父类类加载器进行双亲委派式加载
			
			try {
					c=this.getParent().loadClass(name);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
			//如果父类加载器中存在该类,则由父类加载器加载
			if(c!=null) {
				return c;
			}else {//如果父类类加载器无法加载该类则用自己的类加载器加载
				String path;
				
				//构造加载器需要的绝对路径
				path=rootDir+"/"+name;
				
				
				byte[] data=null;
				try {
					data = getdataArray(path);
				} catch (IOException e) {
//					e.printStackTrace();//因为父类中没有发现该类相关信息所以报错,当然我们可以删除器双亲代理机制,获知捕获异常来解决这一问题
				}
				//自定义类
				c=defineClass(null,data, 0, data.length);
				
				
				//如果返回值为空,则说明路径不存在,抛出异常
				if(c==null) {
					throw new ClassNotFoundException();
				}else {//否则返回使用自定义类加载器加载完成的类
					return  c;
				}
			}
		}
	}
	
	//获得绝对路径的byte数组
	private byte[] getdataArray(String str) throws IOException {
		
		int len=0;
		byte[] car=new byte[1000];
		
		FileInputStream fi=new 	FileInputStream(str);
		ByteArrayOutputStream baos=new ByteArrayOutputStream();
		
		while((len=(fi.read(car)))!=-1) {
			baos.write(car, 0, len);
		}
		
		return baos.toByteArray();
	}
	
}

下面我们来调用该类加载器:

public class Demo01 {
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		MyClassLoader classloader=new MyClassLoader("D:/Temp");//得到第一个文件类加载器对象
		MyClassLoader classloader2=new MyClassLoader("D:/Temp");//得到第二个文件类加载器对象
		
		System.out.println(classloader.hashCode());//输出第一个对象的hashcode
		System.out.println(classloader2.hashCode());//输出第二个对象的hashcode
		
		System.out.println("******************************");
		
		Class<?> clazz=classloader.loadClass("Demo.class");//使用类加载器一加载类文件
		Class<?> clazz2=classloader2.loadClass("Demo.class");//使用类加载器二加载文件
		
		System.out.println(clazz);//输出类
		System.out.println(clazz.hashCode());//输出该类的哈希码
		System.out.println("***********************");
		System.out.println(clazz2);//输该类
		System.out.println(clazz2.hashCode());//输出该类的哈希码
	}
}

运行结果如下(错误不用管)

结论:我们可以看出VM判断类是否相同的依据时是否为同一个类加载器加载的相同的.class文件,也就是说,我new 了两个同一个类加载器对象,它门的哈希码不同,肯定不是同一个类加载器,然后我用这两个不同的类加载器加载一个相同的.class文件所生成的类也不是同一个类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值