8-ClassLoader
来源:互联网 发布:java接口对于耦合实例 编辑:程序博客网 时间:2024/06/14 12:36
ClassLoader的核心作用是:加载指定路径的class
JVM将所需要运行的*.class文件加载到JVM进程中需要有一个类加载器(ClassLoader,系统会提供默认类加载器,我们也可以自己写),类加载器的好处就在于我们可以随意指定*.class文件的位置(包括网络上的)。
类的初始化时间
调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
示例:
class TestClass{ static { System.out.println("TestClass"); }} public class Test2 { public static voidmain(String[] args) throws ClassNotFoundException { ClassLoader classLoader= ClassLoader.getSystemClassLoader(); Class<?> clazz= classLoader.loadClass("edu.swust.JVM.classloader.TestClass"); // 只是加载,没有初始化 System.out.println("-----------------------"); clazz = Class.forName("edu.swust.JVM.classloader.TestClass"); // 初始化 /** * 结果 * ----------------------- * TestClass * ----------------------- * classedu.swust.JVM.classloader.TestClass */ }}
双亲委托机制
类加载器用来把类加载到java虚拟机中,从JDK1.2开始,类加载过程采用了父亲委托机制,这种机制更好的保证了java平台的安全。在这个委托机制中,除了java虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器。当加载器请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载器能够加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类。
若有一个类加载器能够成功加载Sample类,那么这个类加载器称为定义类加载器,所有能够成功返回class对象的引用的类加载器(包括定义类加载器和它的子加载器)都称为初始类加载器
类加载器分两种
Java虚拟机自带的加载器
BootstrapClassLoader根加载器(Bootstrap) C++编写 未开源
ExtensionClassLoader扩展类加载器(Extension)java编写 父加载器是Bootstrap
AppClassLoader:系统类加载器(System)也叫应用类加载器 java编写父加载去是Extension
用户自定义类加载器
CustomClassLoader:继承java.lang.ClassLoader,用户自定义加载方式
注意:这里的父子加载器不一定是继承的关系,也可能是组合的关系
图一:双亲加载模式示例
这么做的原因是出于安全考虑,防止用户定义的类加载器加载不合法的,如果加载不了,抛出ClassNotFoundException。
需要指出的是,加载器之间的父子关系实际上是加载器对象之间的包装关系,而不是类之间的继承关系。一对父子加载器可能是同一个类加载器的两个实例,也可能不是。在子加载器的对象中包装了一个父加载器对象。例如以下loader1和loader2都是MyClassLoader类的实例,并且loader1包装了loader2,loader2是loader1的父加载器 被包装的的父加载器 包装者是子加载器。
当生成一个自定义类加载器实例时,如果没有指定它的父类加载器,那么系统类加载器就会成为该类加载器的父加载器
双亲模式的问题:顶层ClassLoader无法加载底层ClassLoader
双亲模式的破坏:
双亲模式是默认的模式,但并非只能这么做
Tomcat的WebappClassLoader就会先加载自己的Class,找不到在委托parent
OSGi的ClassLoader就形成网站结构,根据需要自由加载Class
自定义类加载器:
首先查看API得到如下例子
图二:ClassLoader 的API的例子
例子:MyClassLoader.java
import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.InputStream; public class MyClassLoader extendsClassLoader { private String name; //加载类名 private String path = "D:\\"; //加载类的路径 private final String fileType = ".class"; public MyClassLoader(String name) { super();//系统类为父加载器 this.name = name; } public MyClassLoader(ClassLoader loader, String name) { super(loader); //显示指定父加载器 this.name = name; } /** * 找到指定的类字节码 *@param name 类的名字 */ @Override public Class<?> findClass(String name) throwsClassNotFoundException { byte[] data = loadClassData(name); return defineClass(name, data, 0,data.length); //根据类字节码的ASCII码流 加载类 } /** * 读取类的字节码 *@param name 类的名字 *@return 指定类字节码的ASCII码流 */ private byte[] loadClassData(String name) { InputStreaminputStream = null; byte[] data = null; ByteArrayOutputStream baos = null; try { inputStream= new FileInputStream(new File(path + name + fileType)); baos= new ByteArrayOutputStream(); intch = 0; while((ch = inputStream.read()) != -1) { baos.write(ch); } data= baos.toByteArray(); }catch (Exception e) { }finally { try{ inputStream.close(); baos.close(); }catch (Exception e2) { } } return data; } public static void main(String[] args) throws Exception { // 测试代码 } public static void test(ClassLoader loader) throws Exception { Class<?>clazz = loader.loadClass("Sample"); @SuppressWarnings("unused") Objectobject = clazz.newInstance(); } publicString getName() { returnname; } publicvoid setName(String name) { this.name= name; } publicString getPath() { returnpath; } publicvoid setPath(String path) { this.path= path; } publicString getFileType() { returnfileType; } @Override public String toString() { return name; }}
测试用例:Sample.java:
public class Sample { public int value = 1; public Sample() { System.out.println("Sample loader : " + this.getClass().getClassLoader()); new Dog(); }}
测试用例:Dog.java
public class Dog { public Dog() { System.out.println("Dog loader :" + this.getClass().getClassLoader()); }}
图三、文件目录结构
测试1:
MyClassLoaderloader1 = new MyClassLoader("loader1");loader1.setPath("D:\\myapp\\serverlib\\"); MyClassLoaderloader2 = new MyClassLoader(loader1, "loader2");loader2.setPath("D:\\myapp\\clientlib\\"); MyClassLoaderloader3 = new MyClassLoader(null, "loader3");//使用根加载器loader3.setPath("D:\\myapp\\otherlib\\");test(loader2);test(loader3);
图四:测试结果
图五:loader2加载顺序
对于D:\myapp\syslib>javaMyClassLoader
从图中可以看,从上往下加载,只有在loader1才能找到Sample
对于java -cp.;D:\myapp\otherlib MyClassLoader
通过改变了classpath 在classpath中找到了Sample,所以直接用System加载
图六:loader3加载顺序
无论怎么变classpath 都只有;loader3才能加载Sample类
类的卸载
当一个类被加载,连接和初始化后,它的生命周期就开始了。当代表这个类的Class对象不在被引用,即不可触及时Class对象就会结束生命周期,这个类在方法区内的数据也会被卸载,从而结束这个类的生命周期。由此可见,一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期。
由java虚拟机自带的类加载器(根类加载器,扩展类加载器,系统类加载器),在虚拟机的生命周期中始终不会被卸载,原因是虚拟机会引用这三个类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些类对象始终是可触及的。
用用户自定义的类加载器所加载的类是可以卸载的
测试代码:
MyClassLoaderloader1 = new MyClassLoader("loader1");loader1.setPath("D:\\myapp\\serverlib\\");Class<?>clazz1 = loader1.loadClass("Sample");@SuppressWarnings("unused")Objectobject = clazz1.newInstance();System.out.println(clazz1.hashCode());/***************************过程1结束***********************/loader1 =null;clazz1 =null;object =null;/***************************过程2结束***********************/loader1 =new MyClassLoader("loader1");loader1.setPath("D:\\myapp\\serverlib\\");clazz1 =loader1.loadClass("Sample");object =clazz1.newInstance();System.out.println(clazz1.hashCode());/***************************过程3结束***********************/Class<?>clazz2 = loader1.loadClass("Sample"); MyClassLoaderloader2 = new MyClassLoader(loader1, "loader2");loader2.setPath("D:\\myapp\\clientlib\\"); Class<?>clazz3 = loader2.loadClass("Sample"); System.out.println("clazz2 == clazz1: " + (clazz2 == clazz1));System.out.println("clazz2 == clazz3: " + (clazz2 == clazz3));System.out.println("clazz3 == clazz1: " + (clazz3 == clazz1)); /** * 结果 * D:\myapp\syslib>java MyClassLoader * Sample loader : loader1 * Dog loader : loader1 * 1311053135 * Sample loader : loader1 * Dog loader : loader1 * 865113938 * clazz2 == clazz1: true * clazz2 == clazz3: true * clazz3 == clazz1: true */
当执行完过程1,得到的内存模型如下图:
图七:执行完过程1的内存模型
执行完过程2,内存中1、2、3连接断开,JVM会卸载加载的类,在执行完过程3之后又会重新加载类,所以两次的clazz1.hashCode()会不同。
后面的clazz1,clazz2,clazz3都是由loader1加载的serverlib目录下的类(clazz3本质上还是loader1加载的,之前有分析),在内存中加载一个类只加载一份,所以三个clazz指向的是同一个对象,所以三个比较为TRUE。
- 8-ClassLoader
- classLoader
- classloader
- ClassLoader
- ClassLoader
- ClassLoader
- classloader
- ClassLoader
- ClassLoader
- classloader
- ClassLoader
- classloader
- classloader
- classloader
- ClassLoader
- ClassLoader
- classloader
- ClassLoader
- 第一章 OpenStack概述
- 基于浏览器的http普通请求与ajax请求
- 欢迎使用CSDN-markdown编辑器
- HQL数据查询基础
- opensamba.sh
- 8-ClassLoader
- 第五周项目1
- 1045. 快速排序(25)
- Spring概述
- 淘宝商品详情平台化思考与实践
- stm32_020_spi简单介绍及stm32spi工作模式
- 为什么很多公司不要培训机构出来的程序员?
- 1061. 判断题(15)
- Tensorflow slim resnet v2源码阅读笔记