Java类加载机制

一、定义

     类加载,这个词在java中并不陌生,但是只有感性的认识,没有达到理性的程度。大神说的。

java因为applet的需要,引入了类加载器的概念。使得java类能够被动态的加载到虚拟机。

java程序首先编写成.java文件,可以阅读的。通过编译后变成字节码.class文件,这时,类加载器读取.class文件,生成Class对象表示一个类,然后通过newInstance可以实例化出对象。

java虚拟机有实现自己的加载器:

1.引导类加载器(bootstrap class loader),该类原生代码实现,负责加载Java 的核心库

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

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

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

除了引导类加载器外,其它类加载器都有父类加载器: 系统类加载器>扩展类加载器>引导类加载器。对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器。


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

这样做的目的是为了保证 Java 核心库的类型安全。保证了Java的核心库的加载统一交给引导类加载器去完成。保证了 Java  应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。同样Java虚拟机区分一个类不仅和 类的名称有关,还和加载该类的加载器有关,相同的class,被不同的类加载器加载,生成的是不同的Class实例。

不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的。 Java 类空间。这种技术在许多框架中都被用到。


三、类加载过程

. ClassLoader 中与加载类相关的方法
        方法                                                               说明
        getParent()                                     返回该类加载器的父类加载器。
        loadClass(String name)              加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。
        findClass(String name)               查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。
        findLoadedClass(String name) 查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class
      类的实例。
        defineClass(String name, byte[] b, int off, int len) 把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的。
        resolveClass(Class<?> c)         链接指定的 Java 类。
        对于给出的方法,表示类名称的 name 参数的值是类的二进制名称。需要注意的是内部类的表示,如  com.example.Sample$1 和 com.example.Sample$Inner  等表示方式。

类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用 defineClass 来实现的;而启动类的加载过程是通过调用 loadClass 来实现的。前者称为一个类的定义加载器(defining  loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。方法 loadClass() 抛出的是 java.lang.ClassNotFoundException 异常;方法 defineClass()  抛出的是 java.lang.NoClassDefFoundError 异常。这个异常信息非常利于调试。

四、线程上下文类加载器(context class loader)

线程上下文类加载器 是从 JDK 1.2 开始引入的。类 java.lang.Thread 中的方法     getContextClassLoader() 和 setContextClassLoader(ClassLoader cl) 用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl) 方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers 包中。这些 SPI的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到。Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。
线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。


五、容器中的 Web 应用类加载器实现

Apache Tomcat 来说,每个 Web
      应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是   Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。

BootStrap classloader(加载JRE/lib下的rt.jar和其他重要jar文件)

                                                    

ExtClassLoader (加载JRE/lib/ext下的jar文件,Java扩展框架使用)

                                                    

AppClassLoader ($CATALINA_HOME/bin/bootstrap.jar commons-logging -api.jarcommons-daemon.jar$JAVA_HOME/lib/tools.jarjmx.jar)

                                       

common (加载$CATALINA_HOME/common/Tomcat本身和Web App共享类)

                                                                              

Catalina (加载$CATALINA_HOME /server/Tomcat本身使用的类)

Shared (加载$CATALINA_BASE /shared/,所有Web App共享类)

                                         ↑

WebappClassLoader (加载各个Web Appclass,不同的Web App被隔离开)

几条简单的原则: 每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes 和 WEB-INF/lib  目录下面。 多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。 当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。

六、OSGI类加载器实现

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

OSGi 中的每个模块(bundle)都包含 Java 包和类。模块可以声明它所依赖的需要导入(import)的其它模块的 Java 包和类(通过 Import-Package),也可以声明导出(export)自己的包和类,供其它模块使用(通过  Export-Package)。也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类。这是通过 OSGi 特有的类加载器机制来实现的。OSGi 中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 Java 包和类。当它需要加载 Java核心库的类时(以 java 开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的 Java 类时,它会代理给导出此Java 类的模块来完成加载。模块也可以显式的声明某些 Java 包和类,必须由父类加载器来加载。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值