`

类加载器入门级应用

 
阅读更多

1、类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。最初是为了满足 Java Applet的需要而开发出来的,
  Java Applet需要从远程下载Java类文件到浏览器中并执行。类加载器使得Java类可以被动态加载到Java虚拟机中并执行。


2、基本上所有的类加载器都是java.lang.ClassLoader类的一个实例,java.lang.ClassLoader类的基本职责
就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,
即java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载Java应用所需的资源,
如图像文件和配置文件等。
ClassLoader中与加载类相关的方法如下:
(1)getParent() 返回该类加载器的父类加载器。 
(2)loadClass(String name) 加载名称为 name的类。
(3)findClass(String name) 查找名称为 name的类。
(4)findLoadedClass(String name) 查找名称为 name的已经被加载过的类。
(5)defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java类。
(6)resolveClass(Class<?> c) 链接指定的 Java 类。

 

3、java虚拟机中可以安装多个类加载,系统默认三个主要类加载器,每个类负责加载特定位置的类:
(1)引导类加载器(BootStrp):
   它用来加载 Java的核心库,是用C++来实现的,并不继承自 java.lang.ClassLoader,
   主要加载目录JRE/lib/rt.jar
(2)扩展类加载器(ExtClassLoader):
    它用来加载 Java 的扩展库。主要加载目录JRE/lib/ext/*.jar。
(3)系统类加载器(AppClassLoader):
   它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类,主要加载目录CLASSPATH指定的所有jar或目录。
  Java 应用的类都是由它来完成加载的,可以通过 ClassLoader.getSystemClassLoader()来获取它。


4. 除了引导类加载器(BootStrp)之外,所有的类加载器都有一个父类加载器。可通过getParent()方法得到父类加载器。
   JDK 的实现对于父类加载器是引导类加载器(BootStrp)的情况,getParent()方法返回 null。例:

Java代码 复制代码 收藏代码
  1. public class ClassLoaderTree {   
  2.     public static void main(String[] args) {    
  3.         ClassLoader loader = ClassLoaderTree.class.getClassLoader();    
  4.         while (loader != null) {    
  5.             System.out.println(loader.toString());    
  6.             loader = loader.getParent();    
  7.         }    
  8.     }    
  9. }  
public class ClassLoaderTree {
	public static void main(String[] args) { 
        ClassLoader loader = ClassLoaderTree.class.getClassLoader(); 
        while (loader != null) { 
            System.out.println(loader.toString()); 
            loader = loader.getParent(); 
        } 
    } 
}

 输出结果如下:

Java代码 复制代码 收藏代码
  1. sun.misc.Launcher$AppClassLoader@187c6c7  
  2. sun.misc.Launcher$ExtClassLoader@10b62c9  
sun.misc.Launcher$AppClassLoader@187c6c7
sun.misc.Launcher$ExtClassLoader@10b62c9

 

5、类加载器的委托机制
   当Java虚拟机要加载一个类时,首先当前线程的类加载器去加载线程中的第一个类,如果类A中引用了类B,
Java虚拟机将使用加载类A的加载器来加载类B.也可以直接使用loadClass()方法直接指定某个类加载器去加载某个类。
每个类加载器加载类时,会先委托给其上级类加载器,当所有上级类加载器没有加载到类时,回到发起者类加载器,
如果发起者类加载器还加载不了,则抛出ClassNotFoundException。

 

6、Java 虚拟机是如何判定两个 Java 类是相同的,Java 虚拟机不仅要看类的全名是否相同,
还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,
被不同的类加载器加载之后所得到的类,也是不同的。
为了保证Java核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object类,
也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由Java应用
自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。
通过代理模式,对于Java核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的
都是同一个版本的 Java 核心库的类,是互相兼容的。

 

7、Class.forName
Class.forName是一个静态方法,同样可以用来加载类。该方法有两种形式:

Java代码 复制代码 收藏代码
  1. Class.forName(String name, boolean initialize, ClassLoader loader)  
Class.forName(String name, boolean initialize, ClassLoader loader)

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

Java代码 复制代码 收藏代码
  1. Class.forName(String className)  
Class.forName(String className)

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

Class.forName的一个很常见的用法是在加载数据库驱动的时候。如

Java代码 复制代码 收藏代码
  1. Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()  
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()

 用来加载 Apache Derby 数据库的驱动。

 

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

Java代码 复制代码 收藏代码
  1. public class FileSystemClassLoader extends ClassLoader {    
  2.     private String rootDir;    
  3.     public FileSystemClassLoader(String rootDir) {    
  4.         this.rootDir = rootDir;    
  5.     }    
  6.     protected Class<?> findClass(String name) throws ClassNotFoundException {    
  7.         byte[] classData = getClassData(name);    
  8.         if (classData == null) {    
  9.             throw new ClassNotFoundException();    
  10.         }    
  11.         else {    
  12.             return defineClass(name, classData, 0, classData.length);    
  13.         }    
  14.     }    
  15.     private byte[] getClassData(String className) {    
  16.         String path = classNameToPath(className);    
  17.         try {    
  18.             InputStream ins = new FileInputStream(path);    
  19.             ByteArrayOutputStream baos = new ByteArrayOutputStream();    
  20.             int bufferSize = 4096;    
  21.             byte[] buffer = new byte[bufferSize];    
  22.             int bytesNumRead = 0;    
  23.             while ((bytesNumRead = ins.read(buffer)) != -1) {    
  24.                 baos.write(buffer, 0, bytesNumRead);    
  25.             }    
  26.             return baos.toByteArray();    
  27.         } catch (IOException e) {    
  28.             e.printStackTrace();    
  29.         }    
  30.         return null;    
  31.     }    
  32.     private String classNameToPath(String className) {    
  33.         return rootDir + File.separatorChar    
  34.                 + className.replace('.', File.separatorChar) + ".class";    
  35.     }    
  36. }  
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"; 
    } 
}

 

8.Tomcat6的类加载器(从上至下)
(1)BootStrapClassLoader,Java的核心库,实际没有这个类
(2)ExtensionClassLoader,对于Sun JVM,是sun.misc.Launcher$ExtClassLoader,加载 Java 的扩展库。                     
(3) SystemClassLoader,对于Sun JVM,是sun.misc.Launcher$AppClassLoader,加载java的应用库。                     
(4) CommonClassLoader,对于Tomcat 6,是org.apache.catalina.loader.StandardClassLoader,
加载的类目录通过{tomcat}/conf/catalina.properties中的common.loader指定,
以SystemClassLoader为parent(目前默认定义是common.loader=${catalina.base}/lib,
${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar)
(5) CatalinaClassLoader,加载的类目录通过{tomcat}/conf/catalina.properties中server.loader指定,
 以CommonClassLoader为parent,如果server.loader配置为空,
 则ServerClassLoader 与CommonClassLoader是同一个(默认server.loader配置为空)
(6) SharedClassLoader:加载的类目录通过{tomcat}/conf/catalina.properties中share.loader指定,
 以CommonClassLoader为parent,如果server.loader配置为空,
 则CatalinaClassLoader 与CommonClassLoader是同一个(默认share.loader配置为空)
(7) WebappClassLoader:每个Context一个WebappClassLoader实例,
 负责加载context的/WEB-INF/lib和/WEB-INF/classes目录,
 context间的隔离就是通过不同的WebappClassLoader来做到的。
 由于类定义一旦加载就不可改变,因此要实现tomcat的context的reload功能,
 实际上是通过新建一个新的WebappClassLoader来做的,
 因此reload的做法实际上代价是很高昂的,需要注意的是,JVM内存的Perm区是只吃不拉的,
 抛弃掉的WebappClassLoader加载的类并不会被JVM释放,
 因此tomcat的reload功能如果应用定义的类比较多的话,reload几次就OutOfPermSpace异常了。
(8)JasperLoader:每个JSP一个JasperLoader实例,与WebappClassLoader做法类似,
JSP支持修改生效是通过丢弃旧的JasperLoader,建一个新的JasperLoader来做到的,
同样的,存在轻微的PermSpace的内存泄露的情况。

分享到:
评论

相关推荐

    swing入门级项目全程实录笔记

    在《swing入门级项目全程实录笔记》中,作者从***网站的课程内容出发,记录了使用Java Swing技术创建一个基础项目的过程。Swing是Java的一个图形用户界面工具包,主要用于创建图形用户界面(GUI)。这份笔记详细介绍...

    ARM开发全集(入门级)

    对于入门级的学习者,建议按照以下步骤学习ARM开发: 1. **基础知识**:理解计算机体系结构和操作系统原理,掌握C/C++编程语言。 2. **了解ARM架构**:学习ARM指令集、寄存器使用、异常处理等基本概念。 3. **开发...

    国嵌 实验手册 之1_入门、应用开发、ARM

    手册详细指导了如何在不同的硬件平台上搭建Linux环境,其中包括引导加载器U-Boot的配置与编译,以及Linux内核的裁剪和编译过程。这些内容不仅为初学者提供了入门的第一步,也为有经验的开发者提供了进一步优化系统...

    入门级Python应用程序教程(含完整代码).docx

    【入门级Python应用程序教程】是面向初学者的Python编程教程,特别关注于使用Python进行伺服电机的控制。教程包括完整的代码示例,旨在帮助新手快速上手Python编程和电机控制。作者强调,教程主要针对入门级学习者,...

    JAVA入门级小程序

    【JAVA入门级小程序】 在计算机编程领域,Java是一种广泛使用的高级编程语言,以其“一次编写,到处运行”的特性而著名。对于初学者来说,Java提供了一个友好的学习平台,能够帮助他们快速掌握编程基础。本资源包...

    ExtJS MVC入门级开发案例

    本篇文章将深入讲解ExtJS 4中的MVC概念以及如何通过实际案例进行入门级开发。 首先,理解MVC模式至关重要。Model代表应用程序的数据模型,负责数据的存储和处理;View是用户界面,展示数据并响应用户交互;...

    C#Web应用程序入门经典_程序设计

    《C# Web应用程序入门经典_程序设计》是一本专为初学者设计的IT技术书籍,主要涵盖了使用C#语言开发Web应用程序的基础知识和实践技巧。这本书对于那些希望通过学习C#来构建动态、交互式Web应用的读者来说,是理想的...

    入门级ARM9开发板原理图.rar

    ARM9是ARM公司设计的一种微处理器内核,广泛应用于嵌入式系统中,因其低功耗、高性能的特点,成为入门级嵌入式开发者的首选。本文将深入解析入门级ARM9开发板的原理图,帮助初学者理解其设计思路与工作原理。 1. **...

    weka参考手册 入门级

    ### Weka参考手册入门级知识点概述 #### 一、Weka简介与基本概念 ##### 1.1 引言 Weka(Waikato Environment for Knowledge Analysis)是一款免费且开源的数据挖掘工具,由新西兰怀卡托大学开发。该软件包含了...

    JSF入门级教程

    这篇JSF入门级教程旨在帮助初学者理解并掌握JSF的基础概念和核心功能。 ### 1. JSF简介 JSF提供了一个组件化的开发方式,允许开发者通过拖放组件来创建用户界面,简化了UI开发。它还集成了JavaBeans和Servlet,使得...

    ASP.MVC3.0 入门级教程

    ### ASP.NET MVC3.0 入门级教程详解 #### 一、ASP.NET MVC3.0简介 ASP.NET MVC3.0是微软推出的基于MVC(Model-View-Controller)设计模式的Web应用开发框架,它允许开发者以更加清晰、模块化的方式构建动态Web站点...

    Osworkflow 入门级例子

    通过学习这个入门级的例子,你可以掌握如何定义流程、启动流程实例、处理任务,以及如何与现有的应用程序集成。一旦掌握了基础,你就可以根据具体需求扩展 Osworkflow,构建出复杂而高效的工作流管理系统。

    C#入门级程序的源代码

    【C#入门级程序的源代码】是一个针对初学者的编程资源,主要涵盖了使用C#语言和ASP.NET框架开发窗体应用的基础知识。这个程序是一个简单的图片管理程序,旨在帮助学习者理解如何在C#环境下处理图形用户界面(GUI)...

    linux入门级课件 嵌入式

    ### Linux入门级课件知识点详解 #### Linux 的官方定义与特性 Linux 是一款开源操作系统,其设计目标是为了兼容现代功能齐全的 UNIX 系统。它具备以下关键特性: 1. **真正的多任务处理**:Linux 支持多个进程或...

    c语言ffmpeg入门级qt项目

    通过这个入门级项目,开发者可以学习到如何在QT环境中使用FFmpeg进行音视频的推流、拉流以及处理,为后续的多媒体应用开发打下坚实的基础。不过,要注意的是,FFmpeg和QT的结合需要对两者都有深入的理解,特别是对于...

    SPI入门级Demo(五:服务消费者)

    SPI(Service Provider Interface)是Java提供的一种服务发现机制,它允许开发者通过定义接口并在运行时动态加载实现类来扩展应用程序的功能。这篇“SPI入门级Demo(五:服务消费者)”很可能是讲解如何作为服务消费...

    SpringBoot-入门级简单源码

    **SpringBoot入门级简单源码解析** 在现代Java开发中,SpringBoot因其简化配置和快速启动的优势,成为了许多开发者首选的框架。本项目是基于SpringBoot的入门级示例,旨在帮助初学者快速掌握SpringBoot的基本用法...

    非常适合新手的入门级QT教程

    ### 非常适合新手的入门级QT教程 #### 知识点概览 1. **QT基础概念** - QT是什么? - QT的历史和发展 - QT的应用领域 - QT与其他GUI库的比较 2. **QT Creator安装与配置** - 安装QT Creator的过程 - 创建第...

Global site tag (gtag.js) - Google Analytics