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

    作为一枚java猿,了解类加载器是有必要的,无论是针对面试还是自我学习。

    本文从JDK提供的ClassLoader、委托模型以及如何编写自定义的ClassLoader三方面对ClassLoader做一个简要的总结。

    JDK中提供的ClassLoader

    1. Bootstrap ClassLoader

      Bootstrap加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib以及%JAVA_HOME%/jre/classes中的类,是最顶级的ClassLoader。

    2. Ext ClassLoader

      Ext ClassLoader是用java写的,且它的父加载器是Bootstrap,具体来说就是sun.misc.Launcher$ExtClassLoader,Ext ClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中的类库。

    3. App ClassLoader 

      系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。它的父加载器为Ext ClassLoader。

    具体关系如下图:

    委托模型

    进入官方的Java doc里看到ClassLoader类的一段说明:

    The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.

    ClassLoader类使用一种委托模型来查找类和资源。每个ClassLoader实例都会关联1个父ClassLoader。当需要查询类和资源的时候,一个ClassLoader实例在查询类或资源之前会先委托给它的父ClassLoader去查询。Bootstrap ClassLoader是最顶层的加载器,并且可以作为其它ClassLoader实例的父ClassLoader。

    由此看见,这个“委托模型”的安全性是很高的,Bootstrap是最顶层的加载器,这样比如加载 java.lang.String 的时候,永远都会被Bootstrap加载(Bootstrap ClassLoader会加载%JAVA_HOME%/jre/lib中rt.jar里的String类)。 这样用户自定义的java.lang.String永远都不会被加载,这样就避免了多个java.lang.String造成的混乱现象。

    下面通过jdk里的ClassLoader源码来验证一下查找过程:

      protected synchronized Class<?> loadClass(String name, boolean resolve)
    	throws ClassNotFoundException
        {
    	// First, check if the class has already been loaded
    	Class c = findLoadedClass(name); //先查找这个类是否已经加载过,每个加载器都有自己的缓存
    	if (c == null) {
    	    try {
    		if (parent != null) { //父加载器存在的话先使用父加载器加载
    		    c = parent.loadClass(name, false);
    		} else {
    		    c = findBootstrapClassOrNull(name); //没有父加载器的话使用bootstrap加载
    		}
    	    } 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.
    	        c = findClass(name); //如果父加载器没有找到,那么自身查找
    	    }
    	}
    	if (resolve) {
    	    resolveClass(c);
    	}
    	return c;
        }

    通过代码看,这里的查找过程符合委托模型。

    如何编写自定义的ClassLoader

    编写自定义的ClassLoader注意2点即可:

    1. 想遵循委托模型的话重写findClass方法即可。

    2. 不遵循委托模型的话重写loadClass。

    其他:defineClass方法把字节数组b中的内容转换成Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为final的。该方法也是jvm预留给我们处理ClassLoader与类文件关系的入口。

        protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError

    下面就来写一个基于文件系统的ClassLoader:

    先来看下定义的一个类:

    package org.format.classloader;
    
    public class Obj {
      
        @Override
        public String toString() {
            return "org.format.classloader.Obj";
        }
        
    }

    自定义的ClassLoader:

    public class FileSystemClassLoader extends ClassLoader{
    
        private String directory;
        
        public FileSystemClassLoader(String directory) {
            this.directory = directory;    
        }
      
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] clsBytes = getClassBytes(name);
            ifclsBytes == null)
                throw new ClassNotFoundException();
            return defineClass(name, clsBytes, 0, clsBytes.length);
        }
        
        private byte[] getClassBytes(String name) {
            String location = getClassLoc(name);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(location);
                byte[] buffer = new byte[4096];
                int readLen = 0;
                while( (readLen = fis.read(buffer)) != -1 ) {
                    baos.write(buffer, 0, readLen);
                }
                baos.flush();
                return baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return null;
        }
    
        private String getClassLoc(String name) {
            return this.directory + File.separatorChar + name.replace('.', File.separatorChar) + ".class";
        }
        
    }

    在测试之前,使用javac命令将Obj.java编译成Obj.class,然后放与/tmp/java/classloader目录下。

    下面就用自定义的ClassLoader来进行几个测试:

    FileSystemClassLoader fscl = new FileSystemClassLoader("/tmp/java/classloader");
    try {
        System.out.println(fscl.loadClass("org.format.classloader.Obj").newInstance());
    } catch (Exception e) {
        e.printStackTrace();
    }    
    

    很明显,自定义的FileSystemClassLoader加载到了自定义的Obj类。

    FileSystemClassLoader fscl1 = new FileSystemClassLoader("/tmp/java/classloader");
    FileSystemClassLoader fscl2 = new FileSystemClassLoader("/tmp/java/classloader");
    try {
        Class cls1 = fscl1.loadClass("org.format.classloader.Obj");
        Class cls2 = fscl2.loadClass("org.format.classloader.Obj");
        System.out.println("class1: " + cls1);
        System.out.println("class2: " + cls2);
        System.out.println("class1 == class2? " + (cls1 == cls2));
    } catch (Exception e) {
        e.printStackTrace();
    }    
    

     

    我们可以,使用不同的类加载器加载同一class文件得出的Class对象是不一样的。

    这是因为Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。

    参考资料

    http://imtiger.net/blog/2009/11/09/java-classloader/

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

    http://jiangbo.me/blog/2012/02/14/jetty-classloader/

  • 相关阅读:
    CentOS安装sctp协议
    视频编码未来简史
    Linux内核:分析coredump文件
    skb的两个函数pskb_copy和skb_copy
    《Linux内核设计与实现》读书笔记(十二)- 内存管理
    Linux内核学习笔记之seq_file接口创建可读写proc文件
    内核如何签名
    《女士品茶》与统计检验
    K近邻算法
    PCA原理分析
  • 原文地址:https://www.cnblogs.com/fangjian0423/p/java-classloader.html
Copyright © 2011-2022 走看看