java类加载器

参考博客
https://www.cnblogs.com/KingIceMou/p/7208319.html
https://blog.csdn.net/BIAOBIAOqi/article/details/6864567

概述

  1. ExtclassLoader的parent是null
    所以在ExtclassLoader中会去调用findBootstrapClassOrNull()
    findBootstrapClassOrNull会去调用native Class<?> findBootstrapClass(String name);
    到jvm中去查找BootstrapClass,交给Bootstrap来解决
  2. ClassLoader关键方法
    public Class<?> loadClass(String name) throws ClassNotFoundException {}
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{}
    protected Class<?> findClass(String name) throws ClassNotFoundException {}
    protected final void resolveClass(Class<?> c) {}

1.类加载体系

1.Bootstrap classLoader jvm 虚拟机自供(我们接触不到,也看不到其java文件)
2.ExtclassLoader jdk自供 扩展类加载器主要加载ext包下面的文件
3.Application Classloader jdk自供 系统上下文cass文件项目里面所有的class
4.Custom ClassLoader: 自定义类加载器 通过继承ClassLoader

他们不是通过extends 来指定父类的
通过属性 指针指明父类的
private final ClassLoader parent;

2.loadClass()方法

debug运行发现以下3种方式最终会走到ClassLoader.loadClass()方法内(jvm内部逻辑实现)

public class HelloWorld {
    public static void main(String[] args) throws         	
       //方式1
       //Class<?> aClass = Class.forName("com.yiwei.model.Anima");
       //方式2
       Class<Animal> animalClass = Animal.class;
       //方式3
       //Animal animal = new Animal();
    }
}

在这里插入图片描述

3.类加载器流程

1 findLoadedClass(name) //从jvm本地classLoader的缓存中找 class
2找不到就用双亲委派机制去加载class //递归调用父类
3父类中都处理不了,再调用自身写好的findClass(name)方法


public abstract class ClassLoader {
	xx

    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);
            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
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    xx
}

4.各个类加载器的加载范围

Bootstrap Loader
负责搜寻JRE所在目录的classes和lib目录下的.jar
ExtClassLoader
负责搜寻JRE所在目录的lib/ext 目录下的classes或.jar
AppClassLoader
搜寻 Classpath中是否有指定的classes并加载
在这里插入图片描述
红线框框的是被压缩了,里面指定了appClassloader的加载范围

😄:\Java\jdk1.8.0_212\bin\java.exe
“-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=64518:D:\IntelliJ IDEA 2020.1\bin” -Dfile.encoding=GBK
-classpath D:\Java\jdk1.8.0_212\jre\lib\charsets.jar;
D:\Java\jdk1.8.0_212\jre\lib\deploy.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\access-bridge-64.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\cldrdata.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\dnsns.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\jaccess.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\jfxrt.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\localedata.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\nashorn.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\sunec.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\sunjce_provider.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\sunmscapi.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\sunpkcs11.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\zipfs.jar;
D:\Java\jdk1.8.0_212\jre\lib\javaws.jar;
D:\Java\jdk1.8.0_212\jre\lib\jce.jar;
D:\Java\jdk1.8.0_212\jre\lib\jfr.jar;
D:\Java\jdk1.8.0_212\jre\lib\jfxswt.jar;
D:\Java\jdk1.8.0_212\jre\lib\jsse.jar;
D:\Java\jdk1.8.0_212\jre\lib\management-agent.jar;
D:\Java\jdk1.8.0_212\jre\lib\plugin.jar;
D:\Java\jdk1.8.0_212\jre\lib\resources.jar;
D:\Java\jdk1.8.0_212\jre\lib\rt.jar;
E:\servlet\out\production\servlet com.yiwei.HelloWorld

5.类加载流程保证了

  1. 一个class文件 只会被所有类加载器当中的一个类加载器去加载
    BootstrapclassLoader jvm虚拟机自供
    ExtclassLoader jdk自供 扩展类加载器主要加载ext包下面的文件
    Application Classloader jdk自供 系统上下文cass文件项目里面所有的class
    Custom ClassLoader:
  2. 同一个 ClassLoader里面只会存在一个相同class名宇 Class缓存
  3. 因为双亲委派,所以最优先处理class文件最优先的都是jdk自供的类加载器保证了核心的class都是jdk自己去加载
    能够保证保证系统核心的class文件不被改

假设黑客在你的项目里面也写了个java.lang.String类,在equals方法里面写了System.exit
,在加载java.lang.String类的时候,父类加载器会优先加载jdk自身的java.lang.String类
那么黑客写的破坏类String就不会再被加载,破坏代码不会生效的
jvm运行的时候String类还是原生的jdk中的String类

6.自定义类加载器破坏双亲委派

有两种方式
1.可重写findLoadedClass(name); 方法,让子类的缓存永远有返回值,那么就不会再委托
父类去加载class
2.可重写loadClass方法,不到父类中去加载class

7.类加载器使用回顾

public class MyClassLoader1 extends  ClassLoader{
    /*
    1.项目的直接上级默认类加载器为AppClassloader
    * */
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        Class<?> animalClass = systemClassLoader.loadClass("com.yiwei.model.Animal");
        System.out.println(animalClass);
        System.out.println(animalClass.getClassLoader());
    }
}

8.自定义破坏双亲委派版本1

public class Animal {
    public void eat(){
        System.out.println("吃肉");
    }
}

此处会因为preDefineClass()的安全验证失败,
感兴趣的可以去看此方法源码


public class MyClassLoader2 extends  ClassLoader{
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        byte[]  data1=getbytes("E:\\javaSE\\AboutClassLoader\\target\\classes\\com\\yiwei\\model\\Animal.class");
        Class<?> aClass = this.defineClass(name, data1, 0, data1.length);
        return aClass;
    }
    public byte[] getbytes(String path)  {
        File file = new File(path);
        FileInputStream inputStream = null;
        byte[] bytes=null;
        try {
            inputStream = new FileInputStream(file);
            bytes = new byte[inputStream.available()];
            int read = inputStream.read(bytes);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }
    /*
     * 1.破坏双亲委派,重写looadClass()方法
     *    1.读流需要读class文件的流  而不是java文件的流   所以需要去进行提前编译
     *    2.父类的类(object类)没加载的话   会再用当前类加载器去加载object
     *    3.在用MyClassLoader2加载Object的时候  会调preDefineClass进行安全验证,会抛出一个安全异常
     * */
    public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader2 myClassLoader2 = new MyClassLoader2();
        Class<?> aClass = myClassLoader2.loadClass("com.yiwei.model.Animal");
        ClassLoader classLoader = aClass.getClassLoader();
        System.out.println(classLoader);
    }
}

Exception in thread “main” java.lang.SecurityException: Prohibited package name: java.lang
at java.base/java.lang.ClassLoader.preDefineClass(ClassLoader.java:889)//加载object类
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1005)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:868)
at com.yiwei.MyClassLoader2.loadClass(MyClassLoader2.java:12)
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1007)//加载animal
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:868)
at com.yiwei.MyClassLoader2.loadClass(MyClassLoader2.java:12)
at com.yiwei.MyClassLoader2.main(MyClassLoader2.java:37)

9.自定义破坏双亲委派版本2

自己加载不出来Object类,那咱们就拿父加载器来加载Object类
Animal就由自己



public class MyClassLoader31 extends  ClassLoader{
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {

        byte[]  data1=getbytes("E:\\javaSE\\AboutClassLoader\\target\\classes\\com\\yiwei\\model\\Animal.class");
        Class<?> aClass = null;
        try {
            aClass = this.defineClass(name, data1, 0, data1.length);
        } catch (Exception exception){
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            aClass=systemClassLoader.loadClass(name);
        }
        return aClass;
    }
    public byte[] getbytes(String path)  {
        File file = new File(path);
        FileInputStream inputStream = null;
        byte[] bytes=null;
        try {
            inputStream = new FileInputStream(file);
            bytes = new byte[inputStream.available()];
            int read = inputStream.read(bytes);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }
    /*
     * 1.破坏双亲委派,重写looadClass()方法
     *    1.读流需要读class文件的流  而不是java文件的流   所以需要去进行提前编译
     *    2.父类的类(object类)没加载的话   会再用当前类加载器去加载object
     *    3.在用MyClassLoader2加载Object的时候  会调preDefineClass进行安全验证,会抛出一个安全异常
     *    4.这时候捕获异常  在catch里面用appClassLoader来加载Object类
     * */
    public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader31 aloader = new MyClassLoader31();
        Class<?> aClass = aloader.loadClass("com.yiwei.model.Animal");
        ClassLoader paclassloader = aClass.getClassLoader();
        System.out.println(paclassloader);
    }
}

在这里插入图片描述

10.自定义破坏双亲委派版本3+模拟热部署

真正的热部署是需要结合文件改变的监听器实现的,此处写的是模拟
用while(true)间隔时间热加载class文件,模拟热部署实现

public class MyClassLoader4 extends ClassLoader {

    //目的 让缓存里面永远能返回一个Class对象 这样就不需要走父类加载器了
    //在构造方法里面加载类  loadClass

    //项目的根路径
    public String rootPath;

    //所有需要由我这个类加载器加载的类存在这个集合
    public List<String> clazzs;

    //classPaths: 需要被热部署的加载器去加载的目录
    public MyClassLoader4(String rootPath, String... classPaths) throws Exception {
        this.rootPath = rootPath;
        this.clazzs = new ArrayList();

        for (String classPath : classPaths) {
            scanClassPath(new File(classPath));
        }
    }


    //扫描项目里面传进来的一些class
    public void scanClassPath(File file) throws Exception {
        if (file.isDirectory()){
            for (File file1 : file.listFiles()) {
                scanClassPath(file1);
            }
        }else{
            String fileName = file.getName();
            String filePath = file.getPath();
            String endName = fileName.substring(fileName.lastIndexOf(".")+1);
            if (endName.equals("class")){
                //现在我们加载到的是一个Class文件
                //如何吧一个Class文件 加载成一个Class对象????
                InputStream inputStream = new FileInputStream(file);
                byte[] bytes = new byte[(int) file.length()];
                inputStream.read(bytes);

                String className = fileNameToClassName(filePath);
                //文件名转类名
                //类名
                super.defineClass(className, bytes, 0, bytes.length);
                clazzs.add(className);
                //loadClass 是从当前ClassLoader里面去获取一个Class对象
            }
        }
    }


    public String fileNameToClassName(String filePath){
        //d: //project//com//luban//xxxx
        String str1=filePath.replace(rootPath,"");
        String className = str1.replaceAll("\\\\",".");

//com.luban.className.class
        className  =  className.substring(1,className.lastIndexOf("."));
        return className;
        //com.luban.classNamexxxx
    }
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> loadClass = findLoadedClass(name);
        //第一情况 这个类 不需要由我们加载
        //第二种情况 这个类需要由我们加载 但是 确实加载不到
        if (loadClass ==null){
            if (!clazzs.contains(name)){
                loadClass = getSystemClassLoader().loadClass(name);
            }else{
                throw  new ClassNotFoundException("没有加载到类");
            }
        }
        return loadClass;
    }

        //先做热替换  先加载单个Class
    public static void main(String[] args)  throws Exception {
        String rootPath = 
MyClassLoader4.class.getResource("").getPath().replaceAll("%20"," ");
//E:/Myhotdeployment/target/classes/
         while (true){
             MyClassLoader4 myClassLoader4 = new
                     MyClassLoader4(rootPath,rootPath+"com");
					//该对象是用自定义类加载器反射创建的,热替换会生效
             Class<?> aClass = myClassLoader4.loadClass("com.yiwei.model.Animal");
             aClass.getMethod("eat").invoke(aClass.newInstance());

             //new Test().test();new关键字用的是main的类加载器,热替换不会生效
             Thread.sleep(2000);
         }
        //1.用了while死循环,去刷新每次的MyClassLoader
		 // MyClassLoader中的class缓存其实是最新的class
        // 确保反射创建Test对象的时候,使用的class都是最新的 
        //2.new Test().xxx
            //jvm 用new关键字创建Test对象的时候不会走我们写好的classLoader  
//原因全盘委托机制

    }
}


注意
启动while循环后,修改Animal类为

public class Animal {
    public void eat(){
        System.out.println("吃福建人");
    }
}

重新编译
在这里插入图片描述
这样最新的代码才会编译到target的文件夹下才有最新Animal.class

结果
在这里插入图片描述

时间有限,类加载的东西挺多的,这里也没整理全,再再深的也没有去研究,希望有帮到大家

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值