zoukankan      html  css  js  c++  java
  • java 类加载器

    类加载器说明

    类加载器负责将.class文件加载到内存中,并为类生成一个java.lang.Class实例。

    一旦一个类被加载入JVM中,同一个类就不会被再次加入了。在JVM中用来判断类的唯一性标识是:类名、类所在的包名和类加载器。

    当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:

    • BootStrap ClassLoader:根类加载器;
    • Extension ClassLoader:扩展类加载器;
    • System ClassLoader:系统类加载器。

    根类加载器,负责加载java的核心类(即JAVA_HOME/jre/lib下的jar包)。当使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性也可以指定加载附加的类。根类加载器比较特殊,他并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。

    扩展类加载器,负责加载JRE的扩展目录(即JAVA_HOME/jre/lib/ext目录)下的jar包。

    系统类加载器,负责在JVM启动时,加载来自命令java中的-classpath选项或java.class.path系统属性,或CLASSPATH环境变量所指定的jar包和类路径,或者是当前类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()方法获取系统类加载器。

    除了Java提供的这三种类加载器,开发者也可以实现自己的类加载器,自定义的类加载器通过继承ClassLoader来实现。

    JVM中这四种类加载器的层次结构如图:

    image

    如图所示:根类加载器是扩展类加载器的父类加载器;扩展类加载器是系统类加载器的父类加载器;如果没有特别指定,用户自定义的类加载器都以系统类加载器作为父加载器。

    需要提下,类加载器之间的父子关系并不是类继承上的父子关系,而是类加载器实例之间的关系。

    类加载机制

    JVM的类加载机制主要有如下三种机制:

    • 全盘负责:全盘负责机制是说当一个类加载器负责加载某个类的时候,该类所依赖的和引用的其他类也将由该类加载器负责载入,除非显式使用另一个类加载器来载入;
    • 父类委托:父类委托机制是说当一个类加载器负责加载某个类的时候,先让父类加载器尝试载入该类,若无法载入,才尝试从自己的类路径中载入;
    • 缓存机制:缓存机制会保证所有被加载过的类都会被缓存。当一个类加载器负责加载某个类的时候,会先从缓存中搜寻该类,只有当缓存中不存在该类对象时,才会载入该类并存放于缓存中。这也是为什么我们修改了某个类以后需要重启动JVM,所做的调整才会生效的原因。

    类加载器加载类大致要经过8个步骤:

    1. 检测目标类是否有载入过(即在缓存中是否有这个类),如果有,则直接进入第八步;
    2. 如果父加载器不存在(父加载器不存在有两种情形,一是当前加载器的parent是根加载器,二是当前加载器就是根加载器),则跳到第四步执行;如果父加载器存在,则执行第三步;
    3. 请求父加载器载入目标类,如果成功跳到第八步,不成功则跳到第五步;
    4. 请求使用根加载器加载目标类,如果成功跳到第八步,不成功则跳到第七步;
    5. 寻找.class文件(从相关的类加载器的加载路径中查找),如果找到则执行第六部,找不到则执行第七步;
    6. 从文件中载入类,成功载入则跳到第八步;
    7. 抛出ClassNotFoundException;
    8. 返回类。

    自定义类加载器

    JVM中除了根类加载器之外的类加载器都是ClassLoader的子类的实例。开发者可以通过继承ClassLoader,并重写ClassLoader的方法来实现自定义类加载器。

    ClassLoader类有三个关键方法:

    • loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类;
    • findClass(String name):根据指定的二进制名称来查找类;
    • defineClass(String name, byte[] b, int off, int len):将指定类的字节码文件(即.class文件)读入字节数组byte[] b内,并把它转为Class对象,该字节码文件可以来源于网络或磁盘。

    要实现自定义的ClassLoader,可以通过重写loadClass或findClass来实现。不过一般推荐重写findClass,而不是loadClass,看一下loadClass的执行步骤:

    • 用findLoadClass来检查是否已经加载类,如果已经加载则直接返回;
    • 在父类加载器上调用loadClass方法。如果父类加载器为null,则使用根类加载器来加载;
    • 调用findClass方法来查找类。

    从上面看来,采用重写findClass的方法可以避免覆盖默认类加载器的父类委托和缓存机制两种策略。

    以下是一个自定类加载器的实例:

    package com.zhyea.test;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Method;
    
    /**
     * 自定义类加载器实例
     * @author robin
     *
     */
    public class MyClassLoader extends ClassLoader {
    
        /**
         * 读取类文件
         * 
         * @param filePath
         *            类文件路径
         * @return
         * @throws IOException
         */
        private byte[] getBytes(String filePath) throws IOException {
            InputStream in = null;
            try {
                File file = new File(filePath);
                long length = file.length();
                in = new FileInputStream(file);
                byte[] buffer = new byte[(int) length];
                int hasRead = in.read(buffer);
                if (hasRead != length) {
                    throw new IOException("无法读取全部文件:" + hasRead + "!=" + length);
                }
                return buffer;
            } finally {
                if (null != in)
                    in.close();
            }
        }
    
        /**
         * 编译Java文件
         * 
         * @param javaFile
         *            Java文件
         * @return
         * @throws IOException
         * @throws InterruptedException
         */
        private boolean compile(String javaFile) throws IOException,
                InterruptedException {
            // 调用系统javac命令
            Process p = Runtime.getRuntime().exec("javac " + javaFile);
            // 强制其他线程等待这个线程完成
            p.waitFor();
            // 获取javac线程的退出值
            int rtn = p.exitValue();
            // 返回编译是否成功
            return rtn == 0;
        }
        
        
        /**
         * 重写ClassLoader的findClass类
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException{
            
            Class<?> clazz = null;
            
            String javaPath = name.replace(".", "/");
            String javaFileName = javaPath + ".java";
            String classFileName = javaPath = ".class";
            
            File javaFile = new File(javaFileName);
            File classFile = new File(classFileName);
            
            if(javaFile.exists() && (!classFile.exists() || javaFile.lastModified()>classFile.lastModified())){
                try{
                    if(!compile(javaFileName) || !classFile.exists()){
                        throw new ClassNotFoundException("Class not found : " + javaFileName);
                    }
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            
            if(classFile.exists()){
                try{
                    byte[] raw = getBytes(classFileName);
                    clazz = defineClass(name, raw, 0, raw.length);
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            
            if(null==clazz){
                throw new ClassNotFoundException(name);
            }
            
            return clazz;
        }
        
        public static void main(String[] args) throws Exception{
            String javaName = "com.zhyea.test.MyTest";
            MyClassLoader mcl = new MyClassLoader();
            Class<?> clazz = mcl.loadClass(javaName);
            Method main = clazz.getMethod("main", (new String[0]).getClass());
            Object[] arrs = {args}; 
            main.invoke(null, arrs);
        }
        
    }

    再附上演示用的测试类,其实很简单了:

    package com.zhyea.test;
    
    /**
     * 自定义类加载器演示用的测试类
     * @author robin
     *
     */
    public class MyTest {
    
        public static void main(String[] args) {
            System.out.println("This is a Test!");
        }
    
    }

    使用自定义的类加载器,可以实现如下功能:

    • 执行代码前自动检验数字签名;
    • 根据用户提供的密码解密代码,从而可以实现代码混淆器避免反编译class文件;
    • 根据用户需求来动态地加载类;
    • 根据应用需求把其他数据以字节码的形式加载到应用中。

    URLClassLoader类

    URLClassLoader是扩展类加载器类和系统类加载器类的父类。URLClassLoader功能比较强大,可以从本地获取java文件来加载类,也可获取远程文件来加载类。

    演示下:

    package com.zhyea.test;
    
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class UCLTest {
        public static void main(String[] args) throws Exception {
            URL[] urls = {new URL("file:com.zhyea.test.MyTest.java")};
            URLClassLoader ucl = new URLClassLoader(urls);
            MyTest mt = (MyTest) ucl.loadClass("com.zhyea.test.MyTest").newInstance();
            mt.test();
            ucl.close();
        }
    }
    
    
    package com.zhyea.test;
    
    /**
     * URLClassLoader演示用的测试类
     * @author robin
     *
     */
    public class MyTest {
        public void test(){
            System.out.println("This is a Test 2!");
        }
    }
  • 相关阅读:
    内置函数拾遗
    jQuery与其他JS库冲突解决
    ckeditor+ckfinder添加水印。
    PostgreSQL与mysql的比较
    php函数 之 iconv 不是php的默认函数,也是默认安装的模块。需要安装才能用的。
    php mb_substr()函数的详细解释!
    成为一名PHP专家其实并不难
    php中级程序员的进化标准
    鼠标经过图片切换效果。
    计算两个日期之间的工作日
  • 原文地址:https://www.cnblogs.com/amunote/p/4176655.html
Copyright © 2011-2022 走看看