当前位置: 移动技术网 > IT编程>开发语言>Java > 018.Java类加载器

018.Java类加载器

2019年05月15日  | 移动技术网IT编程  | 我要评论

 

类加载器(class loader)

用来加载 java 类到 java 虚拟机中

  • 一般来说,java 虚拟机使用 java 类的方式如下:

  java 源程序(.java 文件)在经过 java 编译器编译之后就被转换成 java 字节代码(.class 文件)。类加载器负责读取 java 字节代码,并转换成 java.lang.class类的一个实例;每个这样的实例用来表示一个 java 类。通过此实例的 newinstance()方法就可以创建出该类的一个对象。

 

  • 实际的情况可能更加复杂,比如 java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的

  基本上所有的类加载器都是 java.lang.classloader类的一个实例。

 

java.lang.classloader

  • 根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 java 类,即 java.lang.class类的一个实例;

表示类名称的 name参数的值是类的二进制名称。需要注意的是内部类的表示,如 com.example.sample$1和 com.example.sample$inner等表示方式。

 

  • 负责加载 java 应用所需的资源,如图像文件和配置文件等。

 

 

类加载器的树状组织结构

java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 java 应用开发人员编写的。

 

  • 系统提供的类加载器主要有下面3个:

  引导类加载器(bootstrap class loader):它用来加载 java 的核心库,是用原生代码来实现的,并不继承自 java.lang.classloader

  扩展类加载器(extensions class loader):它用来加载 java 的扩展库。java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 java 类。

  系统类加载器(system class loader):它根据 java 应用的类路径(classpath)来加载 java 类。一般来说,java 应用的类都是由它来完成加载的。可以通过 classloader.getsystemclassloader()来获取它。

 

  • 除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.classloader类的方式实现自己的类加载器,以满足一些特殊的需求。

  除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过 getparent()方法可以得到。

  对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;

  对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 java 类的类加载器。

  因为类加载器 java 类如同其它的 java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。

 

 

public class classloadertree { 
 
   public static void main(string[] args) { 
       classloader loader = classloadertree.class.getclassloader(); 
        //有些 jdk 的实现对于父类加载器是引导类加载器的情况,getparent()方法返回 null
       while (loader != null) { 
           system.out.println(loader.tostring()); 
           loader = loader.getparent(); 
       } 
   } 
}    

//输出
sun.misc.launcher$appclassloader@9304b1   //系统类加载器实例
sun.misc.launcher$extclassloader@190d11   //扩展类加载器实例

 

 

类加载器的代理模式

类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。

 

  • java 虚拟机是如何判定两个 java 类是相同的

java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器(定义加载器)是否一样。只有两者都相同的情况,才认为两个类是相同的

  • 代理模式是为了保证 java 核心库的类型安全

通过代理模式,对于 java 核心库的类的加载工作由引导类加载器来统一完成,保证了 java 应用所使用的都是同一个版本的 java 核心库的类,是互相兼容的。

  • 相同名称的类可以并存在 java 虚拟机中,只需要用不同的类加载器来加载它们即可。

不同类加载器加载的类之间是不兼容的,这就相当于在 java 虚拟机内部创建了一个个相互隔离的 java 类空间。

 

加载类的过程

加载 + 启动加载

  • 真正完成类的加载工作是通过调用加载器的 defineclass来实现的;而启动类的加载工过程是通过调用加载器的 loadclass来实现的。

  前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。

  在 java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。

 

  • 对于一个类加载器实例来说,相同全名的类只加载一次

 

 

线程上下文类加载器

  类 java.lang.thread中的方法 getcontextclassloader()和 setcontextclassloader(classloader cl)用来获取和设置线程的上下文类加载器。

  如果没有通过 setcontextclassloader(classloader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。

  java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

 

  • 在 spi 接口的代码中使用线程上下文类加载器,就可以成功的加载到 spi 实现的类。

 

一种加载类的方法 class.forname

该方法有两种形式:

class.forname(string name, boolean initialize, classloader loader)

class.forname(string classname)

第一种形式的参数 name表示的是类的全名;initialize表示是否初始化类;loader表示加载时使用的类加载器。

第二种形式则相当于设置了参数 initialize的值为 trueloader的值为当前类的类加载器。

 

class.forname的一个很常见的用法是在加载数据库驱动的时候

如 class.forname("org.apache.derby.jdbc.embeddeddriver").newinstance()  //用来加载 apache derby 数据库的驱动。

 

 

开发自己的类加载器

  • 文件系统类加载器

  加载存储在文件系统上的 java 字节代码,通过 类 filesystemclassloader 的 defineclass()方法来把这些字节代码转换成 java.lang.class类的实例。

  • 网络类加载器

  类 networkclassloader负责通过网络下载 java 类字节代码并定义出 java 类。

 

 

类加载器与 web 容器

  • 运行在 java ee™容器中的 web 应用来说,类加载器的实现方式与一般的 java 应用有所不同

  每个 web 应用都有一个对应的类加载器实例。

  该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 java servlet 规范中的推荐做法,其目的是使得 web 应用自己的类的优先级高于 web 容器提供的类。

  这种代理模式的一个例外是:java 核心库的类是不在查找范围之内的。这也是为了保证 java 核心库的类型安全。

 

  • 绝大多数情况下,web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:

每个 web 应用自己的 java 类文件和使用的库的 jar 包,分别放在 web-inf/classes和 web-inf/lib目录下面。

多个应用共享的 java 类文件和 jar 包,分别放在 web 容器指定的由所有 web 应用共享的目录下面。

当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。

 

 

类加载器与 osgi

osgi™是 java 上的动态模块系统。它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。

  • osgi 中的每个模块(bundle)都包含 java 包和类

osgi 中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 java 包和类。当它需要加载 java 核心库的类时(以 java开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的 java 类时,它会代理给导出此 java 类的模块来完成加载。

 

  • 面提供几条比较好的建议

如果一个类库只有一个模块使用,把该类库的 jar 包放在模块中,在 bundle-classpath中指明即可。

如果一个类库被多个模块共用,可以为这个类库单独的创建一个模块,把其它模块需要用到的 java 包声明为导出的。其它模块声明导入这些类。

如果类库提供了 spi 接口,并且利用线程上下文类加载器来加载 spi 实现的 java 类,有可能会找不到 java 类。如果出现了 noclassdeffounderror异常,首先检查当前线程的上下文类加载器是否正确。通过 thread.currentthread().getcontextclassloader()就可以得到该类加载器。该类加载器应该是该模块对应的类加载器。如果不是的话,可以首先通过 class.getclassloader()来得到模块对应的类加载器,再通过 thread.currentthread().setcontextclassloader()来设置当前线程的上下文类加载器。

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网