首页 Java java教程 Java中关于类加载器的代码详解

Java中关于类加载器的代码详解

Jun 18, 2017 am 09:44 AM
java 代码 关于 加载 详解

这篇文章主要为大家详细介绍了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语言实现的

 }

}
登录后复制

以上是Java中关于类加载器的代码详解的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Java 中的完美数 Java 中的完美数 Aug 30, 2024 pm 04:28 PM

Java 完美数指南。这里我们讨论定义,如何在 Java 中检查完美数?,示例和代码实现。

Java中的Weka Java中的Weka Aug 30, 2024 pm 04:28 PM

Java 版 Weka 指南。这里我们通过示例讨论简介、如何使用weka java、平台类型和优点。

Java 中的史密斯数 Java 中的史密斯数 Aug 30, 2024 pm 04:28 PM

Java 史密斯数指南。这里我们讨论定义,如何在Java中检查史密斯号?带有代码实现的示例。

Java Spring 面试题 Java Spring 面试题 Aug 30, 2024 pm 04:29 PM

在本文中,我们保留了最常被问到的 Java Spring 面试问题及其详细答案。这样你就可以顺利通过面试。

突破或从Java 8流返回? 突破或从Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一种强大且表达力丰富的处理数据集合的方式。然而,使用Stream时,一个常见问题是:如何从forEach操作中中断或返回? 传统循环允许提前中断或返回,但Stream的forEach方法并不直接支持这种方式。本文将解释原因,并探讨在Stream处理系统中实现提前终止的替代方法。 延伸阅读: Java Stream API改进 理解Stream forEach forEach方法是一个终端操作,它对Stream中的每个元素执行一个操作。它的设计意图是处

Java 中的时间戳至今 Java 中的时间戳至今 Aug 30, 2024 pm 04:28 PM

Java 中的时间戳到日期指南。这里我们还结合示例讨论了介绍以及如何在java中将时间戳转换为日期。

Java程序查找胶囊的体积 Java程序查找胶囊的体积 Feb 07, 2025 am 11:37 AM

胶囊是一种三维几何图形,由一个圆柱体和两端各一个半球体组成。胶囊的体积可以通过将圆柱体的体积和两端半球体的体积相加来计算。本教程将讨论如何使用不同的方法在Java中计算给定胶囊的体积。 胶囊体积公式 胶囊体积的公式如下: 胶囊体积 = 圆柱体体积 两个半球体体积 其中, r: 半球体的半径。 h: 圆柱体的高度(不包括半球体)。 例子 1 输入 半径 = 5 单位 高度 = 10 单位 输出 体积 = 1570.8 立方单位 解释 使用公式计算体积: 体积 = π × r2 × h (4

创造未来:面向零基础的 Java 编程 创造未来:面向零基础的 Java 编程 Oct 13, 2024 pm 01:32 PM

Java是热门编程语言,适合初学者和经验丰富的开发者学习。本教程从基础概念出发,逐步深入讲解高级主题。安装Java开发工具包后,可通过创建简单的“Hello,World!”程序实践编程。理解代码后,使用命令提示符编译并运行程序,控制台上将输出“Hello,World!”。学习Java开启了编程之旅,随着掌握程度加深,可创建更复杂的应用程序。

See all articles