zoukankan      html  css  js  c++  java
  • 重温Java的类加载机制

    http://blog.csdn.net/hitxueliang/article/details/19992851

    首先简要的说一下类加载器

     

    我们知道,虚拟机的指令存储在以.class为扩展名的类文件中。那么虚拟机如何调用这些.class文件呢。先看一下虚拟机执行的步骤:

    1.     虚拟机有一个有一个用于类加载的机制,用于从数据源中读取类文件,这个源可以是磁盘文件或者web上的文件,这里假设要加载的雷文佳是TestLoader。

    2.     如果TestLoader这个类拥有类型为其他类的实例或者是某个类的子类,也就是说它和其他类产生关联或者依赖,那么相关的类也会被加载

    3.     虚拟机执行TestLoader中的main方法

    4.     如果main方法中有对其他类的依赖,那么加载相应的类。

    可见联系虚拟机和我们的程序的“中间人”就是类加载器,用什么就加载什么。类加载器是有其自身的体系的。每一个Java程序至少拥有三个类加载器:

    引导类加载器:

     

    引导类加载器负责加载系统类,通常从rt.jar中加载,引导类加载器是隶属于虚拟机体系的一部分,而且实现通常是C语言实现的。引导类加载器没有对应的ClassLoader对象,例如:

    System.out.println("----------测试引导类加载器-------------");
    System.out.println("Class的加载器:"+Class.class.getClassLoader().getSystemClassLoader());
    System.out.println("TestLoader的加载器:"+TestLoader.class.getClassLoader());
    System.out.println("Stirng的类加载器:"+String.class.getClassLoader());
    System.out.println("ArrayList的类加载器:"+ArrayList.class.getClassLoader());
    System.out.println("----------测试引导类加载器------------- ");

     

    那么对应的结果如下:

    ----------测试引导类加载器-------------
    Class的加载器:sun.misc.Launcher$AppClassLoader@1e87719
    TestLoader的加载器:sun.misc.Launcher$AppClassLoader@1e87719
    Stirng的类加载器:null
    ArrayList的类加载器:null
    ----------测试引导类加载器-------------

     

    显然我们知道ArrayList,String以及基本类型的封装类是属于rt.jar的,这与结果符合。

     

    扩展类加载器:

     

    扩展类加载器用于从jre/lib/ext目录加载扩展类。可以将你的jar放到这个目录下,这样就没有任何路径问题了,但有个问题要注意:如果将你的jar放到了jre/lib/ext下,如果你的类中调用了系统类或者其他扩展类,那么你就摊上事了,因为扩展类加载器并不使用类路径,这就导致,你是找不到的,所以在使用扩展路径来解决类文件冲突前,要考虑规避这个问题。

    系统类加载器:

     

    系统类加载器用于加载应用类,可以找到classpath里面指定的jar中的类。

    扩展类加载器和系统类加载器都是采用java实现的,都是URLClassLoader的实例,关系图呢,可以看下面的图所示,在这里可以看到AppClassLoader就是我们上面打印出来的系统类加载器,而他正是URLClassLoader的子类

     

     

    类加载器的层次结构

     

    类加载器有一个等级结构,可以认为默认的是上层优先的原则。除了引导类加载器外其他的类加载器都有一个父类加载器。根据规定,每个类加载器都会先为其父类加载器提供工作机会,如果父类不能完成此项任务,他才会自己去做。例如你要系统类加载器加载一个String类型时,首先系统类加载器,会让他的父亲即扩展类加载器去尝试加载,那么扩展类加载器也会让他的父亲——引导类加载器去尝试加载,引导类加载器是顶层的了,他一看这个任务是我的菜,那么他就会完成此项任务。

    类的加载器层次结构如下如所示

     

    虽然大多数的时候你不必担心类加载的层次结构。通常类是由其他的类的需要而被加载的,而这个过程对于大多数人是透明的。但如果你想干涉这个过程,也是可以的。下面介绍如何自己去指定加载器。

    这里就在一个main函数中说明。每一个线程都有一个对类的加载器的引用,称为上下文类加载器。

    Thread t=Thread.currentThread();
    System.out.println("当前主线程的类加载器是:"+t.getContextClassLoader());
    t.setContextClassLoader(new MyLoader("Hello world"));
    ClassLoader loader=t.getContextClassLoader();
    System.out.println("设置加载器,采用自己的类加载器:"+t.getContextClassLoader());

     

    这里先获取当前线程的类加载器,之后再指定为自己的类加载器。

    这里说明一下,主线程的加载器是系统类加载器。当新建一个线程时,他的上下文加载器会被设定为创建者的上下文类加载器。因此如果你不去干预,那么依旧是系统类加载器。

    如何实现自己的类加载器?

    实现自己的类加载器实际很简单,只需要继承ClassLoader类即可,然后覆盖findClass方法即可。当调用我们的类加载器时,首先他会将这个工作交给父亲去做,父类中的LoadClass方法将去做,发现这个类没有加载过或者不能加载时,才去调用findClass方法。

    FindClass方法要实现的任务是:

    1.     加载字节码

    2.     调用父类的definClass方法,向虚拟机提供字节码。

    我的加载器

    import java.io.ByteArrayOutputStream;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;




    public class MyLoader extends ClassLoader{


    private String key;
    public MyLoader(String key){
    this.key=key;
    System.out.println(key+" ,自己的类加载器被实例化了");
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    byte[] classbytes=null;
    classbytes=loadClassBytes(name);
    Class<?> cl=defineClass(null, classbytes, 0, classbytes.length);
    if(cl==null){
    System.out.println("失败");
    return null;
    }
    return cl;
    }
    //读取文件,返回字节数组
    private byte[] loadClassBytes(String name){
    FileInputStream in=null;
    try {
    in=new FileInputStream(name);
    ByteArrayOutputStream buffer=new ByteArrayOutputStream();
    int ch;
    while((ch=in.read())!=-1){
    byte b=(byte) ch;
    buffer.write(b);
    }
    in.close();
    return buffer.toByteArray();
    } catch (FileNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    return null;
    }
    }

    被加载的类

    public class ToBeLoad {


    public static void main(String[] args) {
    System.out.println("被加载的类main方法执行了");
    System.out.println(args[0]);


    }


    }

    测试类:

    import java.io.File;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;




    public class TestLoader {


    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    System.out.println("----------测试引导类加载器-------------");
    System.out.println("Class的加载器:"+Class.class.getClassLoader().getSystemClassLoader());
    System.out.println("TestLoader的加载器:"+TestLoader.class.getClassLoader());
    System.out.println("Stirng的类加载器:"+String.class.getClassLoader());
    System.out.println("ArrayList的类加载器:"+ArrayList.class.getClassLoader());
    System.out.println("----------测试引导类加载器------------- ");


    File f=new File("E://WorkSpace//JavaSE//MyTest//bin//ToBeLoad.class");

    Thread t=Thread.currentThread();
    System.out.println("当前主线程的类加载器是:"+t.getContextClassLoader());
    t.setContextClassLoader(new MyLoader("Hello world"));
    ClassLoader loader=t.getContextClassLoader();
    System.out.println("设置加载器,采用自己的类加载器:"+t.getContextClassLoader());

    Class<?> c=loader.loadClass(f.getAbsolutePath());
    Method[] m=c.getMethods();
    m[0].invoke(null, (Object)new String[]{"1"});



    }


    }

  • 相关阅读:
    java代理的深入浅出(一)-Proxy
    事件分发模型的设计与实现
    LibProject使用theme无效。
    HeaderGridView
    android开源代码
    IOS学习4
    IOS学习3
    IOS学习2
    Mac上添加adb_usb.ini
    OC学习-1
  • 原文地址:https://www.cnblogs.com/feng9exe/p/7116956.html
Copyright © 2011-2022 走看看