这里有新鲜出炉的 Java 设计模式,程序狗速度看过来!
java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台(即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se))的总称。
这篇文章主要为大家详细介绍了 Java 中类加载器的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
从 java 的动态性到类加载机制
Java 是一种动态语言。那么怎样理解这个 "动态" 呢?或者说一门语言具备了什么特性,才能称之为动态语言呢?对于 java,我是这样理解的。
JVM(java 虚拟机)执行的不是本地机器码指令,而是执行一种称之为字节码的指令(存在于 class 文件中)。这就要求虚拟机在真正执行字节码之前,先把相关的 class 文件加载到内存中。虚拟机不是一次性加载所有需要的 class 文件,因为它在执行的时候根本不会知道以后会用到哪些 class 文件。它是每用到一个类,就会在运行时 "动态地" 加载和这个类相关的 class 文件。这就是 java 被称之为动态性语言的根本原因。除了动态加载类之外,还会动态的初始化类,对类进行动态链接。动态初始化和动态链接放在其他文章中进行介绍。本文中只关心类的加载。
在 JVM 中负责对类进行加载的正是本文要介绍的类加载器(ClassLoader),所以,类加载器是 JVM 不可或缺的重要组件。
java 中的类加载器及类加载器工作原理
java 中(指的是 javase)有三种类加载器。每个类加载器在创建的时候已经指定他们对应的目录, 也就是说每个类加载器去哪里加载类是确定的,我认为在 ClassLoader 类中应该会有 getTargetPath() 之类的方法, 得到他们对应的路径,找了找 jdk 的文档, 发现是没有的。以下是这三种类加载器和他们对应的路径:
* AppClassLoader -- 加载 classpath 指定的路径中的类
* ExtClassLoader -- 加载 jre/lib/ext 目录下或者 java.ext.dirs 系统属性定义的目录下的类
* BootStrap -- 加载 JRE/lib/rt.jar 中的类
那么类加载器是如何工作的呢?可以参看 jdk 中 ClassLoader 类的源码。这个类的实现使用了模板方法模式,首先是 loadClass 方法来加载类,loadClass 方法又调用了 findClass 方法,该方法读取并返回类文件的数据,findClass 方法返回后,loadClass 方法继续调用 defineClass 方法,将返回的数据加工成虚拟机运行时可识别的类型信息。所以,我们如果开发自己的类加载器,只需要继承 jdk 中的 ClassLoader 类,并覆盖 findClass 方法就可以了,剩下的而工作,父类会完成。其他 java 平台有的根据自己的需求,实现了自己特定的类加载器,例如 javaee 平台中的 tomcat 服务器,Android 平台中的 dalvik 虚拟机也定义了自己的类加载器。
虚拟机加载类有两种方式,一种方式就是上面提到的 ClassLoader.loadClass() 方法,另一种是使用反射 API,Class.forName() 方法,其实 Class.forName() 方法内部也是使用的 ClassLoader。Class 类中 forName 方法的实现如下:
- public static Class<?> forName(String name, boolean initialize,
- ClassLoader loader)
- throws ClassNotFoundException
- {
- if (loader == null) {
- SecurityManager sm = System.getSecurityManager();
- if (sm != null) {
- ClassLoader ccl = ClassLoader.getCallerClassLoader();
- if (ccl != null) {
- sm.checkPermission(
- SecurityConstants.GET_CLASSLOADER_PERMISSION);
- }
- }
- }
- return forName0(name, initialize, loader);
- }
- /** Called after security checks have been made. */
- private static native Class forName0(String name, boolean initialize,
- ClassLoader loader)
- throws ClassNotFoundException;
类加载器的三个特性
类加载器有三个特性,分别为委派,可见性和单一性,其他文章上对这三个特性的介绍如下:
* 委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。
* 可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
* 单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。
其中,委派机制是基础,在其他资料中也把这种机制叫做类加载器的双亲委派模型,其实说的是同一个意思。可加性和单一性是依赖于委派机制的。
以下代码测试类加载器的委派机制:
- ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
- System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
- ClassLoader extClassLoader = appClassLoader.getParent();
- System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@addbf1
- //AppClassLoader的父加载器是ExtClassLoader
- System.out.println(extClassLoader.getParent()); //null
- //ExtClassLoader的父加载器是null, 也就是BootStrap,这是由c语言实现的
由打印结果可知,加载我们自己编写的类的加载器是 AppClassLoader,AppClassLoader 的父加载器是 ExtClassLoader,在而 ExtClassLoader 的父加载器返回结果为 null,这说明他的附加载器是 BootStrap,这个加载器是和虚拟机紧密联系在一起的,在虚拟机启动时,就会加载 jdk 中的类。它是由 C 实现的,没有对应的 java 对象,所以返回 null。但是在逻辑上,BootStrap 仍是 ExtClassLoader 的父加载器。也就是说每当 ExtClassLoader 加载一个类时,总会委托给 BootStrap 加载。
系统类加载器和线程上下文类加载器
在 java 中,还存在两个概念,分别是系统类加载器和线程上下文类加载器。
其实系统类加载器就是 AppClassLoader 应用程序类加载器,它两个值得是同一个加载器,以下代码可以验证:
- ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
- System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
- ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
- System.out.println(sysClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
- //由上面的验证可知, 应用程序类加载器和系统类加载器是相同的, 因为地址是一样的
这两个类加载器对应的输出,不仅类名相同,连对象的哈希值都是一样的,这充分说明系统类加载器和应用程序类加载器不仅是同一个类,更是同一个类的同一个对象。
每个线程都会有一个上下文类加载器, 由于在线程执行时加载用到的类, 默认情况下是父线程的上下文类加载器, 也就是 AppClassLoader。
- new Thread(new Runnable() {
- @Override
- public void run() {
- ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
- System.out.println(threadcontextClassLosder); //sun.misc.Launcher$AppClassLoader@19821f
- }
- }).start();
这个子线程在执行时打印的信息为 sun.misc.Launcher$AppClassLoader@19821f,可以看到和主线程中的 AppClassLoader 是同一个对象(哈希值相同)。
也可以为线程设置特定的类加载器,这样的话,线程在执行时就会使用这个特定的类加载器来加载使用到的类。如下代码:
- Thread th = new Thread(new Runnable() {
- @Override public void run() {
- ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
- System.out.println(threadcontextClassLosder); //jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74
- }
- });
- th.setContextClassLoader(new ClassLoader() {});
- th.start();
在线程运行之前,为它设置了一个匿名内部类的类加载器对象,线程运行时,输出的信息为:jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74,也就是我们设置的那个类加载器对象。
类加载器的可见性
下面验证类加载器的可见性,也就是 子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
以下代码使用父加载器 ExtClassLoader 加载子加载器 AppClassLoader 路径下的类,由输出可知,是不可能实现的。
- try {
- Class.forName("jg.zhang.java.testConcurrent.Person", true,
- ClassLoaderTest.class.getClassLoader().getParent());
- System.out.println("1 -- 类被加载");
- } catch (ClassNotFoundException e) {
- //e.printStackTrace();
- System.out.println("1 -- 未找到类");
- }
输出为 :1 -- 未找到类 。说明抛出了 ClassNotFoundException 异常。原因是让 ExtClassLoader 加载 jg.zhang.java.testConcurrent.Person 这个类因为这个类不在 jre/lib/ext 目录下或者 java.ext.dirs 系统属性定义的目录下,所以抛出 ClassNotFoundException。所以父加载器不能加载应该被子加载器加载的类。也就是说这个类在父加载器中不可见。这种机制依赖于委派机制。
下面代码使用子加载器 AppClassLoader 加载父加载器 BootStrap 中的类,这是可以实现的。
- try {
- Class.forName("java.lang.String", true,
- ClassLoaderTest.class.getClassLoader());
- System.out.println("2 -- 类被加载");
- } catch (ClassNotFoundException e) {
- //e.printStackTrace();
- System.out.println("2 -- 未找到类");
- }
输出为:2 -- 类被加载。说明成功加载了 String 类。是因为在指定由 AppClassLoader 加载 String 类时,由 AppClassLoader 一直委派到 BootStrap 加载。虽然是由子加载器的父加载器加载的,但是也可以说,父加载器加载的类对于子加载器来说是可见的。这同样依赖于委派机制。其实在虚拟机启动初期,java.lang.String 已经被 BootStrap 预加载了,这时再次加载,虚拟机发现已经加载,不会再重复加载。这同时也证明了类加载器的单一性。
测试代码
到此为止,类加载器的知识就全部讲完了。以下是整个测试代码:
- package jg.zhang.java.testclassloader;
- /**
- * 参考ImportNew上的一篇文章<<类加载器的工作原理>>,
- * 文章地址:http://www.importnew.com/6581.html
- *
- * Java类加载器基于三个机制:委托、可见性和单一性。
- * 委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。
- * 可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
- * 单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。
- *
- * 三种类加载器: 每个类加载器在创建的时候已经指定他们对应的目录, 也就是说每个类加载器去哪里加载类是确定的
- * 我认为在ClassLoader类中应该会有getTargetPath()之类的方法, 得到他们对应的路径,找了找jdk的文档,发现是没有的.
- * AppClassLoader -- 加载classpath指定的路径中的类
- * ExtClassLoader -- 加载jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下的类
- * BootStrap -- 加载JRE/lib/rt.jar中的类
- *
- *
- *
- * @author zhangjg
- *
- */
- public class ClassLoaderTest {
- public static void main(String[] args) {
- test1();
- test2();
- test3();
- }
- /**
- * 验证线程上下文类加载器
- */
- private static void test3() {
- /**
- * 1 每个线程都会有一个上下文类加载器,由于在线程执行时加载用到的类,默认情况下是父线程
- * 的上下文类加载器, 也就是AppClassLoader
- */
- new Thread(new Runnable() {
- @Override public void run() {
- ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
- System.out.println(threadcontextClassLosder); //sun.misc.Launcher$AppClassLoader@19821f
- }
- }).start();
- /**
- * 2 也可以给创建的线程设定特定的上下文类加载器
- */
- Thread th = new Thread(new Runnable() {
- @Override public void run() {
- ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
- System.out.println(threadcontextClassLosder); //jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74
- }
- });
- th.setContextClassLoader(new ClassLoader() {});
- th.start();
- }
- /**
- * 测试可见性,可见性依赖于委托机制
- */
- private static void test2() {
- /**
- * 1 让ExtClassLoader加载 jg.zhang.java.testConcurrent.Person这个类
- * 因为这个类不在jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下
- * 所以抛出ClassNotFoundException
- *
- * 所以父加载器不能加载应该被子加载器加载的类,这个类在父加载器中不可见
- * 这种机制依赖于委派机制
- */
- try {
- Class.forName("jg.zhang.java.testConcurrent.Person", true, ClassLoaderTest.class.getClassLoader().getParent());
- System.out.println("1 -- 类被加载");
- } catch(ClassNotFoundException e) {
- //e.printStackTrace();
- System.out.println("1 -- 未找到类");
- }
- /**
- * 2 让AppClassLoader加载java.lang.String类
- * 没有抛出异常,说明类被正常加载了
- * 虽然是由AppClassLoader一直委派到BootStrap而加载的
- * 所以可以说,父加载器加载的类对于子加载器来说是可见的,这同样依赖于委派机制
- *
- * 其实在虚拟机启动初期,java.lang.String已经被BootStrap预加载了
- * 这时再次加载,虚拟机发现已经加载,不会再重复加载
- */
- try {
- Class.forName("java.lang.String", true, ClassLoaderTest.class.getClassLoader());
- System.out.println("2 -- 类被加载");
- } catch(ClassNotFoundException e) {
- //e.printStackTrace();
- System.out.println("2 -- 未找到类");
- }
- }
- /**
- * 验证三种类加载器的父子关系
- */
- private static void test1() {
- ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
- System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
- ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
- System.out.println(sysClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
- //由上面的验证可知, 应用程序类加载器和系统类加载器是相同的, 因为地址是一样的
- ClassLoader extClassLoader = appClassLoader.getParent();
- System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@addbf1
- //AppClassLoader的父加载器是ExtClassLoader
- System.out.println(extClassLoader.getParent()); //null
- //ExtClassLoader的父加载器是null, 也就是BootStrap,这是由c语言实现的
- }
- }
来源: http://www.phperz.com/article/17/0820/338226.html