深入分析ClassLoader

首先介绍下ClassLoader:
ClassLoader顾名思义就是类加载器,负责将Class加载到JVM中,事实上ClassLoader除了能将Class加载到JVM中之外,还有一个重要的作用就是审查每个类应该有谁加载,ClassLoader是一个父优先的等级加载机制。ClassLoader除了上述两个作用外还有一个任务就是将Class字节码重新解析成JVM统一要求的对象格式-----------由此本文可以划分成三点

  1. ClassLoader类结构分析
    1. ClassLoader常用的方法
    输入图片说明
    1,defineClass():用来将byte字节流解析成JVM能够识别的Class对象
    2,findClass():用来子类扩展的方法,目的是为了寻找类
    3,loadClass():加载类,这里就可以动态加载了,程序运行的时候加载
    4,resolveClass():这个方法是用来链接这个类
    JDK给我们提供了可以扩展的,也就是我们可以自定义的类加载器URLCLassLoader,比较方便

     2. ClassLoader的等级加载机制      
    

输入图片说明

1,BootStrap ClassLoader: 

这是一个引导类加载器,首先声明一下虽说在类的结构中可以看到这个类,但是这个类只有Class文件,据了解这个类是通过C++编写的,这也就是ExtClassLoader的父类为空的原因。引导类负责加载JDK的jre下的类库,例如rt.jar

2,ExtClassLoader:

这是一个扩展类加载器,加载bin目录下的ext文件夹下的jar包,不多说

 3,AppClassLoader:

这个类加载器就是我们自定义的类,例如我们的这个方法getSystemClassLoader()作为父加载器,这个加载器也就是AppClassLoader。

 4,加载类过程总结:

看了很多资料都是什么双亲委托加载机制,具体我也不是很清楚,可以我太low,但是我个人的理解就是,当加载一个类的时候:首先检查这个类是否已经被自己加载过,如果已经加载过,就拒绝本次加载,如果没有加载过,就会抛给他的父类,然后父类在检查是否加载,然后在抛给父类,直到全部检查完都没有发现这个类被加载,那么就有意思了,因为每一个类加载器都有自己的加载范围,然后开始判断,父类不加载类,那么就开始向下寻找这个类的加载器,然后这个类被加载,也就是先向上检查是否加载,其次向下看是否可以加载!--------------------这里有一个讲解,来帮助理解,如果我们自己写了一个java.lang.String,那么类加载器就先去加载jdk下的java.lang.String.遇到我们自己定义的类就不会加载了,这也就是java.lang.Object首先被加载,父类加载会防止我们破坏干扰jre的正常运行,导致类结构被破坏

这里是类关系的代码

public class ClassA {
}
public class ClassB {
}
public class ClassTest {
	public static void main(String[] args) {
		//演示 我們的類加載器
		System.out.println("ClassTest-------------ClassLoader:"+ClassTest.class.getClassLoader());
		System.out.println("ClassTest.Parent------ClassLoader:"+ClassTest.class.getClassLoader().getParent());
		System.out.println("ClassTest.GrandFather-ClassLoader:"+ClassTest.class.getClassLoader().getParent().getParent());
		
		//用户自定义的类加载   都是通过AppClassLoader加载
		System.out.println(ClassA.class.getClassLoader());
		System.out.println(ClassB.class.getClassLoader());
		//结论就是----------当两个类名称不同,那么这两个类就不相同
	}
}

打印结果

ClassTest-------------ClassLoader:sun.misc.Launcher$AppClassLoader@73d16e93
ClassTest.Parent------ClassLoader:sun.misc.Launcher$ExtClassLoader@15db9742
ClassTest.GrandFather-ClassLoader:null
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$AppClassLoader@73d16e93
  1. 如何判断两个类相同
  • 类名称不同
这里就不需要介绍了,只要类名不同,那么类肯定也不同
  • 包名称不同
package me.classloader.classpackage;

public class String {

}


package me.classloader.classpackage;

import org.junit.Test;

public class ClassTest {
	@Test
	public void classTest(){
		//这里是  BootStrap ClassLoader-------因此返回Null
		System.out.println(java.lang.String.class+"------classLoader:"+java.lang.String.class.getClassLoader());
		//这里 我们自定义的类        被AppClassLoader 加载
		System.out.println(String.class+"--------ClassLoader:"+String.class.getClassLoader());
		//很明显这两个类是不同的
	}
}
这里可以得到我们的包名可以引起 两个类不是同一个类,并且,这里JVM就近调用一个类先调用的同包下的类
  • 类加载器不同
package me.classloader.classloader;

public class Demo {
	private Demo demo;

	public void setDemo(Demo demo) {
		this.demo = demo;
	}
	
}

package me.classloader.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

//自己写的ClassLoader
public class MyClassLoader extends ClassLoader {

	private String classPath;// 类路径

	public MyClassLoader() {
	}

	public MyClassLoader(String classPath) {

		this.classPath = classPath;
	}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {

		byte[] classDate = getDate(name);
		if (classDate == null) {
			throw new ClassNotFoundException();
		} else {
			return defineClass(name, classDate, 0, classDate.length);
		}
	}

	// 将 class 文件转化成字节流
        //className是类名称---权限定名
	private byte[] getDate(String className) {
		// 将XX.XX.java --------->XX/XX/java.class
		String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
		try {
			InputStream is = new FileInputStream(path);
			ByteArrayOutputStream stream = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int num = 0;
			while ((num = is.read(buffer)) != -1) {
				stream.write(buffer, 0, num);
			}
			return stream.toByteArray();

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
}

package me.classloader.classloader;

public class ClassTest {
	public static void main(String[] args) throws Exception{
		String classPath ="E:\\javaprogram\\java-exercise\\classload\\bin";
		String className = "me.classloader.classloader.Demo";
		ClassLoader appClassLoader = ClassTest.class.getClassLoader();
		MyClassLoader myClassLoader = new MyClassLoader(classPath);
		Class demo1 = appClassLoader.loadClass(className);
		System.out.println(demo1.newInstance().getClass().getClassLoader());
		Class demo2 = myClassLoader.findClass(className);
		System.out.println(demo2.newInstance().getClass().getClassLoader());
        System.out.println(demo1.newInstance().getClass() ==demo2.newInstance().getClass());
	}
}
测试结果
sun.misc.Launcher$AppClassLoader@73d16e93
me.classloader.classloader.MyClassLoader@6d06d69c
false

通过上面的代码,我们也知道了如何写一个类加载器,其实查看findClass()是ClassLoader提供的一个模板方法!

总结:类加载器也是一个比较深的东西,例如servlet中jsp使用了热部署!其实就是类加载器的应用,可能动态代理也用到了类加载器,因为实际工作加载的是我们代理的类

posted @ 2017-01-06 23:35  白白胖胖的空格  阅读(217)  评论(0编辑  收藏  举报