详细讲解Java中的类加载器
2016-01-27 14:23
543 查看
1 简介
与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Javaclass文件加载到JVM里运行,负责加载Java class的这部分就叫做Class Loader。
JVM 本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,Bootstrap ClassLoader是用本地代码实现的,它负责加载核心Java
Class,即所有java.*开头的类,它搜索的范围为:jdk/jre/lib/*.jar。
另外JVM还会提供两个ClassLoader:ExtClassLoader和AppClassLoader。它们都是用 Java语言编写的,由Bootstrap
ClassLoader加载,其中Extension ClassLoader负责加载扩展的Java Class类,包括所有javax.*开头的类和存放在jdk/jre/ext/*.jar目录下的类)。Application
ClassLoader负责加载应用程序自身的类,它搜索的范围为:classPath指定的jar或者class类。如下图所示:
![](file:///C:/Users/XAYQYA~1/AppData/Local/Temp/enhtmlclip/Image.png)
2 类加载器的父子关系
Java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class Person { static { System.out.println("I am loading"); } } public class Test { public static void main(String[] args) { Person p = new Person (); Class pclass = p.getClass(); ClassLoader cl = pclass.getClassLoader(); System.out.println("person类加载器"+cl); System.out.println("person类加载器的父类加载器"+cl.getParent()); System.out.println("person类加载器的祖父类加载器"+cl.getParent().getParent()); } } |
person类加载器sun.misc.Launcher$AppClassLoader@82ba41
person类加载器的父类加载器sun.misc.Launcher$ExtClassLoader@923e30
person类加载器的祖父类加载器null
注意:$说明是内部类。
3 委托加载机制
在Java的类加载器中,存在一种委托关系:当类加载器需要加载一个类的时候,会首先委托它的父类从其搜索路径中搜索相关类,如果找到,则加载父类加载器所找到的类,否则,才从自身的搜索路径中寻找相关的类,如果还是找不到,将会抛出一个ClassNotFoundException异常。注意:在这里的类加载器之间的委托是递归的,它将一层一层的往上委托,直到Bootstrap ClassLoader。具体实现过程见下面的例子:Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | public class Sample { public int v1 = 1; public Sample() { System.out.println("Sample is load by :" + this.getClass().getClassLoader()); } } import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; 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; } @Override public String toString() { return this.name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getFileType() { return fileType; } protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] data = this.loadClassData(name); return this.defineClass(name, data, 0, data.length); } private byte[] loadClassData(String name) { InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = null; try { this.name = this.name.replace(".", "\\"); is = new FileInputStream(new File(path + name + fileType)); baos = new ByteArrayOutputStream(); int ch = 0; while (-1 != (ch = is.read())) { baos.write(ch); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { is.close(); baos.close(); } catch (Exception e) { e.printStackTrace(); } } return data; } public static void showClassLoader(ClassLoader loader) throws Exception { Class clazz = loader.loadClass("Sample"); Object object = clazz.newInstance(); } public static void main(String[] args) throws Exception { MyClassLoader loader1 = new MyClassLoader("loader1"); loader1.setPath("d:\\loader1\\"); MyClassLoader loader2 = new MyClassLoader(loader1, "loader2"); loader2.setPath("d:\\loader2\\"); MyClassLoader loader3 = new MyClassLoader(null, "loader3"); loader3.setPath("d:\\loader3\\"); showClassLoader(loader2); showClassLoader(loader3); } } |
![](file:///C:/Users/XAYQYA~1/AppData/Local/Temp/enhtmlclip/Image.jpg)
将sample.class放到d:\loader1和d:\loader3中,运行结果为:
Sample is load by :sun.misc.Launcher$AppClassLoader@3e25a5
Sample is load by :loader3
但是将classpath中的sample.class文件删除之后,运行结果为:
Sample is load by :loader1
Sample is load by :loader3
[b]上面类的内部引用关系为:[/b]
![](file:///C:/Users/XAYQYA~1/AppData/Local/Temp/enhtmlclip/Image(1).jpg)
4 类的互见性质
命名空间每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
运行时包
由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看他们的包名是否相同,还要看定义类加载器是否相同。只有属于同一运行时包的类才能互相访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。假设用户自己定义了一个类java.lang.Spy,并由用户自定义的类加载器加载,由于java.lang.Spy和核心类库 java.lang.*由不同的加载器加载,它们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员。
类的互见
同一个命名空间内的类是互相可见的。子加载器的命名空间包含所有父加载器的命名空间,因此由子加载器加载的类能看见父加载器加载的类,例如系统类加载器加载的类能看见根类加载器加载的类;但是由父加载器加载的类不能看见子加载器加载的类;如果两个加载器之间没有直接或间接的父子关系,那么他们各自加载的类相互不可见。如下所示:
Java
1 2 3 4 5 6 7 8 9 | public static void main(String[] args) throws Exception { MyClassLoader loader1 = new MyClassLoader("loader1"); loader1.setPath("d:\\loader1\\"); Class clazz = loader1.loadClass("Sample"); Object object = clazz.newInstance(); Sample sample = (Sample) object; System.out.println(sample.v1); } |
Sample is load by :loader1
Exception in thread "main" java.lang.NoClassDefFoundError: Sample
错误在于Sample sample = (Sample)object;这一行;
MyClassLoader类由系统类加载器加载,而Sample类由loader1类加载器加载,因此MyClassLoader类看不见Sample 类。在MyClassLoader的main方法中使用Sample类,会导致NoClassDefFoundError错误。当两个不同命名空间内的类互相不可见时,可采用java的反射机制来访问对方实例的属性和方法。如下:
Field field = clazz.getField("v1");
int v1 = field.getInt(object);
System.out.println(v1);
5 资源的加载
每一个.class文件加载都要用到类加载器,加载器也可以加载其他文件。Java
1 2 3 4 5 6 7 | public class Test { public static void main(String[] args) throws FileNotFoundException { InputStream is = Test.class.getClassLoader().getResourceAsStream("config.properties"); } } |
6 类的卸载
当Sample类被加载、连接和初始化后,他的生命周期就开始了。当代表Sample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,Sample类在方法区内的数据也会被卸载,从而结束Sample类的生命周期。由此可见,一个类何时结束生命周期,取决于代表他的Class对象何时结束生命周期。由java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载,java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public static void main(String[] args) throws Exception { MyClassLoader loader1 = new MyClassLoader("loader1"); loader1.setPath("d:\\loader1\\"); Class clazz = loader1.loadClass("Sample"); System.out.println(clazz.hashCode()); Object obj = clazz.newInstance(); loader1 = null; clazz = null; obj = null; loader1 = new MyClassLoader("loader1"); loader1.setPath("d:\\loader1\\"); clazz = loader1.loadClass("Sample"); System.out.println(clazz.hashCode()); obj = clazz.newInstance(); } |
6413875
Sample is load by :sun.misc.Launcher$AppClassLoader@3e25a5
6413875
Sample is load by :sun.misc.Launcher$AppClassLoader@3e25a5
运行结果二为:
6413875
Sample is load by :loader1
17510567
Sample is load by :loader1
7线程上下文类加载器
线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoadercl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。
相关文章推荐
- JAVA敏捷开发环境搭建
- HouseMD, 比BTrace更实用的Java运行诊断工具
- 在MyEclipse中使用maven
- 生成随机数-java工具类
- java继承方法重写权限问题
- 关于spring+springMVC+myBatis的一些基础配置以及整合
- MyBatis3整合Spring3、SpringMVC3
- Java遍历Map对象的四种方式
- volatile关键字作用与内存可见性、指令重排序概述[JAVA]
- Java中,logger.debuge是什么意思?有什么作用?
- JAVA 静态内部类用法
- java获取cpu、内存、硬盘信息
- Java Hex 16进制的 byte String 转换类
- Eclipse插件开发4-SWT布局
- Spring整合Quartz实现动态定时任务
- 在整合springmvc+mybatis+ecache时候报错如下: Another unnamed CacheManager already exists in the same VM.
- Java-正确使用equals和hashCode方法
- java poi excel
- Spring RMI调用远程方法
- SpringMVC 和 Struts2 框架区别