Java 类装载器

 哈哈,今天没事做,但心情不错,所以写这篇文章.

     类装载器是Java程序运行时不可缺少的一部分,它的任务是把由Java源程序编译成的class文件读入到内存中,确切的说是装入到JVM的内存中,应为JVM是一台抽象的计算机,它有着自己的CPU,内存等. 在class文件中包含了一个类的各种信息,当执行java XXX命令运行一个以XXX为初始类的Java程序时,类装载器会把XXX的class文件装载到内存,然后根据该class文件中包含的信息在方法区中生成一个JVM的内部数据结构,该数据结构里的内容是XXX类的类型信息,大家知道每个类都会对应一个Class对象,该对象就是JVM根据这个类型信息在堆区中创建的,这个对象含有一个指向方法区中对应类型信息的指针,除此之外,该Class对象还包含一个指向类装载器的指针,根据这个Class对象就可以创建对象,可以访问装载本类的类装载器,还可以访问方法区中的数据(Java反射机制就是这样实现的).

 

一.Java中有四中类装载器:启动类装载器,扩展类装载器,类路径类装载器,自定义类装载器.下面将围绕着这三种类装载器做一个详细的描述:

1.启动类装载器:也叫引导类装装载器,它是JVM内部的类装载器,用C++编写,作用是装载Java核心类库中的类,比如说一个Java虚拟机目录为D:/JDK1.5,则启动类装载器负责装载D:/JDK1.5/jre/lib/rt.jar,rt.jar为Java核心类库,比如我们常用到的java.lang包中的System类,由于启动类装载器是供JVM使用的,Java程序并不能直接使用,下面举个例子来说明:

class Test {
   public static void main(String[] args) {
      System.out.println(System.class.getClassLoader());

   }
}

上述程序打印出装载System类的类装载器,而打印出的却是null,说明了启动类装载器不能直接调用.

2.扩展类装载器:它负责装载Java的扩展类,比如说一个Java虚拟机目录为D:/JDK1.5,则它负责加载D:/JDK1.5/jre/lib/ext下的类库中的类,假如ext目录下有个jar包,包里有个Test类,包名为org.test,则可以用org.test.Test.class.getClassLoader()方法打印出该类加载器,可以看到结果为sun.misc.Launcher$ExtClassLoader,说明Test类由扩展类装载器装载,可以根据需要把自己写的类打成jar包放到ext目录下.

3.类路径类装载器:也可以叫系统类装载器,它负责装载当前classpath下的类,也就是当我们要运行一个Java程序时初始类的包所在的目录,比如在D:/test下有个Test.class,它是程序的初始类,当执行Java Test命令时D:/test就是classpath. 一般classpath下的类都是我们自定义的类,同样Test.getClassLoader()方法得到的结果是sun.misc.Launcher$AppClassLoader,说明Test类由类路径类装载器加载.

4.自定义类装载器:Java允许用户可以自定义类装载器来装载类,下面举个例子,使用URLClassLoader,从网络上装载一个类:

首先自己创建一个Java Web工程,Web根目录为WebRoot(WEB-INF的上一级目录),在WebRoot下创建一个包名为org.test.TestImpl的类,即TestImpl.class文件在WebRoot目录的org/test目录下,我们在定义一个接口,名为Test,接口有个test()方法,让Test实现它的test()方法,为了测试方便,把Test接口放在classes目录下:

Test类代码如下:

package org.test.TestImpl;

public class TestImpl implements Test{
   public void add(){
      System.out.println("TestImpl#add()");
   }
}

在classes目录下写个测试类TestLoader :

public class TestLoader {
 public static void main(String[] args) throws Exception{
  URL url = new URL("http://localhost:8080/WebRoot/");
  URLClassLoader loader = new URLClassLoader(new URL[]{url});
  Class clazz = loader.loadClass("org.test.TestImpl");
  Test t = (Test) clazz.newInstance();
  t.add();
 }
}

URLClassLoader从http://localhost:8080/WebRoot/下加载TestImpl,要执行TestLoader类,要把Web项目部署到Tomcat中,启动Tomcat后(上述的端口可以自己修改,tomcat的内容这里就不再敖述),执行TestLoader,可以看到程序打印出TestImpl#add(),说明加载成功. 我们可以在TestLoader的main方法中添加System.out.println(loader),结果答应出java.net.URLClassLoader,说明URLClassLoader为自定义类装载器.

 

二.由于除启动类装载器之外的所有类装载器都是由Java实现的,这些类加载器也是Java类,也需要用相应的类加载器来装载,那么各种类加载器分别是用什么加载器来加载呢?下面描述一下这个问题,请看如下代码,其中WebRequest是httpunit的包,我把它放到了ext目录下,用来测试扩展类装载器:

public class Test{
 public static void main(String[] args) throws Exception{
  ClassLoader loader0 = Thread.currentThread().getContextClassLoader();
  URLClassLoader loader1 = new URLClassLoader(new URL[]{});
  System.out.println(loader0);
  System.out.println(loader0.getClass().getClassLoader());
  System.out.println("======================");
  System.out.println(loader1);
  System.out.println(loader1.getClass().getClassLoader());
  System.out.println("======================");
  System.out.println(WebRequest.class.getClassLoader());
  System.out.println(WebRequest.class.getClassLoader().getClass().getClassLoader());
 }
}

上述代码中分别把装载类路径类装载器(系统类装载器)loader0,自定义类装载器loader1,扩展类装载器WebRequest.class.getClassLoader()这三个类装载器的类装载器打印出来,结果为:

sun.misc.Launcher$AppClassLoader@187c6c7
null
======================
java.net.URLClassLoader@7d772e
null
======================
sun.misc.Launcher$ExtClassLoader@10b62c9
null

从结果中看出,类路径类装载器,自定义类装载器,扩展类装载器都是由启动类装载器装载的.

 

三.一个类中如果引用了其他类,如果被引用的类不是Java核心类,不是Java扩展类,也不是从网络加载的类, 而在classpath下,则装载被引用类的类装载器是装载第一个的类装载器,如下代码说明这个问题:

public class Test{
 public static void main(String[] args) throws Exception{
  Test0 t0 = new Test0();
  Test1 t1 = new Test1();
  System.out.println(t0.getClass().getClassLoader());
  System.out.println(t1.getClass().getClassLoader());
  System.out.println(Thread.currentThread().getContextClassLoader());
 }
}

Test0和Test1都在classpath下,最后得到当前线程的类装载器(装载Test的类装载器),打印结果为:

sun.misc.Launcher$AppClassLoader@187c6c7
sun.misc.Launcher$AppClassLoader@187c6c7
sun.misc.Launcher$AppClassLoader@187c6c7

从中看出得到的类装载器实例的hash码是相同的,说明都是用同一个类加载器加载的,也就是装载Test的类装载器.

 

四.除启动类装载器之外的任何类装载器有且仅有一个父加载器,扩展类装载器的父装载器为启动类装载器,类路径类装载器的父装载器为扩展类装载器,自定义类装载器的父装载器为类路径类装载器.如下代码说明:

public class Test{
 public static void main(String[] args) throws Exception{
  URLClassLoader loader0 = new URLClassLoader(new URL[]{});
  System.out.println(loader0.getParent());
  System.out.println(loader0.getParent().getParent());
  System.out.println(loader0.getParent().getParent().getParent());
 }
}

打印结果为:

sun.misc.Launcher$AppClassLoader@187c6c7
sun.misc.Launcher$ExtClassLoader@10b62c9
null

正好说明了上述的结论.

 

五.Java类装载器的委派机制:除启动类装载器外的任何类装载器都有一个默认的委派类装载器,比如:当一个自定义类装载器loader0试图去装载网络上的一个类时,它不会自己先去加载,而是先将加载任务委托给它的类路径类装载器,类路径类装载器又会将任务委托给扩展类装载器,扩展类装载器又会将任务委托给启动类装载器,由于在核心类库中找不到要加载的类,所以它告诉扩展类装载器没有加载到相应的类,这是扩展类装载器试图去加载,也没有找到,然后类路径类装载器在classpath也没找到.于是,loader0自己从网络上加载该类.从上面的描述中可以看出,启动类装载器优先去加载类.这样做的好处就是提高了安全性. 设想一下,如果有个恶意类,它的全限定名和Java核心类库中某个类相同,如java.lang.Integer,如果你的Java程序中使用了Integer这个类,如果没有这种委派机制的话,类路径类装载器会加载这个恶意类并执行,假如这个恶意Integer类中使用JNI来调用操作系统的API,那么后果可想而知. 不过通过委派机制的话,启动类装载器先去找java.lang.Integer,找到了之后,直接返回给类路径类装载器,这样类路径类装载器就没有机会去加载那个恶意的java.lang.Integer,这样就避免了一些安全问题.

上述写的可能不太直观,下面用代码说明:

假如我自己定义一个java.lang.Integer尝试去调用它:

package java.lang;

public class Integer {
 public static void destroy(){
  System.out.println("start destroy...");
 }
}

测试类如下:

public class Test{
 public static void main(String[] args) throws Exception{
  Integer.destroy();
 }
}

执行测试类并没有看到所期望的"start destroy...",而是一个异常:Exception in thread "main" java.lang.NoSuchMethodError: java.lang.Integer.destroy()V;这正好说明了启动类装载器已经装载了Integer并返回,程序中的Integer是Java核心类库中的类,当试图去调用destroy()方法时,由于Java核心类java.lang.Integer没有destroy()方法,所以报NoSuchMethodError.

 

好了,时间不早了,睡觉去了,还有一些以后有时间再说了!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值