JVM类加载机制

1 篇文章 0 订阅

1.类加载器的双亲委托模型

在JVM中,类加载器使用双亲委托机制进行类加载,每一个类加载器实例都有一个与之对应的父类加载器,当类加载器需要加载类时,类加载器在加载类之前会把加载类的操作委托给父类加载器执行。类加载器的层次结构如下图所示。

在这里插入图片描述

虚拟机内建的类加载器称为启动类加载器(bootstrap class loader),它没有父类加载器,但可以作为其他类加载器的父类加载器。它主要加载存放在<JAVA_HOME>/lib目录中的、或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库(如rt.jar)。
扩展类加载器加载<JAVA_HOME>/lib/ext目录中的或者被java.ext.dirs系统变量所指定的路径中的所有类库。
应用程序类加载器也称为系统类加载器,负责加载用户类路径上所指定的类库。
获得类加载器的途径有以下方式:
  • 获得当前类的ClassLoader:clazz.getClassLoader()
  • 获得当前线程上下文的ClassLoader:Thread.currentThread().getContextClassLoader()
  • 获得系统的ClassLoader:ClassLoader.getSystemClassLoader()
  • 获得调用者的ClassLoader:DriverManager.getCallerClassLoader()

2.自定义类加载器

在Java中,类加载器对应的类为ClassLoader,ClassLoader是一个抽象类,自定义类加载器需继承ClassLoader类。一个类加载器通过一个类的binary name(如:“java.lang.String”,javax.swing.JSpinner$DefaultEditor")去定位或产生这个类的数据,类加载器通过读取磁盘上的class文件创建出类对象。
在JVM中,数组类的Class对象不是由类加载器创建的,而是由java虚拟机在需要时自动创建。数组类的Class.getClassLoader()方法的返回值与数组中元素的Class.getClassLoader()方法返回值相同,如果数组中的元素是原生类型,那么这个数组类没有类加载器,返回null。
代码
public static void main(String[] args) {
    int[] array1 = new int[1];
    System.out.println(array1.getClass().getClassLoader());
    System.out.println("------------");
    String[] array2 = new String[1];
    System.out.println(array2.getClass().getClassLoader());
    System.out.println("------------");
    A[] array3 = new A[1];
    System.out.println(array3.getClass().getClassLoader());
    }
输出
null
------------
null
------------
sun.misc.Launcher$AppClassLoader@18b4aac2
在以上代码中,int为原生类型,类加载器为null;String类位于rt.jar中,对应的类加载器为启动类加载器,启动类加载器通常由C实现,在Java中没实际类实例来表示,所以此处显示null;用户自定义的类其数组的类加载器与类的加载器相同,为应用类加载器。
用户自定义一个类加载器,需要继承ClassLoader抽象类,然后重写其中的findClass方法,代码如下:
public class MyClassLoader extends ClassLoader {
    private String path;
    private String loaderName;
    private static final String fileExtension = ".class";

    public MyClassLoader(String loaderName){
        super();
        this.loaderName = loaderName;
    }
    //设置类的路径
    public void setPath(String path) {
        this.path = path;
    }

    //通过调用defineClass方法把读取的字节数组转为class对象
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = loadClassData(name);
        return this.defineClass(name,data,0,data.length);
    }
    //将class文件转为字节数组
    private byte[] loadClassData(String className){
        byte[] data = null;
        InputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        className = className.replace(".","/");
        try {
            inputStream = new FileInputStream(new File(this.path + className + fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();
            int i;
            while ((i = inputStream.read()) != -1){
                byteArrayOutputStream.write(i);
            }
            data = byteArrayOutputStream.toByteArray();
        }catch (Exception e){
            e.printStackTrace();
        }finally {

            try {
                inputStream.close();
                byteArrayOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


        return data;
    }
}

在构造方法中,调用了父类的构造方法,在父类的构造方法中,我们自定义的类加载器其父类加载器默认被指定为了应用类加载器(ClassLoader.getSystemClassLoader())。setPath方法用来指定需要加载的类的.class文件的路径。在重写的findClass方法中,首先读取磁盘中的.class文件,将其转为字节数组,然后调用ClassLoader.defineClass把字节数据转为Class对象。
Main方法中的执行代码如下:
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        MyClassLoader myClassLoader = new MyClassLoader("MyClassLoader");
        myClassLoader.setPath("/Users/wby/Desktop/");
        Class clazz = myClassLoader.loadClass("jvm.classloader.Person");
        Object p = clazz.newInstance();
        System.out.println(p.getClass().getClassLoader());
        System.out.println(p);
    }
在执行代码中,首先创建了一个自定义的类加载器的对象,然后指定类的存放路径,我们把需要加载的类的class文件放在了桌面上,然后指定该类的名称,接着执行loadClass方法,加载该类,得到结果如下。
sun.misc.Launcher$AppClassLoader@18b4aac2
Person{name='null', age=0}
在输出中,得到了类的对象,但是其类加载器为应用类加载器,我们自定义的类加载器没有使用到。查看loadClass方法的部分源码如下。
 Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } 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);

                    // 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();
                }
            }
在调用loadClass方法后,程序首先执行findLoadedClass方法查看类是否已被加载,如果没有被加载,会调用parent.loadClass方法,即调用父类加载器去加载这个类,最终会调用到findBootstrapClassOrNull方法使用启动类加载器加载,如果启动类加载器和其他的父类加载器一直没有加载成功,才会调用findClass方法。在之前的输出结果中,由于经过编译之后,用户类路径上存在Person.class文件,应用类加载器会从这个路径上成功加载这个类,轮不到自定义的类加载器去加载它,因此结果会输出AppClassLoader。
如果需要使用自定义类加载器去加载这个类,需要删除用户类路径上存在Person.class文件,只保留setPath方法中设置的类路径"/Users/wby/Desktop/"下的jvm.classloader.Person.class,然后再次执行,结果如下,此时就使用了自定义的类加载器。
jvm.classloader.MyClassLoader@610455d6
Person{name='null', age=0}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值