类加载器详解(自己实现类加载器)

目录:
java虚拟机汇总

  1. class文件结构分析
    1).class文件常量池中的常量项结构
    2). 常用的属性表的集合
  2. 类加载过程
    1).类加载器的原理以及实现<<== 现在位置
  3. 虚拟机结构分析
    1).jdk1.7和1.8版本的方法区构造变化
    2).常量池简单区分
  4. 对象结构分析
    1).压缩指针详解
  5. gc垃圾回收
  6. 对象的定位方式

目的:看懂4,并且自己实现一个类加载器,前三条为理论知识,懂了可以跳过,最主要的是自己实现一个,并完成功能
1.类加载器是什么东西
2.类加载器的种类
3.类加载器的机制
4.自己实现一个类加载器
在这里引用大佬的链接,这个是讲的很详细的,如果心急,不想细细研究那就直接看我总结的吧

类加载器是什么东西?

1.类加载器就是加载所有的类的工具,它加载的类在内存中只会存在一份,也就是生成的堆中的Class对象。不可以重复加载
2.在我们java编码里,一个类是用全限定名(包名加类名)标识的唯一的一个类,但在jvm里是根据类加载器+全限定名来标识,也就是说不同类加载器加载的同一个类,在内存中也是不同的Class

类加载器的种类

java有三种类加载器(面试会问)
1)根类加载器(bootstrap class loader)
2)扩展类加载器(extension class loader)
3)应用类加载器(application class loader)
关系如下图所示
在这里插入图片描述
简单说一下用途
启动类加载器
大白话来说,加载核心类的,底层c++实现的,jvm启动时候就去你jdk加载jre/lib/rt.jar下的class,我们也触碰不到,并且它是不继承classLoader的,
查找范围:sun.mic.boot.class下面的路径

扩展类加载器:
它负责加载JRE的扩展目录,由Java语言实现,父类加载器为null(因为关系上父类加载器是启动类加载器,但是启动类加载器是c++,所以这里拟为null)。(注意父类加载器不代表那是他的父类,只是一个关系的称呼而已,你可以用getParent方法来查看此类的父加载器)
查找范围:java.ext.dirs下面的路径

应用类加载器
这个是和我们相关的,父加载器是扩展类加载器,我们默认自己实现的类加载器都是此应用类加载器的子加载器
查找范围:java.class.path (也就是你的工程下的bin目录)下面的路径

3.类加载器的机制

双亲委派机制
即一个类加载器接收到加载此类的请求时会先去请求父类加载器加载此类,如果父类还有父类则继续传给父类的父类,当找到最根的类加载器时(启动类加载器),加载器会尝试在自己的查找范围内找此类的class文件,若找到则加载,找不到就往回走,即告诉子类我不能处理这个任务,然后子类看看在他的范围能不能找到,找到就加载,再找不到继续往下传,都找不到报错
总之一句话:有事往上级抛,上级都处理不了我再处理

缓存机制
缓存机制指,类加载器加载时将缓存所有加载过的class,存在每一个类加载器的缓冲中,下次获取此类的class时先去缓存中找,那么结合双亲委派机制,流程应该是这样的
拿到加载请求后,先去我的缓存里找,没有了往上抛,上面接收到了,直接在自己的缓存找,没有了再往上抛,如果上面没有了,那就尝试加载,找不到了,往下扔,然后下面的拿到直接尝试加载,找不到了,再往下扔,下面没有了就报错

4.自己实现一个类加载器

上面都是些理论的知识,一定要实战才能加深印象!!!上面的可以半知半解,下面的一定要会,会了再去理解上面的知识
目的:自己写一个类加载器,加载一个D盘目录下的Test.class文件并获取实例

在这里插入图片描述在这里插入图片描述

自定义类加载器步骤
写一个类继承ClassLoader
复写findClass()方法。
在findClass()方法中调用defineClass()。(方法什么意思,看注释!)

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MyClassLoader extends ClassLoader {
    
    

    //自定义加载器时传进一个路径名,就跟其他类加载器的查找范围一样,
    //当从顶层开始查找时,任务从上面扔到我们这,我们也将去这个位置找那个类,找不到就往下抛
    String filePath;

    //构造器
    public MyClassLoader(String filePath) {
    
    
        this.filePath = filePath;
    }

    //这是继承的方法,要求传入一个文件名 例如:Test,返回一个Class对象,其中实际调用的是difineClass
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
        //这个是加上个.class后缀
        String fileName = name+".class";

        FileInputStream fi = null;
        ByteArrayOutputStream bos =null;
        byte[] bytes = null;

        try {
    
    
            fi = new FileInputStream(new File(filePath,fileName));
            bos = new ByteArrayOutputStream();
            int i =0;
            while((i = fi.read())!=-1){
    
    
                    bos.write(i);
                    bos.flush();
            }
            bytes = bos.toByteArray();

        } catch (FileNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            try {
    
    
                fi.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
            try {
    
    
                bos.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
        //最核心的方法,defineClass通过字节数组帮你创建了class对象
       return defineClass(name,bytes, 0, bytes.length);
    }

    public static void main(String[] args) {
    
    
        
        MyClassLoader myClassLoader = new MyClassLoader("D:\\");
        
        try {
    
    
            Class<?> test = myClassLoader.loadClass("Test");
            if(test!=null){
    
    
                Object x = test.newInstance();
                Method getA = test.getDeclaredMethod("getA", null);
                //调用方法
                System.out.println(getA.invoke(x,null));
            }else{
    
    
                System.out.println("err");
            }
        } catch (ClassNotFoundException | NoSuchMethodException e) {
    
    
            e.printStackTrace();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        } catch (InstantiationException e) {
    
    
            e.printStackTrace();
        } catch (InvocationTargetException e) {
    
    
            e.printStackTrace();
        }
    }
}
结果:3

注意现在我们的我们的目录结构,没有其他类,只有我们的类加载器,下面做对比会加上Test类
在这里插入图片描述
此时注意我们覆盖的是findClass方法,调用的是确是loadClass方法,其实loadClass方法会将此Test类扔到父加载器,一直扔到最顶端,然后开始向下调用此类加载器的findClass方法查找,如果找到,就返回一个实例,找不到继续往下扔
所以说要不破坏双亲委派机制就不要覆盖loadclass方法(面试题:怎么破坏双亲委派)
接下来注意:
在这里插入图片描述
我们在idea里又创建了同名的Test文件,带大家分析一遍流程,分析执行结果:
1.首先调用我们自己写的类加载器,自己创建的类加载器,默认父类加载器是applicationClassLoder,发现缓存里没有,上面走起
2.Test加载请求向上到applicationClassLoder,然后applicationClassLoder发现他的缓存里没有,继续向上抛
3.然后到extClassLoder里面了,他发现缓存里也没有,继续向上抛,此时发现extClassLoder父类加载器是null,所以直接抛给bootstrapClassLoder
4.bootstrapClassLoder发现缓存里也没有,然后又发现此时自己就是最顶层,于是开始在自己的路径sun.mic.boot.class里找,没有,自己处理不了,向下抛
5.同理ext也发现在自己路径里找不到,往下抛,
6.注意!此时已经到了application类加载器里了,找!java.class.path 路径,也就是bin路径,发现我们有了一个Test,好了,他不会在向下抛给我们自己创建的类加载器了,也就是说我们定义的类加载器的findClass没有被执行,直接让applicationClassloader加载了,于是报错
在这里插入图片描述
这就是双亲委派机制的流程,这样做有什么好处
1.保护我们原有类:如果没有这个机制,如果你在appicationClassLoader管理的目录下(bin目录)创建了一个Object类,好了,你用到的所有Object类全部都加载你自己创建的Object类,这样你就可以随意篡改原来的Object类(也可以是其他类),不安全
2.这样做可以使加载有优先级,避免类的重复加载,父亲已经加载了该类时,就没有必要子ClassLoader再加载一次

猜你喜欢

转载自blog.csdn.net/lioncatch/article/details/106013246