`
perfect5085
  • 浏览: 271267 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

JVM 类加载器详解

阅读更多

类加载器介绍:
类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。
每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。

 

java.lang.ClassLoader 类介绍:
    基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,
    然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。
    除此之外,ClassLoader 还负责加载 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 类。
   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

 

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

  1. 引导类加载器(bootstrap classloader)
  2. 扩展类加载器(extensions classloader)
  3. 应用程序类加载器(application classloader)

1, 引导类加载器(bootstrap classloader)
 负责加载核心的Java库,比如位于<JAVA_HOME>/jre/lib 目录下的vm.jar,core.jar。
 这个类加载器,是JVM核心部分,是用native代码写成的。
 并不继承自 java.lang.ClassLoader。

 

2, 扩展类加载器(extensions classloader)
 负责加载扩展路径下的代码,一般位于<JAVA_HOME>/jre/lib/ext  或者通过java.ext.dirs 这个系统属性指定的路径下的代码。
 这个类加载器是由sun.misc.Launcher$ExtClassLoader 实现的。 
 
3, 应用程序类加载器(application classloader)
 负责加载java.class.path(映射系统参数 CLASSPATH的值) 路径下面的代码,这个类加载器是由 sun.misc.Launcher$AppClassLoader 实现的。 
 可以通过 ClassLoader.getSystemClassLoader() 来获取它

 

java.ext.dirs属性指的是系统属性下的一个key,可以通过System.getProperties()方法获得。
java.class.path属性指的是系统属性下的一个key,可以通过System.getProperties()方法获得。

 

类加载器之间的关系:父子
引导类加载器 
      |
扩展类加载器 
      |
应用程序类加载器

自定义的类加载器的父亲是应用程序类加载器。

 

类的加载模式:父委托模式
 类加载器在加载自己的类之前,先委托加载父类。父类加载器可以是客户化的类加载器或者引导类加载器。
 但是有一点很重要,类加载器只能委托自己的父类加载器,而不能是子类加载器(只能向上不能向下)
 如果应用程序类加载器需要加载一个类,它首先委托扩展类加载器,扩展类加载器再委托引导类加载器。
 如果父类加载器不能加载类,子类加载器就回在自己的库中查找这个类。基于这个特性,类加载器只负责它的祖先无法加载的类。

 

重点注意:当一个类已经被类加载器加载后,这个类需要的任何其他的新类都必须用同一个类加载器加载他们(或者遵循父委托模式,由父类加载器加载)。

 

例如:WhichClassLoader2 引用 WhichClassLoader3,当类加载器加载WhichClassLoader2时,由于WhichClassLoader3被引用了,
 该类加载器会试着加载WhichClassLoader3。

 

注意:开发者通常会使用如下语法通过类加载器机制加载属性文件:
Properties p = new Properties();
p.load(MyClass.class.getClassLoader().getResourceAsStream("myApp.properties"));
这个意思是:如果MyClass 由扩展类加载器加载,而 myApp.properties 文件只能应用程序类加载器加载,
 否则装入属性文件就会失败。

 

每个 Java 类都维护着一个指向定义它的类加载器的引用,通过 getClassLoader() 方法就可以获取到此引用。
例如:targetObject.class.getClassLoader();

 

Java 虚拟机是如何判定两个 Java 类是相同的:
 一个类的全名和一个加载类ClassLoader的实例作为唯一标识,
 也就是:Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。
 只有两者都相同的情况,才认为两个类是相同的。

 

不同两个类的实例之间是不能相互赋值的。

 

父委托模式是为了保证 Java 核心库的类型安全,所有 Java 应用都至少需要引用 java.lang.Object 类,
也就是说在运行的时候,java.lang.Object 这个类需要被加载到 Java 虚拟机中。
如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object 类,而且这些类之间是不兼容的。

 

类加载器在成功加载某个类之后,会把得到的 java.lang.Class 类的实例缓存起来。
下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。
也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass 方法不会被重复调用。

 

线程上下文类加载器:
 类 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)来找到。
 而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。
 引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。
 线程上下文类加载器正好解决了这个问题,Java 应用的线程的上下文类加载器默认就是应用程序类加载器。
 在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。

 

Class.forName:
 Class.forName 是一个静态方法,同样可以用来加载类。该方法有两种形式:
 Class.forName(String name, boolean initialize, ClassLoader loader)
 Class.forName(String className)
 第一种形式的参数 name 表示的是类的全名;initialize 表示是否初始化类;loader 表示加载时使用的类加载器。
 第二种形式则相当于设置了参数 initialize 的值为 true,loader 的值为当前类的类加载器。
 Class.forName 的一个很常见的用法是在加载数据库驱动的时候。
 如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance() 用来加载 Apache Derby 数据库的驱动。
开发自己的类加载器:

public class FileSystemClassLoader extends ClassLoader { 

    private String rootDir; 

    public FileSystemClassLoader(String rootDir) 
    { 
        this.rootDir = rootDir; 
    } 

    protected Class<?> findClass(String name) throws ClassNotFoundException 
    { 
        byte[] classData = getClassData(name); 
        if (classData == null) 
        { 
            throw new ClassNotFoundException(); 
        } 
        else 
        { 
            return defineClass(name, classData, 0, classData.length); 
        } 
    } 

    private byte[] getClassData(String className) 
    { 
        String path = classNameToPath(className); 
        try 
        { 
            InputStream ins = new FileInputStream(path); 
            ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
            int bufferSize = 4096; 
            byte[] buffer = new byte[bufferSize]; 
            int bytesNumRead = 0; 
            while ((bytesNumRead = ins.read(buffer)) != -1) 
            { 
                baos.write(buffer, 0, bytesNumRead); 
            } 
            return baos.toByteArray(); 
        } 
        catch (IOException e) 
        { 
            e.printStackTrace(); 
        } 
        return null; 
    } 

    private String classNameToPath(String className) 
    { 
        return rootDir + File.separatorChar 
                + className.replace('.', File.separatorChar) + ".class"; 
    } 
}

 

一般来说,自己开发的类加载器只需要覆写 findClass(String name) 方法即可。
 java.lang.ClassLoader 类的方法 loadClass() 封装了前面提到的父委托模式的实现。
 该方法会首先调用 findLoadedClass() 方法来检查该类是否已经被加载过;
 如果没有加载过的话,会调用父类加载器的 loadClass() 方法来尝试加载该类;
 如果父类加载器无法加载该类的话,就调用 findClass() 方法来查找该类。
 因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,
 最好不要覆写 loadClass() 方法,而是覆写 findClass() 方法。

 

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

 

我们在加载类时通常会遇到两种异常:
 方法 loadClass() 抛出的是 java.lang.ClassNotFoundException 异常:没有找到这个类会抛出这个异常
 方法 defineClass() 抛出的是 java.lang.NoClassDefFoundError 异常:通常是加载SPI类时,由于SPI的实现类没有定义,导致加载该类的定义失败。

分享到:
评论

相关推荐

    深入Java虚拟机_002_深入详解JVM之类加载器深度剖析、根、扩展及系统类加载器

    本部分我们将深入探讨JVM中的类加载器,特别是根类加载器、扩展类加载器和系统类加载器。 首先,让我们了解类加载的基本过程。当JVM启动时,会触发类加载。这个过程分为三个阶段:加载、链接和初始化。加载阶段,类...

    Java中的JVM类加载机制详解

    JVM的类加载机制是Java程序运行的基础,它包括加载、验证、准备、解析和初始化等阶段。通过理解类加载机制,我们可以更好地管理类的生命周期,实现动态加载和卸载类,以及优化程序的性能。随着Java技术的不断发展,...

    类加载器文件

    ### 类加载器详解 #### 一、类加载器概述 **类加载器(ClassLoader)**是Java虚拟机(JVM)中的一个重要组成部分,它负责将编译好的`.class`文件加载到JVM中,使得这些类可以在Java环境中运行。类加载器不仅能够加载类...

    java类加载器

    ### Java 类加载器详解 #### 一、类加载器概述 在Java中,类加载器(Class Loader)是一项核心机制,用于将字节码(.class文件)加载到JVM中,使其成为运行时的对象。类加载器不仅实现了类的加载功能,还确保了...

    JVM类加载机制详解

    当Java程序启动时,JVM首先会查找JRE目录下的jvm.dll,初始化JVM,然后依次创建Bootstrap Loader、Extended Loader(标准扩展类加载器)和AppClass Loader(系统类加载器)。Bootstrap Loader是最顶层的加载器,它的...

    深入Java虚拟机JVM类加载学习笔记

    在Java虚拟机(JVM)中,类加载器(ClassLoader)是负责将类的`.class`文件加载到内存中的重要组件。理解类加载器的工作原理对于深入掌握JVM以及解决实际开发中的问题至关重要。 **1.1 类加载器分类** 类加载器可以...

    详解JVM类加载机制及类缓存问题的处理方法

    类缓存的主要问题在于,如果JVM加载了同名的类,会出现类冲突问题。解决类缓存问题的方法有多种,例如使用不同的类加载器加载同名的类,或者使用不同的命名空间来避免类冲突。 在JVM中,类加载器会将类加载到内存中...

    Java类加载器的详解

    Java类加载器是Java虚拟机(JVM)的重要组成部分,它的主要职责是将类的字节码文件(.class文件)从文件系统、网络或内存中读取,并转换为运行时的java.lang.Class对象。这个过程是Java动态加载机制的核心,使得Java...

    深入JVM概要 JVM详解

    本文将详细介绍Java虚拟机(JVM)的内部机理和实现原理,从类型的生命周期、方法区、常量池、类加载器、垃圾收集器、栈和局部变量等方面对JVM进行深入解析。 类型的生命周期 类型的生命周期是JVM中最重要的部分,...

    jvm 加载class文件

    ### JVM加载Class文件详解 #### 一、Java与JVM中的Class文件加载机制概述 Java作为一种动态性极强的解释型编程语言,在程序运行时,Java虚拟机(JVM)负责将编译生成的`.class`文件加载到内存中进行执行。在Java...

    Java类加载器.pdf

    ### Java类加载器详解 Java类加载器是Java运行时环境的一个关键组成部分,负责将类文件(.class)从各种来源加载到JVM中。它不仅管理类的生命周期,还确保了类的正确加载和初始化,是Java动态特性的基石。 #### 类...

    30道JVM综合面试题详解含答案(值得珍藏)

    双亲委派模型是JVM加载类的一种策略,确保核心类库(如java.lang.String)由Bootstrap加载器加载,防止用户自定义的类覆盖系统类。当一个类加载器收到加载请求时,它首先会转发给父类加载器,只有当父类加载器无法...

    JAVA ClassLoader 讲解 (类加载器)

    ### Java ClassLoader (类加载器)详解 #### 一、教程提示 如果你正在查看这份文档,在线版中你可以点击下面的任何主题直接跳转到相应的部分。 1. **教程提示** 2. **介绍** 3. **类加载器结构** 4. **编译类加载...

    jvm类装载器原理

    《JVM类装载器原理详解》 Java的JVM(Java Virtual Machine)类装载器是Java运行时系统的重要组成部分,负责在程序运行期间查找并加载类的二进制数据。理解类装载器的工作原理对于优化Java应用性能和实现动态加载类...

    类加载器和双亲委派模型加载类、类的加载优先级的详解.docx

    ### 类加载器与双亲委派模型详解 #### 类的生命周期与加载过程 类的生命周期主要包括七个阶段:加载、验证、准备、解析、初始化、使用和卸载。在这七个阶段中,验证、准备和解析统称为连接阶段。类的加载过程主要...

    14.类加载器1

    ### 类加载器详解 #### 一、类加载器概述 在Java编程中,类加载器(Class Loader)扮演着至关重要的角色。它不仅负责将类的二进制字节流加载到内存中,还参与了类的实例化过程。本文将深入探讨三种核心的类加载器...

    深入理解JVM之类加载机制详解

    "深入理解JVM之类加载机制详解" 本文主要介绍了深入理解JVM之类加载机制的知识点,结合实例形式详细分析了类加载机制原理、过程及相关操作注意事项。 1. 类加载机制概述 类加载机制是Java虚拟机把描述类的数据从...

    一文读懂Jvm类加载机制

    这个阶段会将类加载器概念引入,类加载器负责将.class文件加载到JVM中。在这个阶段,JVM会将类加载到JVM中,并为类分配内存空间。 验证阶段是指对加载到JVM中的类进行验证,以确保其符合Java语言的规范。这个阶段...

    classloader类加载器_基于java类的加载方式详解

    Java类加载器(ClassLoader)是Java虚拟机(JVM)中的一个重要组成部分,用于将Java类文件加载到JVM中,以便能够执行Java程序。在Java中,类加载器的设计采用了一种称为“双亲委派模式”(Parent Delegation Model)...

Global site tag (gtag.js) - Google Analytics