Java虚拟机(六):类加载器ClassLoader

    “通过一个类的全限定名来获取描述此类的二进制流”这个动作放到虚拟机外部去实现,以便让应用程序决定如何去获取所需要的类。这个动作模块就是类加载 器。 而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。
一、双亲委派模型
   1、Java默认类加载器
        java中 只存在两种不同的类加载器:启动类加载器(Bootstrap ClassLoader),使用C++实现,是虚拟机自身的一部分。另一种是所有其他的类加载器,使用JAVA实现,独立于JVM,并且全部继承自抽象类java.lang.ClassLoader。
    (1)、Bootstrap ClassLoader
           启动类加载器(Bootstrap ClassLoader),是Java类加载器层次中最顶层的类加载器,负责将存放在<JAVA+HOME>\lib目录中的JDK核心类库,或者被-Xbootclasspath参数所制定的路径中的,并且是JVM识别的(仅按照文件名识别,如rt.jar,如果名字不符合,即使放在lib目录中也不会被加载),加载到虚拟机内存中,启动类加载器无法被JAVA程序直接引用。
      查看 sun.boot.class.path 启动类加载路径:
      System.out.println(System.getProperty("sun.boot.class.path"));
     输出 :
        D:\Program Files\java1\jre7\lib\resources.jar;D:\Program Files\java1\jre7\lib\rt.jar;D:\Program Files\java1\jre7\lib\sunrsasign.jar;D:\Program Files\java1\jre7\lib\jsse.jar;D:\Program Files\java1\jre7\lib\jce.jar;D:\Program Files\java1\jre7\lib\charsets.jar;D:\Program Files\java1\jre7\lib\jfr.jar;D:\Program Files\java1\jre7\classes
 
  (2)、Extension ClassLoader
         扩展类加载器,由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
     通过:   ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent();获得扩展类加载器。
      查看  java.ext.dirs 扩展类加载路径:
    System.out.println(System.getProperty("java.ext.dirs")); 
     输出: 
        D:\Program Files\java1\jre7\lib\ext;C:\windows\Sun\Java\lib\ext 
  (3)、 Application ClassLoader
        应用程序类加载器(Application ClassLoader),由sun.misc.Launcher$AppClassLoader来实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般称它为系统类加载器。负责加载用户类路径(ClassPath)上或者 java.class.path系统属性 所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。   
 查看  java.class.path 系统应用类加载路径:
    System.out.println(System.getProperty("java.class.path")); 
     输出: 
      E:\XL\workspace\Test\bin;D:\Program Files (x86)\eclipse\plugins\org.junit_4.10.0.v4_10_0_v20120426-0900\junit.jar;D:\Program Files (x86)\eclipse\plugins\org.hamcrest.core_1.1.0.v20090501071000.jar
   (4)、ClassLoader体系结构
        

2、双亲委派
  (1)、双亲委派模型
                   
                                                   
    
         这张图表示类加载器的双亲委派模型(Parents Delegation model)。双亲委派模型要求除了顶层的启动加载类外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承的关系来实现,而是使用组合关系来复用父类加载器的代码。
           如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都是应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
        通过双亲委派, Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。 就是保证某个范围的类一定是被某个类加载器所加载的,这就保证在程序中同 一个类不会被不同的类加载器加载。这样做的一个主要的考量,就是从安全层 面上,杜绝通过使用和JRE相同的类名冒充现有JRE的类达到替换的攻击方式。
 (2)、双亲委派的实现
 源码:
   protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);  //     1.检测此Class是否载入过(即在cache中是否有此Class),如果有到6,如果没有到2
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {   // 2. 如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
                        c = parent.loadClass(name, false); / 3.请求parent classloader载入,如果成功到6,不成功到5
                    } else {
                        c = findBootstrapClassOrNull(name);  //   4. 请求jvm从bootstrap classloader中载入,如果成功到8
                    }
                } 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);    // 5.  寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到6
                    // 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;       //   6.返回Class.
        }
    }
(3)、判断类相等
        JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。 就算两个class是同一份class字节码,如果被两个不同的ClassLoader实例所加载,JVM也会认为它们是两个不同class。

二、自定义类加载器
  
     自定义类加载器,从网络上获取class文件,以及获取网络资源。步骤如下:

        1、继承java.lang.ClassLoader

        2、重写父类的findClass方法

        注:JDK在loadClass方法中帮我们实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可。如没有特殊的要求,一般不建议重写loadClass搜索类的算法。

        3、重写父类的findResource方法(可选)

    代码:
package com.xl.jvm;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
public class NetworkClassloader    extends ClassLoader {
 private String rootUrl;  
    public NetworkClassloader(String rootUrl) {  
        this.rootUrl = rootUrl;  
    }  
   
    public NetworkClassloader(String rootUrl,ClassLoader parent){
     super(parent);
     this.rootUrl = rootUrl;
    }
    @Override      //
    protected Class<?> findClass(String name) throws ClassNotFoundException {  
        Class clazz = null;
        this.findLoadedClass(name); // 父类已加载    
        if (clazz == null) {  //检查该类是否已被加载过  
            byte[] classData = getClassData(name);  //根据类的二进制名称,获得该class文件的字节码数组  
            if (classData == null) {  
                throw new ClassNotFoundException();  
            }  
            clazz = defineClass(name, classData, 0, classData.length);  //将class的字节码数组转换成Class类的实例  
        }  
        return clazz;  
    }  
        
    @Override
 protected URL findResource(String path) {
  // TODO Auto-generated method stub
 
   URL url = null;  
         try {  
             url = new URL(rootUrl +"/" + path);  
         } catch (MalformedURLException e) {  
             // TODO Auto-generated catch block  
             e.printStackTrace();  
         }  
         return url;  
 }
 private byte[] getClassData(String name) {  
        InputStream is = null;  
        try {  
            String path = classNameToPath(name);  
            URL url = new URL(path);  
            byte[] buff = new byte[1024*4];  
            int len = -1;  
            is = url.openStream();  
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            while((len = is.read(buff)) != -1) {  
                baos.write(buff,0,len);  
            }  
            return baos.toByteArray();  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            if (is != null) {  
               try {  
                  is.close();  
               } catch(IOException e) {  
                  e.printStackTrace();  
               }  
            }  
        }  
        return null;  
    }  
    private String classNameToPath(String name) {  
        return rootUrl + "/" + name.replace(".", "/") + ".class";  
    }  

    public String getRootUrl() {
  return rootUrl;
 }
 public void setRootUrl(String rootUrl) {
  this.rootUrl = rootUrl;
 }
  
}


使用:
          
 String rootUrl = "http://localhost:8080/myweb/classes";  
           NetworkClassloader networkClassLoader = new NetworkClassLoader(rootUrl);  
           String classname = "org.com.xl.NetClassLoaderTest";  
           Class clazz = networkClassLoader.loadClass(classname);  
           System.out.println(clazz.getClassLoader());                 

           Url   resource = networkClassLoader.getResource("classes.config");



参考资料:《深入理解Java虚拟机》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值