类加载器
类加载器基本职责就是根据类的二进制名(binary name)读取java编译器编译好的字节码文件(.class文件),并且转化生成一个java.lang.Class类的一个实例。这样的每个实例用来表示一个Java类,jvm就是用这些实例来生成java对象的。比如new一个String对象;反射生成一个String对象,都会用到String.class 这个java.lang.Class类的对象。基本上所有的类加载器都是java.lang.ClassLoader 类的一个实例。
类加载器分类
1、 Bootstrap ClassLoader 启动类加载器,主要加载jre/lib/rt.jar里所有的class。有C++实现,不是ClassLoader子类。
2、 Extension ClassLoader扩展类加载器,主要加载jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
3、 App ClassLoader系统类加载器,负责加载classPath中指定的jar包及目录中的class
4、 自定义加载器
加载器命名空间
每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成
在同一个命名空间中不会出现类的完整名字相同的两个类
在不同的命名空间中,有可能出现完整名字相同的两个类
若有一个类加载器能成功加载Test类,那么这个类加载器被称为定义类加载器,所有能成功返回Class对象引用的类加载器(包括定义类加载器),被称为初始类加载器
子加载器所加载的类能够访问父加载器所加载的类;父加载器所加载的类无法访问子加载器所加载的类,他们命名空间不同造成的
类加载器的父亲委托机制
1、 至底向上检查类是否加载
2、 至顶向下尝试加载类
好处:
1、可以确保Java核心类库的安全
2、可以确保Java核心类库所提供的类不会被自定义类所代替
3、不同类加载器可以为相同名称的类创建额外的命名空间,这就相当于在Java虚拟机中创建了一个又一个相互隔离的java类空间。
类加载器路径
对于启动类加载器、扩展类加载器、系统类加载器,他们都有自己的加载路径,可以通过如下代码获得
public static void main(String[] args) { //bootstrap加载器加载路径 System.out.println(System.getProperty("sun.boot.class.path")); //扩展类加载器加载路径,扩展类加载器只能加载jar包 System.out.println(System.getProperty("java.ext.dirs")); //系统类加载器加载路径 System.out.println(System.getProperty("java.class.path")); }
可以通过命令 -Dsun.boot.class.path,-Djava.ext.dirs, -Djaca.class.path修改类加载器加载路径
自定义类加载器
自定义类加载器,需要继承ClassLoader类,重写findClass方法
package com.jvm; import java.io.*; public class MyTestClassLoad extends ClassLoader { public String classLoaderName = "MyTestClassLoad"; public static final String SUFFIX = ".class"; private String path = ""; public MyTestClassLoad(String classLoaderName) { super(); this.classLoaderName = classLoaderName; } public MyTestClassLoad(ClassLoader parent, String classLoaderName) { super(parent); this.classLoaderName = classLoaderName; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] datas = this.getData(name); return this.defineClass(name, datas, 0, datas.length);//解析转化为class对象 } /** * 获取类的字节码数据 * @param name * @return */ private byte[] getData(String name) { name = name.replace(".", "/"); File file = new File(path + name + SUFFIX); InputStream in = null; ByteArrayOutputStream bos = null; byte[] datas = null; try { in = new FileInputStream(file); bos = new ByteArrayOutputStream(); int ch = 0; while ((ch = in.read()) != -1){ bos.write(ch); } datas = bos.toByteArray(); } catch (Exception e) { e.printStackTrace(); }finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (bos != null) { try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } } return datas; } }
package com.jvm; public class Test6 { Test6 test6; public void setTest6(Object test6) { this.test6 = (Test6) test6; } }
public static void main(String[] args) throws Exception { MyTestClassLoad classLoad1 = new MyTestClassLoad("loader1"); classLoad1.setPath("C:/Users/Administrator/Desktop/test/"); Class<?> clazz1 = classLoad1.loadClass("com.jvm.Test6"); Object object1 = clazz1.newInstance(); MyTestClassLoad classLoad2 = new MyTestClassLoad("loader2"); classLoad2.setPath("C:/Users/Administrator/Desktop/test/"); Class<?> clazz2 = classLoad2.loadClass("com.jvm.Test6"); Object object2 = clazz2.newInstance(); Method method = clazz1.getMethod("setTest6", Object.class); method.invoke(object1, object2); }
从项目中删除Test6,系统类加载器将不能加载Test6了,使用自定义类加载器去加载Test6,两个类加载的命名空间不同,相互不可见Test6,将产生如下异常
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.jvm.Test6_1.main(Test6_1.java:16)
Caused by: java.lang.ClassCastException: com.jvm.Test6 cannot be cast to com.jvm.Test6
at com.jvm.Test6.setTest6(Test6.java:7)
... 5 more