`
perfect5085
  • 浏览: 270030 次
  • 性别: 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之类加载器深度剖析、根、扩展及系统类加载器

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

    JAVA-JVM-01类加载机制

    java中JVM类加载器和双亲委派机制剖析,类加载示例、加载器示例、自定义一个类加载器示例;Tomcat自定义加载器详解

    JVM的类加载过程以及双亲委派模型详解

    主要介绍了JVM的类加载过程以及双亲委派模型详解,类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。,需要的朋友可以参考下

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

    主要给大家介绍了关于JVM类加载机制及类缓存问题的处理方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

    Java类加载器:静态变量初始化.docx

    Java 类加载器静态变量初始化机制详解 Java 类加载器是 Java 语言的核心组件之一,负责将 Java 字节码文件加载到内存中,以便 JVM 可以执行它们。在 Java 中,类加载器是通过委派机制来实现的,即一个类加载器可以...

    Java 类加载机制详解

    在我们使用一个类之前,JVM需要先将该类的字节码文件(.class文件)从磁盘、网络或其他来源加载到内存中,并对字节码进行解析生成对应的Class对象,这是类加载器的功能。我们可以利用类加载器,实现类的动态加载。 ...

    Java虚拟机工作原理详解

    类加载器之间存在一种委派模式(Delegation Mode),当 JVM 加载一个类的时候,下层的加载器会将任务委托给上一层类加载器,上一层加载检查它的命名空间中是否已经加载这个类,如果已经加载,直接使用这个类。...

    JVM 运行时数据区域,垃圾回收机制,类加载机制三大功能详解.docx

    VM相关的一些内容,比如下面的这三个内容算是比较核心知识点了 运行时数据区域: 在运行时数据区里存储类Class文件元数据...类加载机制: 虚拟机首先需要把编译完成的字节码文件通过类加载器来加载到运行时数据区域

    Java进阶教程解密JVM视频教程

    手把手视频详细讲解项目开发全过程,需要的小伙伴自行百度网盘下载,链接见附件,永久...5. 类加载器 6. 运行期优化 第五章:内存模型 1. Java 内存模型 2. 可见性 3. 有序性 4. CAS 与原子类 5. synchronized 优化

    深入理解JVM内存结构及运行原理全套视频加资料.txt

    包括JVM执行过程、虚拟机类加载机制、运行时数据区、GC、类加载器、内存分配与回收策略等,全套视频加资料高清无密码  第1讲 说在前面的话 免费 00:05:07  第2讲 整个部分要讲的内容说明 免费 00:06:58  第3讲...

    JVM讲解视频.zip

    类加载器大全 手写自定义类加载器 类加载器双亲委派机制 oJVM内存模型深度理解 JVM内存模型结构 线程栈及栈帧内部结构详解 程序计数器详解 本地方法栈详解 堆详解 JVM启动参数设置 堆内存...

    深入理解Java虚拟机视频教程(jvm性能调优+内存模型+虚拟机原理)视频教程

    第93节类加载器00:22:41分钟 | 第94节双亲委派模型00:17:03分钟 | 第95节运行时栈帧结构00:08:46分钟 | 第96节局部变量表00:20:48分钟 | 第97节操作数栈00:08:36分钟 | 第98节动态连接00:02:56分钟 | 第99节...

    免费超全面的Java基础类型,容器,并发,IO流,面向对象,Web编程等代码总结

    JVM类加载机制 JVM运行时区数据 JVM执行引擎和垃圾回收 基础语法 理解Java中对象基础Object类 基本数据类型,核心点整理 特殊的String类,以及相关扩展API 日期与时间API详解 流程控制语句,和算法应用 函数式编程...

    详解JVM的垃圾回收算法来做细节.docx

    我们知道目前的JVM的垃圾回收器都是采用 可达性分析算法 标记存活对象,该算法首先需要找到GC Roots,然后通过这些根节点向下搜索,能搜索到的就标记为存活对象,未被标记的最后就会被垃圾回收器回收。那你是否想...

    java反射机制原理详解.docx

    我们创建一个类,通过编译,生成对应的.calss文件,之后使用java.exe加载(jvm的类加载器)此.class文件,此.class文件加载到内存以后,就是一个运行时类,存在缓存区,那么这个运行时类的本身就是一个class的实例 ...

    java中JVM中如何存取数据和相关信息详解

    主要介绍了JVM中如何存取数据和相关信息详解,Java源代码文件(.java后缀)会被Java编译器编译为字节码文件,然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。JVM中怎么存取数据和...

    圣思园Java视频 java se & java web

    深入详解JVM之类加载器深度剖析, Javascript, Node,

    java内核源码-JavaCompass:「Java指南针」为你学习Java指明方向。内容涵盖互联网Java工程师所需要掌握的核心知识,涉及J

    十种垃圾收集器详解 JVM调优工具详解 GC日志详细分析 JVM调优实战 Mysql性能调优 SQL执行原理详解 索引底层剖析 执行计划与SQL优化 Mysql锁机制与事务隔离级别详解 并发编程 JMM内存模型 并发同步处理 并发包之tools...

    JAVA基础技术框架详解二.pdf

    * JVM 的架构:包括类加载器、运行时数据区域、执行引擎等。 * JVM 的生命周期:包括类加载、实例化、垃圾回收等。 * JVM 的性能优化:包括 MinorGC、MajorGC、FullGC 等垃圾回收算法。 Java 集合框架 * Java 集合...

    【JVM和性能优化】3.JVM的执行子系统

    文章目录Class 文件格式字节码Class类的本质Class文件格式类加载机制加载验证准备解析初始化类加载器双亲委派机制栈桢JVM方法调用详解方法解析静态分派动态分派参考 Class 文件格式 一般情况下Java代码执行流程如下...

Global site tag (gtag.js) - Google Analytics