返回顶部

JVM专题学习之类加载器(二)

类加载器

三层类加载器

1.启动类加载器-BootstrapClassLoader
AppClassLoader负责加载核心类,存放在lib目录下的jar包或class文件。
2.扩展类加载器-ExtensionClassLoader
ExtensionClassLoader负责加载\lib\ext目录下的jar包或class文件,我们可以将通用性的功能,打成jar包放置到ext
3.应用程序加载器-ApplicationClassLoader
ApplicationClassLoader负责加载用户类路径的所有类库,比如classpath就是由ApplicationClassLoader加载的。

他们三者关系如图所示:
img

双亲委派机制

工作机制

如果一个类加载器收到了类加载的请求,它会把这个请求委派给父类加载器去完成,每层的类加载都是这个处理逻辑,如果父加载器反馈自己无法完成的时候,子加载类就会自己去尝试加载。

为什么这样设计一个机制

其实是为了保证基础类库的稳定性,安全性。基础类rt.jar有很多我们开发中经常用到的类比如String类,假设没有设计这个机制,我们在项目中同样声明一个String类那岂不是被替换掉了。由此可以看出,此机制设计的目的在于保证基础类库的稳定性和安全性。

学习源码

//java.lang.ClassLoader
public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
 synchronized (getClassLoadingLock(name)) {
    // First, check if the class has already been loaded
    //1.首先,检查这个类是否被加载了
    Class<?> c = findLoadedClass(name);
     if (c == null) {
            //2.当前这个类未被加载
            //2.1 让上层去负责加载类
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //如果存在上层的类加载器
                    //就交给父加载器去加载
                    c = parent.loadClass(name, false);
                } else {
                    //如果不存在上层的类加载器
                    //就交给BootstrapClassLoader来负责加载
                    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.
                //2.1 说明父加载器未加载到类
                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;
    }
 }

自定义类加载器

1.代码构思

建好一个Order类,然后在Main方法中new一个Order类。在项目文件夹中找到Order.class文件。复制到这个路径:G:\com\classLoader\Order.class,然后删掉Order.java文件,因为项目中的类会优先使用ApplicationClassLoader去加载,达不到我们自定义类加载的效果。

  1. 继承java.lang.ClassLoader
  2. 重写findClass方法
  3. 自定义一个方法,通过自定义类加载器,加载磁盘上的class文件

注意:包名要保持一致,测试代码中的包名要和磁盘中的包名保持一致。要不然会报错

2.代码实现

package com.classLoader;

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

public class MyClassLoader extends ClassLoader{
    //指定从哪加载类
    private String classPath;

    public MyClassLoader(String classPath){
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //1.根据类全名获取到对应的字节数组
        byte[] data = loadByte(className);
        //2.通过JDK提供的API,将字节数组转换为Classs对象
        return defineClass(className,data,0,data.length);

    }

    private byte[] loadByte(String className) {
        //1.将类全名转换为完整类路径
        className = className.replaceAll("\\.","/");
        //G:\javaHomeWork\jvmDemo\src\main\java\com\Demo\com.classLoader
        StringBuilder stringBuilder = new StringBuilder(classPath);
        stringBuilder.append(className);
        stringBuilder.append(".class");
        System.out.println(stringBuilder);
        //2.采用文件字节流读取文件内容,并将其转换为字节数组
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(stringBuilder.toString());
            //3.创建字节数组,用于存放文件内容
            byte[] data = new byte[inputStream.available()];
            inputStream.read(data);
            return data;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(inputStream!=null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}

3.测试代码

package com.classLoader;

public class MyClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader myClassLoader = new MyClassLoader("G:/");
        Class<?> clazz = myClassLoader.loadClass("com.Person");
        //AppClassLoader MyClassLoader
        System.out.println(clazz.getClassLoader());
        System.out.println(myClassLoader.getParent());
    }
}

4. 运行结果

img

5. 自定义类加载器关系

img

6. 打破双亲委派机制

我们在不删除Order类的情况下去使用我们的自定义类加载器去加载Order类

思路如下:

  • loadClass方法规定了双亲委派机制的工作模式
  • 只需要重写loadClass方法

代码实现

@Override
    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) {
                if (name.startsWith("com.classLoader")) {
                    //如果是我们自己项目自定义的类,则交给自己来加载
                    c = findClass(name);
                }else{
                    //交给父类加载器去加载
                    //保证核心类库的唯一性
                    c = this.getParent().loadClass(name);
                }

            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

运行结果

img

7.Tomcat的类加载机制

tomcat如何保证每个应用的类库的独立的?

img
不同的类加载器实例加载的类是隔离的
tomcat为每个web项目创建一个类加载实例,webappClassLoader

posted @ 2024-12-21 16:15  搬砖的杰先生  阅读(7)  评论(0编辑  收藏  举报