类加载
知识点一:类加载器概述:
- 定义:
类加载器就是加载类的工具,当出现一个类,用到此类的时候,Java虚拟机首先将类字节码加载进内存,通常字节码的原始信息放在硬盘上的classpath指定的目录下。
- 作用:
类加载器加载类就是将.class文件中的内容加载进内存进行处理,处理完后的结果就是字节码。例如:
test5 t5 = new test5(); ClassLoader classloader =test5.class.getClassLoader(); Class test5_code = classloader.loadClass("test5");//用加载器加载类 print("test5.class:"+test5.class);//test5.class:class test5 print("test5_code:"+test5_code);//test5_code:class test5
- 默认加载器:
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader 。而类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap。它是嵌套在Java虚拟机内核中的,已启动即出现在虚拟机中,是用c++写的一段二进制代码。所以不能通过java程序获取其名字,获得的只能是null。Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
知识点二:类加载器的层次结构关系
ClassLoader loader = ClassLoaderTest.class.getClassLoader(); //打印出当前的类装载器,及该类装载器的各级父类装载器 while(loader != null) { System.out.println(loader.getClass().getName()); loader = loader.getParent(); }
知识点三:不同的类加载器有不同的关系范围
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName()); //将上面语句的测试类改为System则抛NullPointerException,这两个类存放位置不同 System.out.println(System.class.getClassLoader().getClass().getName())
知识点四:类加载器的委托机制
解决一个类可由多个类加载器加载时,由哪个加载器加载的问题。具体规则如下:
1、首先当前线程的类加载器去加载线程中的第一个类。
2、如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
3、还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子。
知识点五:自定义加载器
- 步骤:
1、自定义加载类继承ClassLoader
2、重写LoaderClassData方法,根据自定义加载器的路径、名字以及文件类型即.class获得.class的字节数组data
new FileInputStream(new File(path + name + fileType)
3、重写findClass方法,该方法调用LoaderClassData方法获得.class字节数组data,将其传入defineClass方法中获得Class对象(类的字节码),实现类加载机制。
public Class<?> findClass(String name)
{
byte[] data = loaderClassData(name);// data(.class文件的字节数组)
return this.defineClass(name, data, 0, data.length);
}
代码如下:
import java.io.*; import java.util.*; public class MyClassLoader extends ClassLoader { //类加载器名称 private String name; //加载类的路径 private String path = "D:/"; private final String fileType = ".class"; //让系统类加载器成为该 类加载器的父加载器 public MyClassLoader(String name) { super(); this.name = name; } //显示指定该类加载器的父加载器 public MyClassLoader(ClassLoader parent, String name) { super(parent); this.name = name; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } @Override public String toString() { return this.name; } //获取.class文件的字节数组 private byte[] loaderClassData(String name) { InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); this.name = this.name.replace(".", "/"); try { is = new FileInputStream( new File(path + name + fileType) ); int c = 0; while(-1 != (c = is.read())){ baos.write(c); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally{ try { is.close(); baos.close(); } catch (IOException e) { e.printStackTrace(); } } return data; } /** * 获取Class对象 */ @Override public Class<?> findClass(String name) { byte[] data = loaderClassData(name); //data(.class文件的字节数组) return this.defineClass(name, data, 0, data.length); } public static void main(String[] args) throws Exception { //loader1的父加载器为系统类加载器 MyClassLoader loader1 = new MyClassLoader("loader1"); loader1.setPath("D:\\java1\\"); //loader2的父加载器为loader1 MyClassLoader loader2 = new MyClassLoader(loader1, "loader2"); loader2.setPath("D:\\java2\\"); //loader3的父加载器为根类加载器 MyClassLoader loader3 = new MyClassLoader(null, "loader3"); loader3.setPath("D:\\java3\\"); Class clazz = loader1.loadClass("Sample"); Sample o =(Sample)clazz.newInstance(); } } public class Sample { public Sample() { System.out.println("Sample is loaded by " + this.getClass().getClassLoader()); } }
正常执行,运行结果:
Sample is loaded bysun.misc.Launcher$AppClassLoader@15601ea
分析:Sample由Loader1加载,Loader1向其父类系统加载器委托,Loader1寻找相对应.class文件,加载成功,将得到的字节码返回给Loader1。
将编译的Sample.class文件剪切,放入D:/java1/中,再此执行,运行结果:
Sample is loaded by loader1
分析:Sample由Loader1加载,Loader1向其父类系统加载器委托,Loader1寻找相对应.class文件,未找到,加载失败,继续向上委托都失败,最后又委托给Loader1加载,加载成功,得到类字节码。