一、ClassLoader概念

  ClassLoader是用来动态的加载class文件到虚拟机中,并转换成java.lang.class类的一个实例,每个这样的实例用来表示一个java类,我们可以根据Class的实例得到该类的信息,并通过实例的newInstance()方法创建出该类的一个对象,除此之外,ClassLoader还负责加载Java应用所需的资源,如图像文件和配置文件等。

  ClassLoader类是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。ClassLoader类使用委托模型来搜索类和资源。每个 ClassLoader实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。

  注意:程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制来动态加载某个class文件到内存中。

二、JVM平台提供三层classLoader

  1. Bootstrap classLoader:采用native code实现,是JVM的一部分,主要加载JVM自身工作需要的类,如java.lang.*、java.uti.*等; 这些类位于$JAVA_HOME/jre/lib/rt.jar。Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。
  2. ExtClassLoader:扩展的class loader,加载位于$JAVA_HOME/jre/lib/ext目录下的扩展jar。
  3. AppClassLoader:系统class loader,父类是ExtClassLoader,加载$CLASSPATH下的目录和jar;它负责加载应用程序主函数类。

  其体系结构图如下:

  

  如果要实现自己的类加载器,不管是实现抽象列ClassLoader,还是继承URLClassLoader类,它的父加载器都是AppClassLoader,因为不管调用哪个父类加载器,创建的对象都必须最终调用getSystemClassLoader()作为父加载器,getSystemClassLoader()方法获取到的正是AppClassLoader。

  注意:Bootstrap classLoader并不属于JVM的等级层次,它不遵守ClassLoader的加载规则,Bootstrap classLoader并没有子类。

三、JVM加载class文件到内存有两种方式

  1. 隐式加载:不通过在代码里调用ClassLoader来加载需要的类,而是通过JVM来自动加载需要的类到内存,例如:当类中继承或者引用某个类时,JVM在解析当前这个类不在内存中时,就会自动将这些类加载到内存中。
  2. 显示加载:在代码中通过ClassLoader类来加载一个类,例如调用this.getClass.getClassLoader().loadClass()或者Class.forName()。

四、ClassLoader加载类的过程

  1. 找到.class文件并把这个文件加载到内存中
  2. 字节码验证,Class类数据结构分析,内存分配和符号表的链接
  3. 类中静态属性和初始化赋值以及静态代码块的执行

五、自定义类加载器

  1、为何要自定义类加载器?

  JVM提供的类加载器,只能加载指定目录的jar和class,如果我们想加载其他位置的类或jar时,例如加载网络上的一个class文件,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的类加载器。

  2、如何实现自定义的类加载器?

  我们实现一个ClassLoader,并指定这个ClassLoader的加载路径。有两种方式:

  方式一:继承ClassLoader,重写父类的findClass()方法,代码如下:

 
 1 import java.io.ByteArrayOutputStream;
2 import java.io.File;
3 import java.io.FileInputStream;
4 import java.io.IOException;
5 public class PathClassLoader extends ClassLoader
6 {
7 public static final String drive = "d:/";
8 public static final String fileType = ".class";
9
10 public static void main(String[] args) throws Exception
11 {
12 PathClassLoader loader = new PathClassLoader();
13 Class<?> objClass = loader.loadClass("HelloWorld", true);
14 Object obj = objClass.newInstance();
15 System.out.println(objClass.getName());
16 System.out.println(objClass.getClassLoader());
17 System.out.println(obj.getClass().toString());
18 }
19
20 public Class<?> findClass(String name)
21 {
22 byte[] data = loadClassData(name);
23 return defineClass(name, data, 0, data.length);// 将一个 byte 数组转换为 Class// 类的实例
24 }
25 public byte[] loadClassData(String name)
26 {
27 FileInputStream fis = null;
28 byte[] data = null;
29 try
30 {
31 fis = new FileInputStream(new File(drive + name + fileType));
32 ByteArrayOutputStream baos = new ByteArrayOutputStream();
33 int ch = 0;
34 while ((ch = fis.read()) != -1)
35 {
36 baos.write(ch);
37 }
38 data = baos.toByteArray();
39 } catch (IOException e)
40 {
41 e.printStackTrace();
42 }
43 return data;
44 }
45 }
 

  在第13行,我们调用了父类的loadClass()方法,该方法使用指定的二进制名称来加载类,下面是loadClass方法的源代码:

 
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name))
{
// 第一步先检查这个类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null)
{
long t0 = System.nanoTime();
try
{
//parent为父加载器
if (parent != null)
{
//将搜索类或资源的任务委托给其父类加载器
c = parent.loadClass(name, false);
} else
{
//检查该class是否被BootstrapClassLoader加载
c = findBootstrapClassOrNull(name);
}
}
catch (ClassNotFoundException e)
{
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null)
{
//如果上述两步均没有找到加载的class,则调用findClass()方法
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve)
{
resolveClass(c);
}
return c;
}
}
 

  这个方法首先检查指定class是否已经被加载,如果已被加载过,则调用resolveClass()方法链接指定的类,如果还未加载,则先将搜索类或资源的任务委托给其父类加载器,检查该class是否被BootstrapClassLoader加载,如果上述两步均没有找到加载的class,则调用findClass()方法,在我们自定义的加载器中,我们重写了findClass方法,去我们指定的路径下加载class文件。

  另外,我们自定义的类加载器没有指定父加载器,在JVM规范中不指定父类加载器的情况下,默认采用系统类加载器即AppClassLoader作为其父加载器,所以在使用该自定义类加载器时,需要加载的类不能在类路径中,否则的话根据双亲委派模型的原则,待加载的类会由系统类加载器加载。如果一定想要把自定义加载器需要加载的类放在类路径中, 就要把自定义类加载器的父加载器设置为null。

  方式二:继承URLClassLoader类,然后设置自定义路径的URL来加载URL下的类。

  我们将指定的目录转换为URL路径,然后重写findClass方法。

六、实现类的热部署

  1、什么是类的热部署?

  所谓热部署,就是在应用正在运行的时候升级软件,不需要重新启用应用。

  对于Java应用程序来说,热部署就是运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署。

  类装入器不能重新装入一个已经装入的类,但只要使用一个新的类装入器实例,就可以将类再次装入一个正在运行的应用程序。

  2、如何实现Java类的热部署

  前面的分析,我们已经知道,JVM在加载类之前会检查请求的类是否已经被加载过来,也就是要调用findLoadedClass方法查看是否能够返回类实例。如果类已经加载过来,再调用loadClass会导致类冲突。

  但是,JVM判断一个类是否是同一个类有两个条件:一是看这个类的完整类名是否一样(包括包名),二是看加载这个类的ClassLoader加载器是否是同一个(既是是同一个ClassLoader类的两个实例,加载同一个类也会不一样)。

  所以,要实现类的热部署可以创建不同的ClassLoader的实例对象,然后通过这个不同的实例对象来加载同名的类。

七、参考资料

  1、http://www.2cto.com/kf/201403/284030.html

标签: Java

最新文章

  1. github for window的代理设置方法
  2. django queryset values&amp;values_list
  3. 关于@Html.Action()的异常“控制器或该控制器未实现 IController。”
  4. 解决Windows Server2008 R2中IE开网页时弹出阻止框(Windows Server2008网页无法打开的问题)
  5. 用C#制作PDF文件全攻略
  6. Javascript的一种代码结构方式——插件式
  7. C#基础整理
  8. Linq to sql并发与事务
  9. Asp.net 菜单控件
  10. [转] Python 模块学习:os模块
  11. edit distance leetcode
  12. 使用iftop网络流量监控
  13. iOS知识点、面试题 之三
  14. 冲顶大会APP技术选型及架构设计
  15. 如何将Ubuntu部署到U盘中,用U盘安装linux操作系统
  16. css引用与html语义化
  17. Java常见异常及解释
  18. sql存储过程算法
  19. node api 之:fs
  20. sysbench压力工具报错:

热门文章

  1. FZU2169 shadow题解
  2. [游戏模版6] Win32 graph
  3. sqlzoo.net刷题3
  4. Python ftplib
  5. Excel中连接函数CONCATENATE()
  6. Tomcat启动过程(二):EndPoint解析
  7. asp导航条子菜单横向
  8. java web 入门级 开发 常用页面调试方法
  9. python接口自动化-post请求1
  10. 了解fastadmin标准的控制器模块js的表格事件
  11. activity select problem(greedy algorithms)
  12. java中的数据加密5 数字证书
  13. 漫谈数据仓库之拉链表(原理、设计以及在Hive中的实现)
  14. django学习网站
  15. iconfont 在项目中的简单使用
  16. Variable() placeholder() constant() 的区别
  17. 脚本其实很简单-windows配置核查程序(1)
  18. java中copy 一个list集合的方法
  19. Linux学习笔记—vim程序编辑器
  20. Mybatis与Hibernate的详细对比