详解Java 中的类加载机制

来源:互联网 发布:西门子plc编程视频教程 编辑:程序博客网 时间:2024/05/20 01:08

首先明确下类加载的定义,类加载实际上就是将java class文件加载到java 虚拟机中,根据JVM规范的定义,一般分为2种类型的类加载,一种是启动类加载器,另外一种是用户自定义类加载器。

 启动类加载器。

启动类加载器也有以下三种:Bootstrap ClassLoaderAppClassLoaderExtClassLoader,这三种classloaderjava虚拟机启动时会相继创建,首先启动时加载BootstrapClassLoader,然后BootstrapClassLoader加载ExtClassLoader,然后ExtClassLoader加载AppClassLoader,他们之间是由父子关系的,也就是通过父的classloader加载子classloader.下边分别介绍下这三种不同的classloader

BootstrapClassLoader比较特殊,实际上它不是 java.lang.ClassLoader的子类,C++编写的,java虚拟机启动时第一个执行,它是java虚拟机自带的装载器,用来装载核心类库,也就是java.lang.*,因为是c++编写的,所以在运行时,我们无法获取BootstrapClassLoader的任何信息。

ExtClassLoader的父亲是BootstrapClassLoader,但是在java运行时环境中,由于无法获取BootstrapClassLoader的任何信息,因此我们通过获取ExtClassLoaderparent的方式查看其父亲,会发现得到是nullExtClassLoader的职责是负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包

AppClassLoader的父亲是ExtClassLoader,它的主要职责是加载用户应用系统所需要的类,如用户系统自己编写的class,或用户系统导入的其他jar架包。

 

 

下边用代码测试下这三种类加载器:

/**

 * 测试类加载机制

 * @author Administrator

 *

 */

public class TestLoader

{

    /**

     * 分别加载三种不同类型的class类检验其用到的加载器。

     * @param args

     * @throws Exception

     */

    public static void main(String[] args) throws Exception

    {

       // 获取AppClassloader

       ClassLoader appClassloader = ClassLoader.getSystemClassLoader();

       // 获取ExtClassloader,也就是AppClassloader的父亲

       ClassLoader extClassloader = ClassLoader.getSystemClassLoader().getParent();

       // 获取BootStrapClassLoader

       ClassLoader bootClassloader = ClassLoader.getSystemClassLoader().getParent().getParent();

      

       // 下边分别打印输出

       System.out.println("appClassloader:" + appClassloader);

       System.out.println("extClassloader:" + extClassloader);

       System.out.println("bootClassloader:" + bootClassloader);

      

       // 根据三种加载器的职责分别通过加载具体的class来进一步测试下

      

       // 加载核心class,也就是java.lang.*中的类

       Class cls1 = Class.forName("java.lang.String");

       // 加载扩展class,也就是/jre/lib/ext下的包

       Class cls2 = Class.forName("sun.security.tools.KeyStoreUtil");

       // 加载用户系统中的class,如这个类本身。

       Class cls3 = Class.forName("TestLoader");

      

       //获取三个class的类加载对象,然后打印输出。

       ClassLoader cl1 = cls1.getClassLoader();

       ClassLoader cl2 = cls2.getClassLoader();

       ClassLoader cl3 = cls3.getClassLoader();

       System.out.println("cl1:" + cl1);

       System.out.println("cl2:" + cl2);

       System.out.println("cl3:" + cl3);

    }

}

运行测试类,输出如下结果

appClassloader:sun.misc.Launcher$AppClassLoader@19821f

extClassloader:sun.misc.Launcher$ExtClassLoader@addbf1

bootClassloader:null

cl1:null

cl2:null

cl3:sun.misc.Launcher$AppClassLoader@19821f

 

根据输出前面的三行的测试结果正确,跟我们预期的结果一致。可是cl2得输出结果不太明白,因为cl1加载的是核心类库,核心类库由BootstrapClassLoader加载,为null是正确的,但是cl2加载的是sun.security.tools.KeyStoreUtil类,而该类是/jre/lib/ext下的类,按理应该是由extClassloader加载,怎么输出的加载器也是null,这里不明白,怎么都搞不太清楚。如果有朋友研究过,可以一起探讨下,谢谢。

 

 用户自定义类加载器。

有时,我们需要根据自身系统的需要,定义自己的类加载器。要定义自己的类加载,一般需要继承java.lang.ClassLoader类,然后重写loadClassfindClass方法即可。

下边写个自定义的测试类,来验证下,代码注释中已经说明很详细,不再详细说明,代码如下:

import java.io.ByteArrayOutputStream;

import java.io.FileInputStream;

import java.io.IOException;

import java.lang.reflect.Method;

import java.nio.ByteBuffer;

import java.nio.channels.Channels;

import java.nio.channels.FileChannel;

import java.nio.channels.WritableByteChannel;

 

/**

 * 自定义类加载器测试类

 * @author Administrator

 *

 */

public class MyClassLoader extends ClassLoader

{

    // 加载的类文件所在的目录

    private static final String DIR = "D://workspace//MapReduce//bin//";

   

    /**

     * 启动测试类的main方法

     * @param args

     * @throws Exception

     */

    public static void main(String args[]) throws Exception

    {

       // 通过加载类,创建类的实例,调用类TestLoader的方法来测试是否成功装载指定的类

       MyClassLoader clsLoader = new MyClassLoader();

      

       // 装载class

       Class cls = clsLoader.findClass("TestLoader");

      

       // 创建其实例

       Object instance = cls.newInstance();

      

       // 获取test方法的Method对象,需要输入其参数类型,TestLoader类的test方法的传入参数是String类型,

       // 所以这里是 java.lang.String.class

       Method method = cls.getMethod("test", java.lang.String.class);

      

       // 调用方法,传入参数为一字符串how are you

       method.invoke(instance,"how are you");

    }

   

    /**

     * 重写父类的findClass方法

     */

    public Class findClass(String name) throws ClassNotFoundException

    {

       // 装载类

       byte[] data = loadData(name);

      

       // 调用父类的defineClass装载 类,并返回装载好的类的class实例

       return defineClass(name, data, 0, data.length);

    }

   

    /**

     * 装载类文件中的数据到内存,返回数据到一个字节数组中

     * @param className 类名,不包括目录名和.class后缀。

     * @return 类数据

     * @throws Exception

     * @throws ClassNotFoundException

     */

    private byte[] loadData(String className)

    {

       String file = DIR + className + ".class";

       FileChannel fileChannel = null;

       try

       {

           // 这里主要是读文件里的数据到输出流,然后转换到一个字节数组中返回。

           fileChannel = new FileInputStream(file).getChannel();

           ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();

           WritableByteChannel writeChannel = Channels.newChannel(byteArrayStream);

           ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);

           while (true)

           {

              int index = fileChannel.read(byteBuffer);

              if (index == 0 || index == -1)

              {

                  break;

              }

              byteBuffer.flip();

              writeChannel.write(byteBuffer);

              byteBuffer.clear();

           }

           return byteArrayStream.toByteArray();

       }

       catch (Exception e)

       {

           e.printStackTrace();

           return null;

       }

       finally

       {

           try {

              fileChannel.close();

           catch (IOException e) {

              // TODO Auto-generated catch block

              e.printStackTrace();

           }

       }

    }

}

 

运行结果:

how are you

调用了加载类的test方法,打印出了传入的参数,完全正确。顺便附上test方法的定义:

    public void test(String str)

    {

       System.out.println(str);

    }
总结:理解java类加载机制,对掌握java的基础技术有重要的帮助,特别是对理解java的反射等特性,接下来有时间,再研究下java的反射特性,因为java的反射机制对编写工具软件是很有帮助的。