java的类加载器ClassLoader
类在执行之前会执行三个步骤:加载 -> 连接 -> 初始化
1.类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个Class对象,用来封装类在方法区的数据结构。可以把堆区的Class理解为方法区的一面镜子,对应方法区的类的数据结构,通过这面镜子实现类的调用。
加载.class文件的多种方式:
1.从本地系统中直接加载
2.通过网络下载.class文件
3.从zip,jar里加载.class文件
4.从专有的数据库中提取.class文件
5.将java源文件动态编译为.class文件
类加载的最终结果是生成位于堆中的Class对象,Class对象封装了类在方法区内的数据结构,并且向java程序员提供了访问方法区内的数据结构的接口
查看源码得到Class对象只能由java虚拟机来创建,其构造函数私有化
2.连接
1. 验证:确保被加载的类的正确性(主要防止恶心的class文件被加载)
2. 准备:为类的静态变量分配内存,并将其初始化为默认值
3. 解析:把类中的符合引用转换为直接引用
3.初始化
为类的静态变量赋予正确的初始值
所有的java虚拟机实现必须在每一个类或接口被java程序“首次主动使用”时才初始化
java对类的使用方式分为:主动使用,被动使用
主动使用有六种:(除这6种外,其他都是被动使用)
1。创建类的实例
2。访问某个类或接口的静态变量或对该静态变量赋值
3。调用类的静态方法
4。反射
5。初始化类的子类
6。java虚拟机启动时被标注位启动类的类
4.类加载器Classloader
类加载器类型
1.java虚拟机自带的类加载器:
-----根类加载器Bootstrap
该类加载器没有父类加载器,它负责加载虚拟机的核心类库,如java.lang.String等,根类加载器用于在启动JVM时加载类,以使JVM能正常工作,因而它是用Native(c++)代码实现的,最早被创建出来,处于最底层。它并没有继承java.lang.ClassLoader类。
-----扩展类加载器Extension
该类加载器的父类加载器是根类加载器。它从java.ext.dirs系统属性所指定的目录获取加载类库或从JDK的安装目录的jre\lib\ext子目录下加载类库。如果把jar放到这个目录下,也会自动用扩展类加载器加载。扩展类加载器是java类,是java.lang.ClassLoader类的子类
-----系统类加载器System
也成为应用类加载器,它的父类加载器是扩展类加载器,它将加载CLASSPATH中配置的目录和jar文件,它是用户自定义类加载器的默认父类加载器,系统类加载器是java类,是java.lang.ClassLoader类的子类
2.用户自定义类加载器:是java.lang.ClassLoader的子类,可以定义类加载器
类加载器机制
类加载器用来把类加载到java虚拟机中,类加载过程采用的是父亲委托机制,这种机制能很好的保证java平台的安全,因为在这种机制下用户定义的类加载器不可能加载应由父加载器加载的可靠类,如java.lang.String总是由根类加载器加载。在此委托机制中,除了java的根类加载器以外,其余的加载器都有且只有一个父加载器。
自定义类加载器
用户自定义类加载器需要满足父委托机制,默认系统类加载器为其父加载器。要实现自定义加载器,只需要集成ClassLoader类,然后覆盖findClass(String name)方法,该方法根据参数指定加载类的名字,返回对应的Class对象的引用
自定义类加载器类: MyClassLoader
package com.longpo.classloader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class MyClassLoader extends ClassLoader { private String name; //类加载器的名字 private String path="D:\\"; //加载类的路径 private final String type=".class"; //class文件的扩展名 public MyClassLoader(String name) { super(); this.name=name; } //指定父类加载器 public MyClassLoader(ClassLoader parent ,String name) { super(parent);//显示指定该加载器的父加载器 this.name=name; } //设置加载class的路径 public String getPath() { return path; } public void setPath(String path) { this.path = path; } @Override public String toString() { return "MyClassLoader :" + name ; } //读取对应class文件的二进制数据---这里简单读取 private byte[] loadClassData(String name) { //根据要加载的类名找到对应的文件 name=name.replace(".", "\\");//com.longpo.test目录结构是\\ File file=new File(path+name+type); //根据文件大小来创建字节数组 byte[]bytes=new byte[(int)file.length()] ; try { InputStream is = new FileInputStream(file); int len=is.read(bytes);//返回读取字节的长度 is.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return bytes; } //一定要重写该方法 @Override public Class<?> findClass(String name) throws ClassNotFoundException { //得到class文件的二进制数据 byte[]data=loadClassData(name); return this.defineClass(name, data, 0,data.length);//返回class对象的引用 } }
定义一个测试加载类:Simple
package com.longpo.classloader; public class Simple { public Simple( ) { System.out.println("Simple的类加载器的名字是:"+this.getClass().getClassLoader()); } }
测试自定义加载类:Test
package com.longpo.classloader; public class Test { public static void main(String[] args) throws Exception{ //loader1的父加载器默认是系统加载器--上面还有系统加载器,扩展加载器,根加载器 MyClassLoader loader1=new MyClassLoader("loader1"); loader1.setPath("D:\\lib\\loader1\\");//loader1加载路径 //loader2的父加载器为loader1--上面有loader1加载器,系统加载器,扩展加载器,根加载器 MyClassLoader loader2=new MyClassLoader(loader1,"loader2"); loader2.setPath("D:\\lib\\loader2\\");//loader2加载路径 //loader3的父加载器为null,即根加载器--上面只有根加载器 MyClassLoader loader3=new MyClassLoader(null,"loader3"); loader3.setPath("D:\\lib\\loader3\\");//loader3加载路径 test(loader2); test(loader3); } public static void test(ClassLoader loader)throws Exception{ Class clazz=loader.loadClass("com.longpo.classloader.Simple");//loadClas会自动调用findClass方法 Object object=clazz.newInstance(); } }
此时把编译好的class文件发到对应加载器目录的文件夹里面,我用的是Eclipse,可在项目的目录的bin目录下找到.class文件
在Eclipse运行Test,得结果:
各个加载器的关系为
使用loader2加载Simple时,根据父亲委托机制,会从根加载器开始尝试加载,一直往下加载,之道系统类加载器加载成功。使用loader3加载Simple,根据父亲委托机制,先由根加载器加载,加载失败后有loader3自己加载
上一篇: A星寻路算法
下一篇: 据说一半以上的java程序员会出错的题