Java类加载器的学习---底层类加载器思想以及自己写的类加载器

Java 类加载机制

ava 虚拟机一般使用 Java 类的流程为:首先将开发者编写的 Java 源代码(.java 文件)编译成 Java 字节码(.class 文件),然后类加载器会读取这个 .class 文件,并转换成 java.lang.Class 的实例。有了该 Class 实例后,Java 虚拟机可以利用 newInstance 之类的方法创建其真正对象了。

ClassLoader 是 Java 提供的类加载器,绝大多数的类加载器都继承自 ClassLoader,它们被用来加载不同来源的 Class 文件。

Class 文件来源

  • Java自己的核心类 如 java.lang、java.math、java.io 等 package 内部的类,位于 $JAVA_HOME/jre/lib/ 目录下,如 java.lang.String 类就是定义在 $JAVA_HOME/jre/lib/rt.jar 文件里;
  • Java的核心扩展类,位于 $JAVA_HOME/jre/lib/ext 目录下。开发者也可以把自己编写的类打包成 jar 文件放入该目录下;
  • 开发者自己写的类,这些类位于项目目录下;
  • 动态加载远程的 .class 文件

Java的类加载器

针对上面四种来源的类,分别有不同的加载器负责加载。

  1. 级别最高的 Java 核心类 ,即$JAVA_HOME/jre/lib 里的核心 jar 文件。这些类是 Java 运行的基础类,由一个名为 BootstrapClassLoader 加载器负责加载,它也被称作 根加载器/引导加载器。注意,BootstrapClassLoader 比较特殊,它不继承 ClassLoader,而是由 JVM 内部实现
  2. 加载 Java 核心扩展类 ,即 $JAVA_HOME/jre/lib/ext 目录下的 jar 文件。这些文件由 ExtensionClassLoader 负责加载,它也被称作 扩展类加载器。当然,用户如果把自己开发的 jar 文件放在这个目录,也会被 ExtClassLoader 加载;
  3. 是开发者在项目中编写的类,这些文件将由 AppClassLoader 加载器进行加载,它也被称作 系统类加载器 System ClassLoader
  4. 如果想远程加载如(本地文件/网络下载)的方式,则必须要自己自定义一个 ClassLoader,复写其中的 findClass() 方法才能得以实现

双亲委托 加载机制

源码查看双亲委托加载机制过程如下

  1. 检查目标 class 是否曾经加载过,如果加载过则直接返回;
  2. 如果没加载过,把加载请求传递给 parent 加载器去加载;
  3. 如果 parent 加载器加载成功,则直接返回;
  4. 如果 parent 未加载到,则自身调用 findClass() 方法进行寻找,并把寻找结果返回。
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查是否曾加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    // 优先让 parent 加载器去加载
                    c = parent.loadClass(name, false);
                } else {
                    // 如无 parent,表示当前是 BootstrapClassLoader,调用 native 方法去 JVM 加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
                // 如果 parent 均没有加载到目标 class,调用自身的 findClass() 方法去搜索
                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();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
// BootstrapClassLoader 会调用 native 方法去 JVM 加载
private native Class<?> findBootstrapClass(String name);

系统默认三个主要类加载器

Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap, ExtClassLoader, AppClassLoader

类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。
(c语言写的)
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
关系如下图
这里写图片描述
类加载器的父类委托机制: 父类里面有.class 就先使用父类里的
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。

还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

每个类加载器加载类时,又先委托给其上级类加载器。当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的aa.jar包中后,运行结果为ExtClassLoader的原因。
<不同的类加强器,对于同一个类,是不能转换的>
比如Person类, 正常 p = new Person(){这个是通过java类加载器加载的) 那么我们再调用自己的加载器 类反射得到个 Object obj ;我们知道这个obj一定是Person 但是不能转换为Java类加载器 写的Person

package cn.hncu.classLoader;

import java.lang.reflect.Method;

import org.junit.Test;

public class ClassLoaderDemo1 {

    //1演示:类加载器中的父类委托机制---父类加载器能加载的,子类就不加载
    @Test
    public void demo1(){
        //如果“\jre\lib\ext”目录中的某个jar包中存在Person.class则会
        //屏蔽掉当前classpath下的Person类 ---父类委托机制的原因。
        //AppClassLoader加载classpath下的类, 父类加载器ExtClassLoader加载的是“\jre\lib\ext”目录中的所有jar包,爷爷类加载器BootStrap加载的是rt.jar

        Person p = new Person();
        p.aa();
    }

    @Test//看看默认的3个类加载器
    public void demo2(){
        ClassLoader loader = Person.class.getClassLoader();
        //如果“\jre\lib\ext”目录中的某个jar包中存在Person.class,则此处输出:ExtClassLoader。否则输出:AppClassLoader
        System.out.println("1:"+loader);

        loader = loader.getParent();
        System.out.println("2:"+loader);

        loader = loader.getParent();
        System.out.println("3:"+loader); //BootStrap是C开发的,因此这里输出null
    }

    @Test//用系统的3个类加载器是无法加载它们默认加载地方(rt.jar,ext目录,classpath)以外的类的----如果想要加载这些类(包括本地磁盘指定目录、来自网络甚至无中生有的类,得自己做类加载器
    public void demo3() throws Exception{
        //MyA a = new MyA(); a.aa();
        //用类反射来加载d盘a文件夹下的 “aa.MyA类”
        Class c = Class.forName("d:/a/aa.MyA");
        Object obj = c.newInstance();
        Method m = c.getDeclaredMethod("aa", nu`````    l   ;
        m.invoke(obj, null);
    }

}

自制类加载器

我们自己写的类加载器
自制类加载类的核心步骤:1.继承ClassLoader 2.调用父类的defineClass(对应class字节码文件的数据—byte[]),该方法会帮我们返回一个Class对象
因为ClassLoader类中的方法是protected权限的,因此我们想要访问,必须定义成它的子类

package cn.hncu.classLoader.myClassLoader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

//自制类加载类的核心步骤:1.继承ClassLoader  2.调用父类的defineClass(对应class字节码文件的数据---byte[]),该方法会帮我们返回一个Class对象
//因为ClassLoader类中的方法是protected权限的,因此我们想要访问,必须定义成它的子类

public class MyClassLoader extends ClassLoader{

    public Class findClass(String fileName) {
        Class c=null;
        try {
            //把外面的字节码文件(.class文件)读取到内存流baout中
            InputStream in = new FileInputStream(fileName);
            ByteArrayOutputStream baout = new ByteArrayOutputStream();
            int len=0;
            byte bs[] = new byte[512];
            while((len=in.read(bs))!=-1){
                baout.write(bs, 0, len);
            }
            baout.close();

            //把内存流baout中的数据定义成一个Class对象---利用父类ClassLoader中的一个方法
            byte b[] = baout.toByteArray();
            c = defineClass(null, b, 0, b.length);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassFormatError e) {
            e.printStackTrace();
        }
        return c;
    }

}
package cn.hncu.classLoader.myClassLoader;

import java.lang.reflect.Method;

import org.junit.Test;

public class TestMyClassLoader {

    public static void main(String[] args) {
        try {
            Class c = Class.forName("cn.hncu.classLoader.myClassLoader.TestMyClassLoader");
            Object obj = c.newInstance();
            System.out.println(obj);
            System.out.println(c.getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testMyClassLoader() throws Exception{
        MyClassLoader loader = new MyClassLoader();
        Class c = loader.findClass("D:/a/aa/MyA.class");
        Object obj = c.newInstance();
        System.out.println(obj);
        System.out.println(c.getClassLoader());

        Method m = c.getDeclaredMethod("aa", null);
        m.invoke(obj, null);

    }

}

部分内容引用地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值