您的位置:首页 > 编程语言 > Java开发

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

2016-06-13 15:15 639 查看
    “通过一个类的全限定名来获取描述此类的二进制流”这个动作放到虚拟机外部去实现,以便让应用程序决定如何去获取所需要的类。这个动作模块就是类加载器。而程序在启动的时候,并不会一次性加载程序所要用的所有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();
}
}
}
aa7a

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虚拟机》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息