Java虚拟机:类加载器与双亲委派模型

来源:互联网 发布:hadoop git 源码下载 编辑:程序博客网 时间:2024/05/17 02:52

学了挺久的java,也接触过java虚拟机,但是就是没有深入到源码中细细品读,也导致每次看到关于虚拟机的问题的时候都似曾相识却无从下手.这个系列就一点一点的品读java虚拟机.

类加载器

从java虚拟机规范描述来看,JVM支持两种类加载器:引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader).自定义类加载器派生于抽象类ClassLoader.

最常见的类加载器始终只有3个:
* Bootstrap ClassLoader 启动类加载器(引导加载器)
* ExtClassLoader
* AppClassLoader

对应来看,每种类加载器对应的目录为:

类加载器 目录 备注 Bootstrap ClassLoader JAVA_HOME/lib,或者由选项-Xbootclasspath指定 用C++编写,嵌在JVM内部 ExtClassLoader JAVA_HOME/ext Java编写 派生于ClassLoader APPClassLoader ClassPath

测试代码:

public class Test {    public static void main(String[] args) {        //BootstrapClassLoader        ClassLoader classLoader = System.class.getClassLoader();        System.out.println(null != classLoader ? classLoader.getClass().getName(): "name:null");        //ExtClassLoader        System.out.println(CollationData_ar.class.getClassLoader().getClass().getName());        //AppClassLoader        System.out.println(Test.class.getClassLoader().getClass().getName());    }}

输出结果:

name:nullsun.misc.Launcher$ExtClassLoadersun.misc.Launcher$AppClassLoader

以上测试的含义是:
1. 因为java.lang.System类包含在JAVA_HOME/lib目录中,测试由BootstrapClassLoader加载,由于启动类加载器本身由C++编写并嵌套在JVM内部,所以输出为null.
2. sun.text.resoutces.CollationData_ar类包含在JAVA_HOME/lib/ext扩展目录中,由ExtClassLoader加载.
3. 而我们的一般测试类在ClassPath目录中,因此由AppClassLoader加载.

双亲委派模型

java程序中重要的一点是要保证一个类的全局唯一性.当程序中出现多个全限定名相同的类时,类加载器始终只加载其中的某一个类而不会都加载.

查看ClassLoader类中loadClass的源码:

    protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {        //1.检查目标类之前是否已经被加载过了        synchronized (getClassLoadingLock(name)) {            // First, check if the class has already been loaded            Class<?> c = findLoadedClass(name);            if (c == null) {                long t0 = System.nanoTime();                try {                    //如果存在超类加载器,就直接委派给顶层的启动类加载器.                    if (parent != null) {                        c = parent.loadClass(name, false);                    } else {                        c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {                    // ClassNotFoundException thrown if class not found                    // from the non-null parent class loader                }                if (c == null) {                    //如果超类加载器无法加载,就自行加载                    // If still not found, then invoke findClass in order                    // to find the class.                    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;        }    }

双亲委派模型的优点是使java类加载器具备优先级层级关系,越是基础的类越是被上层的类加载器加载,保证稳定运行.

**注意:**java虚拟机规范没有明确要求类加载器机制一定是双亲委派模型,只是建议.比如在Tomcat中,默认类加载器首先自己加载,失败时才给超类加载.

自定义类加载器

自定义类加载器的需求并不多.但是,当有一些特殊需求,比如,当一个字节码文件在编译的时候进行了加密处理,那么类加载器在加载的时候首先就要解密,否则会认为它不是标准的字节码文件.

自定义类加载器只需继承抽象类ClassLoader并重写ClassLoader.

注意:怎样查看当前的ClassPath目录?

System.getProperty("java.class.path");

准备工作

在F盘目录下创建Test1.java文件:

public Test1{        public static void main(String[] args){                System.out.println("test1");    }}

并在此打开命令行,用javac Test1.java 命令将其编译为Test1.class 文件.

程序代码

public class MyClassLoader extends ClassLoader {    private String byteCode_Path;    public MyClassLoader(String byteCode_Path) {        this.byteCode_Path = byteCode_Path;    }    @Override    protected Class<?> findClass(String className) throws ClassNotFoundException {        byte value[] = null;        BufferedInputStream in = null;        try {            in = new BufferedInputStream(new FileInputStream(byteCode_Path + className + ".class"));            value = new byte[in.available()];//将字节码全部读取到数组里            in.read(value);        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } finally {            if (null != in) {                try {                    in.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }        return defineClass(value, 0, value.length);//将byte数组转化为一个class对象实例    }    public static void main(String[] args) throws ClassNotFoundException {        MyClassLoader classLoader = new MyClassLoader("F:/");        System.out.println("加载目标类的类加载器->"+classLoader.loadClass("Test1").getClassLoader().getClass().getName());        System.out.println("当前类加载器的超类加载器:->"+classLoader.getParent().getClass().getName());    }}

输出为:

加载目标类的类加载器->MyClassLoader当前类加载器的超类加载器:->sun.misc.Launcher$AppClassLoader

参考书目:《Java虚拟机精讲》 高翔龙

0 0
原创粉丝点击