Java类加载器器

Java类加载器(Class loader)是一个很重要的概念,一直想写一篇关于这个的博客,今天看了不少别人的博客,也来写一下,希望能写的明白。

首先明白类加载器的概念:

顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。例如:当我们

有一个A类,先是javac.A.java,生成了A.class文件,这个时候,再java A ,此时jvm就需要加载A类,它只得向类加载器要,此时类加载器用处的时候就来了,将A类加载给jvm,具体怎么加载后面介绍。

明白了类加载器的基本概念,我们就会想在java中一切都是对象,类加载器也是一个java类,那么谁来加载它呢?--->jvm

jvm在启动时,会启动jre/rt.jar里的类加载器:bootstrap classloader,用来加载java核心api;然后启动扩展类加载器ExtClassLoader加载扩展类,并加载用户程序加载器AppClassLoader,并指定ExtClassLoader为他的父类;

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

  • 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用c++来实现的,并不继承自 java.lang.ClassLoader
  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器,在java中使用的是双亲委托模式,-->加载一个类时,首先BootStrap进行寻找,找不到再由Extension ClassLoader寻找,最后才是App ClassLoader,这样,可以有效避免安全问题。

基本概念讲到这里,接下来我们看一下java.lang.ClassLoader这个类:ClassLoader为一个抽象类,由前面可知该类是由bootstrap ClassLoader加载,所有的类加载器器实现类(除了bootstrap ClassLoader)都需要继承该类,

getParent() 返回该类加载器的父类加载器。如果该类的父类加载器为bootstrap classloader则返回null 
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 类。
  1. // First, check if the class has already been loaded
  2. //JVM 规范规定ClassLoader可以在缓存保留它所加载的Class,如果一个Class已经被加载过,则直接从缓存中获取

protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}

//如果没有被加载过 则调用findBootstrapClass0()方法,官方原文:

  1. Invoke findLoadedClass(String) to check if the class has already been loaded.
  2. Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead.
  3. Invoke the findClass(String) method to find the class.

从上面可以看出:类加载器加载一个类,首先通过类名去判别该类是否被加载过,如果被加载过,直接去缓存中取,如果没有被加载过,则用系统类加载器去加载它(system class loader),没有加载成功,调用findBootstrapClass0(name)方法加载,如果两个都没有成功,则调用findclass(String name)方法再找该类。

线程上下文类加载器:

在Thread类里面有两个方法:getContextClassLoader(),setContextClassLoader(cl);分别用来获取和设置线程的上下文类加载器器,如果没有通过setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。(sun.misc.Launcher$AppClassLoader)在线程中运行的代码可以通过此类加载器来加载类和资源。

前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的全部问题。Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到,如实现了 JAXP SPI 的 Apache Xerces所包含的 jar 包。SPI 接口中的代码经常需要加载具体的实现类。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory类中的 newInstance()方法用来生成一个新的DocumentBuilderFactory的实例。这里的实例的真正的类是继承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。

线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。

实际上,在Java应用中所有程序都运行在线程里,如果在程序中没有手工设置过ClassLoader,对于一般的java类如下两种方法获得的ClassLoader通常都是同一个

this.getClass.getClassLoader();  
Thread.currentThread().getContextClassLoader();  
方法一得到的Classloader是静态的,表明类的载入者是谁;方法二得到的Classloader是动态的,谁执行(某个线程),就是那个执行者的Classloader。对于单例模式的类,静态类等,载入一次后,这个实例会被很多程序(线程)调用,对于这些类,载入的Classloader和执行线程的Classloader通常都不同。

Class.forName

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

/********基本概念到此为止,接下来开发自己的类加载器******************/

首先我们需要知道为什么需要开发自己的类加载器,。比如您的应用通过网络来传输 Java 类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来

我们自己写的类加载器一般重写findClass(name)这个方法即可

假设:我们有一个HelloWorld类,代码如下:

1 public class HelloWorld
2 {
3 public HelloWorld() {
4 System.out.println("Test");
5 }
6 public static void main(String[] args) {
7 System.out.println("Hello World");
8 }
9 }  

这时候 我们不想Java的系统类加载器(system class loader)加载它,而是我们自己的类加载器加载它,则让我们的类加载器器类继承java.lang.ClassLoader类,我们自己实现

findclass()方法,步骤:

1:根据构造方法传入的类名找出自己要加载的类

2:利用java.lang.ClassLoader的findLoadedClass(name)方法,判断是否缓存中有该类,如果有直接返回,没有的话,则进行下一步

3:因为我们的目标是将我们要加载的类转换成byte[]数组,然后用this.defineClass(name, b, off, len)方法定义出类,给虚拟机所以我们朝这个方向努力即可,代码不做过多解释,里面有一些java.nio.*的类,会专门写一篇博客讲述

 1 public class MyClassLoader extends ClassLoader{
 2
 3     private String rootDir;
 4
 5     public MyClassLoader(String rootDir){
 6         this.rootDir = rootDir;
 7     }
 8
 9
10     @Override
11     protected Class<?> findClass(String name) throws ClassNotFoundException {
12         Class<?> clazz = this.findLoadedClass(name);
13         if(clazz == null){
14             try {
15                 String classFile = nameToPath(name);
17                 FileInputStream in = new FileInputStream(classFile);
18                 //将目标类写入
19                 FileChannel channel = in.getChannel();
20
21                 ByteArrayOutputStream out = new ByteArrayOutputStream();
22                 WritableByteChannel byteChannel = Channels.newChannel(out);
23                 //定义一个人长度为1024大小的byte缓冲区
24                 ByteBuffer buffer = ByteBuffer.allocate(1024);
25                 while(true){
27                     int readNum = channel.read(buffer);
28                     if(readNum == -1){
30                         break;
31                     }
33                     buffer.flip();
34                     byteChannel.write(buffer);
35                     buffer.clear();
37                 }
38
39                 channel.close();
40                 byte[] byteArray = out.toByteArray();
41                 clazz = this.defineClass(name, byteArray, 0, byteArray.length);
42
43             } catch (FileNotFoundException e) {
44                 e.printStackTrace();
45             } catch (IOException e) {
46                 e.printStackTrace();
47             }
49         }
50         return clazz;
51     }
52
53
54     public  String nameToPath(String className){
55         StringBuffer sb = new StringBuffer(rootDir);
56         className = className.replace(‘.‘, File.separatorChar) + ".class";
57         sb.append(File.separator + className);
58         return sb.toString();
59     }
60
61 }

最后测试一下我们写的类就可以了:

 1 public class Test{
 2     public static void main(String[] args) {
 3         try {
 4             MyClassLoader myclassloader = new MyClassLoader("f:"+File.separator+"workbase");
 5             Class<?> clazz = myclassloader.findClass("HelloWorld");
 6             clazz.newInstance();
 7             System.out.println(clazz.getClassLoader());
 8
 9         } catch (Exception e) {
10             e.printStackTrace();
11         }
12     }
13 }

运行结果:[email protected],可以看出确实使用了我们自己写的类加载器。

总结:个人感觉明白这些应该就OK了吧,当然看到别人博客还有许多更加深层次的东西就不说了

时间: 2024-08-29 09:45:56

Java类加载器器的相关文章

Java类加载器的工作原理

Java类加载器的作用就是在运行时加载类.Java类加载器基于三个机制:委托.可见性和单一性.委托机制是指将加载一个类的请求交给父类加载 器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它.可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类 加载器加载的类.单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类.正确理解类加载器能够帮你解决 NoClassDefFoundError和java.lang.ClassNo

转载:深入探讨Java类加载器

来源: http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 类加载器(class loader)是 Java™中的一个很重要的概念.类加载器负责加载 Java 类的字节代码到 Java 虚拟机中.本文首先详细介绍了 Java 类加载器的基本概念,包括代理模式.加载类的具体过程和线程上下文类加载器等,接着介绍如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™中的应用.

深入探讨 Java 类加载器

转自:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 类加载器(class loader)是 Java™中的一个很重要的概念.类加载器负责加载 Java 类的字节代码到 Java 虚拟机中.本文首先详细介绍了 Java 类加载器的基本概念,包括代理模式.加载类的具体过程和线程上下文类加载器等,接着介绍如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™中的应用. 类加载器是 Java 语言的一个创新,也是

java类加载器——ClassLoader

Java的设计初衷是主要面向嵌入式领域,对于自定义的一些类,考虑使用依需求加载原则,即在程序使用到时才加载类,节省内存消耗,这时即可通过类加载器来动态加载. 如果你平时只是做web开发,那应该很少会跟类加载器打交道,但如果你想深入学习tomcat服务器的架构,它是必不可少的.所谓类加载器,就是用于加载Java类到Java虚拟机中,它负责读取Java字节码,并转换成java.lang.Class类的一个实例,使字节代码.class文件得以运行.一般类加载器负责根据一个指定的类找到对应的字节代码,然

一篇文章读懂Java类加载器

Java类加载器算是一个老生常谈的问题,大多Java工程师也都对其中的知识点倒背如流,最近在看源码的时候发现有一些细节的地方理解还是比较模糊,正好写一篇文章梳理一下. 关于Java类加载器的知识,网上一搜一大片,我自己也看过很多文档,博客.资料虽然很多,但还是希望通过本文尽量写出一些自己的理解,自己的东西.如果只是重复别人写的内容那就失去写作的意义了. 类加载器结构 名称解释: 根类加载器,也叫引导类加载器.启动类加载器.由于它不属于Java类库,这里就不说它对应的类名了,很多人喜欢称Boots

java笔记--理解java类加载器以及ClassLoader类

类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制.JVM中用来完成上述功能的具体实现就是类加载器.类加载器读取.class字节码文件将其转换成java.lang.Class类的一个实例.每个实例用来表示一个java类.通过该实例的newInstance()方法可以创建出一个该类的对象. 类的生命周期: 类从加载到虚拟机内存到被从内存中释放,经历的

Java类加载器工作原理

Java类加载器是用来在运行时加载类(*.class文件).Java类加载器基于三个原则:委托.可见性.唯一性.委托原则把加载类的请求转发给父 类加载器,而且仅加载类当父 类加载器无法找到或者不能加载类时.可见性原则允许子类加载器查看由父类加载器加载的所有的类,但是父类加载器不能查看由子类加载器加载的类.唯一性原则只允许加载一次类文件,这基本上是通过委托原则来实现的并确保子类加载器不重新加载由父类加载器加载过的类.正确的理解类加载器原理必须解决像 NoClassDefFoundError in

全面解析Java类加载器

深入理解和探究Java类加载机制---- 1.java.lang.ClassLoader类介绍 java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java 类,即 java.lang.Class类的一个实例. ClassLoader提供了一系列的方法,比较重要的方法如: 2.JVM中类加载器的树状层次结构 Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写

浅析java类加载器ClassLoader

作为一枚java猿,了解类加载器是有必要的,无论是针对面试还是自我学习. 本文从JDK提供的ClassLoader.委托模型以及如何编写自定义的ClassLoader三方面对ClassLoader做一个简要的总结. JDK中提供的ClassLoader 1. Bootstrap ClassLoader Bootstrap加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib以及%JAVA_HOME%/jre/classes中的类,是最顶

Java 类加载器(转)

java虚拟机中可以安装多个类加载,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap(内嵌在java虚拟机中由C++编写),ExtClassLoader,AppClassLoad    类加载器也是java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap.    java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象    或者