java类加载器

来源:互联网 发布:腾讯视频怎么没有mac版 编辑:程序博客网 时间:2024/06/18 10:18

Java虚拟机中可以安装多个类加载器,系统默认主要有三个类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader。当然也可以自定义类加载器,自定义的加载器必须继承ClassLoader。

类加载器也是Java类,因为其它Java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这个就是BootStrap。BootStrap它是嵌套在Java虚拟机内核中的,jvm启动,这个类就会启动,它是由c++语言编写的。

Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载。

例如:

Java代码

public class ClassLoaderTest {         public static void main(String[] args) {           ClassLoader loader = ClassLoaderTest.class.getClassLoader();             while (loader != null) {               System.out.println(loader.getClass().getName());               loader = loader.getParent();           }           System.out.println(loader);       }   }  public class ClassLoaderTest {    public static void main(String[] args) {        ClassLoader loader = ClassLoaderTest.class.getClassLoader();        while (loader != null) {            System.out.println(loader.getClass().getName());            loader = loader.getParent();        }        System.out.println(loader);    }} 

将输出:

Output代码

sun.misc.Launcher$AppClassLoader   sun.misc.Launcher$ExtClassLoader   null //null就代表是BootStrap类加载器,该加载器是顶级加载器,没有父类加载器  

深入

在Java中每个类都是由某个类加载器的实体来载入的,因此在Class类的实体中,都会有字段记录载入它的类加载器的实体(当为null时,其实是指Bootstrap ClassLoader)。 在java类加载器中除了引导类加载器(既Bootstrap ClassLoader),所有的类加载器都有一个父类加载器(因为他们本身自己就是java类)。而类的加载机制是遵循一种委托模式:当类加载器有加载类的需求时,会先请求其Parent加载(依次递归),如果在其父加载器树中都没有成功加载该类,则由当前类加载器加载。

Java的类加载器分为以下几种:

(1) Bootstrap ClassLoader

Bootstrap ClassLoader用C++实现,一切的开始,是所有类加载器的最终父加载器。负责将一些关键的Java类,如java.lang.Object和其他一些运行时代码先加载进内存中。

(2) ExtClassLoader

ExtClassLoader用java实现,是Launcher.java的内部类,编译后的名字为:Launcher$ExtClassLoader.class 。此类由Bootstrap ClassLoader加载,但由于Bootstrap ClassLoader已经脱离了java体系(c++),所以Launcher$ExtClassLoader.class的Parent(父加载器)被设置为null;它用于装载Java运行环境扩展包(jre/lib/ext)中的类,而且一旦建立其加载的路径将不再改变。

(3) AppClassLoader

AppClassLoader用java实现,也是是Launcher.java的内部类,编译后的名字为:Launcher$AppClassLoader.class 。AppClassLoader是当Bootstrap ClassLoader加载完ExtClassLoader后,再被Bootstrap ClassLoader加载。所以ExtClassLoader和AppClassLoader都是被Bootstrap ClassLoader加载,但AppClassLoader的Parent被设置为ExtClassLoader。可见Parent和由哪个类加载器来加载不一定是对应的。

这个类装载器是我们经常使用的,可以调用ClassLoader.getSystemClassLoader() 来获得,如果程序中没有使用类装载器相关操作设定或者自定义新的类装载器,那么我们编写的所有java类都会由它来装载。而它的查找区域就是我们常常说到的Classpath,一旦建立其加载路径也不再改变。

(4) ClassLoader

ClassLoader一般我们自定义的ClassLoader从ClassLoader类继承而来。比如:URLClassloader是ClassLoader的一个子类,而URLClassloader也是ExtClassLoader和AppClassLoader的父类(注意不是父加载器)。

类加载器之间的父子关系为:

Output代码

BootStrap -> ExtClassLoader -> AppClassLoader (即通常所说的System ClassLoader)  

示意图:

它们的管辖范围依次是:

BootStrap------>JRE/lib/rt.jar ExtClassLoader---------->JRE/lib/ext/*.jar AppClassLoader---------->CLASSPATH指定的所有jar或目录。 
classLoader常用方法:1. 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虚拟机要加载一个类时,到底该派哪个类加载器去加载呢?

(1) 首先是当前线程的类加载器去加载线程中的第一个类。

(2) 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。

(3) 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

每个类加载器加载类时,又先委托给其上级类加载器。当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFoundException,不是再去找发起者类加载器的儿子。因为没有getChlid方法,即使有,那么当有多个儿子,找哪一个呢?

那么,能不能自己写个类叫java.lang.System?

一般情况下不能,因为类加载采用委托机制,这样可以保证parent类加载器优先,也就是总是使用parent类加载器能找到的类,这样总是使用java系统提供的System。因为每个类加载器加载类时,又先委托给其上级类加载器,java.lang.System在BootStrap中最先加载。但是我们可以写一个类加载器来加载我们自己写的java.lang.System类。

当需要编写自己的类加载器时:

  • 自定义的类加载器必须继承ClassLoader。
  • 重写loadClass方法与findClass方法。loadClass中先调用父类的loadClass,然后调用findClass,通常情况下只覆盖findClass就可以。
  • 重写defineClass方法。
例:文件系统类加载器

系统类加载器

第一个类加载器用来加载存储在文件系统上的 Java 字节代码。完整的实现如下所示。单 6. 文件系统类加载器

 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";     }  }

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

类 FileSystemClassLoader的 findClass()方法首先根据类的全名在硬盘上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass()方法来把这些字节代码转换成 java.lang.Class类的实例。


Q&A:

1.如何在程序中更改classspath?

URL url = new File(newClassPath).toUri().toUrl();ClassLoader cl = new URLClassLoader(new URL[]{url});Class<?> foo = cl.loadClass("xxxx.Foo");

2.ClassNOtFoundExceptionNoClassDefFoundError的差别?

Java支持使用Class.forName方法来动态地加载类,任意一个类的类名如果被作为参数传递给这个方法都将导致该类被加载到JVM内存中,如果这个类在类路径中没有被找到,那么此时就会在运行时抛出ClassNotFoundException异常

要解决这个问题很容易,唯一需要做的就是要确保所需的类连同它依赖的包存在于类路径中。当Class.forName被调用的时候,类加载器会查找类路径中的类,如果找到了那么这个类就会被成功加载,如果没找到,那么就会抛出ClassNotFountException,除了Class.forName,ClassLoader.loadClass、ClassLOader.findSystemClass在动态加载类到内存中的时候也可能会抛出这个异常。

NoClassDefFoundError产生的原因:

如果JVM或者ClassLoader实例尝试加载(可以通过正常的方法调用,也可能是使用new来创建新的对象)类的时候却找不到类的定义。要查找的类在编译的时候是存在的,运行的时候却找不到了。这个错误往往是你使用new操作符来创建一个新的对象但却找不到该对象对应的类。这个时候就会导致NoClassDefFoundError.

由于NoClassDefFoundError是有JVM引起的,所以不应该尝试捕捉这个错误。

解决这个问题的办法就是:查找那些在开发期间存在于类路径下但在运行期间却不在类路径下的类



ref:http://express.ruanko.com/ruanko-express_41/tech-overnight4.html

       http://www.ibm.com/developerworks/cn/java/j-lo-classloader/#code5

       http://my.oschina.net/jasonultimate/blog/166932


原创粉丝点击