Java 类加载ClassLoader学习心得

时间:2022-01-31 13:41:46

本文是笔者在学习Java 类加载的过程中,整理的心得体会,共勉!

类加载的意义

简单概念:

将Java类的.class文件中的二进制数据读入到内存中,放置在运行时数据区的方法区内。

什么时候会进行类加载?

         本地系统加载;

         代理加载,比如Spring的AOP动态代理;

         从jar包中加载;

         等等等等

ClassLoader类结构介绍

ClassLoader是一个抽象类,核心方法如下:

defineClass():目的是将byte字节流解析成JVM能够识别的Class对象。

findClass():此方法支持重载,与defineClass()配合使用,目的是获取Class对象的字节码。这个方法的意义在于,我们不仅仅可以通过class文件实例化对象,也可以通过其他方式,比如从网络上获取的字节码文件,可能会有对应的加密规则。

loadClass():此方法支持重载,目的是获取加载类的类对象。

resolveClass():实现让JVM链接这个类,此方法调用的是本地方法,不能重载。

Class类的生命周期

 Java 类加载ClassLoader学习心得

 

此生命周期对应到ClassLoader类的具体方法:
加载:loadClass()、findClass()、defineClass()

连接:包括验证、准备、解析:resolveClass() 本地native方法

初始化:JVM负责

类加载机制(双亲委托)

概念介绍:

大致的ClassLoader分类:

  1. Bootstrap ClassLoader:负责加载存放在 JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被 -Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库
  2. ExtClassLoader:负责加载 JDK\jre\lib\ext目录中,或者由 java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器
  3. AppClassLoader:负责加载用户类路径(ClassPath)所指定的类,其父类是ExtClassLoader

心得体会:

  这里的父加载器与子加载器 在java类关系上并不是继承关系;

  实现自己的类加载器,不管是直接实现抽象类ClassLoader,还是继承其他子类,父加载器都是AppClassLoader;

类加载的方式

1、命令行启动应用时候由JVM初始化加载

2、通过Class.forName()方法动态加载

3、通过ClassLoader.loadClass()方法动态加载

Class.forName()ClassLoader.loadClass()区别

Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;

ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

Class.forName(name,initialize,loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 

应用实践

实现自定义的ClassLoader

基于上面对于类加载器的分析,自定义ClassLoader的情况如下:

  1. 需要在自定义路径下查找自定义的class文件
  2. 对我们想要加载的类做特殊处理,比如通过网络传输的类的字节码可能进行了加密,获取这种类的字节码需要在我们自定义的ClassLoader中实现
  3. 实现类的热部署

从上面的分析我们还可以得到,自定义ClassLoader的实现思路可以通过继承ClassLoader类,重写findClass()来实现,当然你也可以重写loadClass(),但是默认的loadClass()是实现类加载双亲委托模式的,除非你想要破坏这种双亲委托模式,不然就别重写了。

下面是我在本机写的测试自定义ClassLoader的Demo:

/**
 * 自定义路径下的class文件加载
 * @Auther: trey_stao@163.com
 * @Date: 2018/5/31 19:34
 * @Description:
 */
public class PathClassLoader extends ClassLoader {
    private String classPath;

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

    private String packageName = "com.trey.classloader.Test";

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if(packageName.startsWith(name)) {
            byte[] classData = getData(name);
            if(classData == null) {
                throw new ClassNotFoundException();
            } else {
                System.out.println("当前类加载器:"+this.getClass().getClassLoader().toString());
                return defineClass(name, classData, 0, classData.length);
            }
        } else {
            return super.loadClass(name);
        }
    }

    private byte[] getData(String name) {
        String path = classPath + File.separatorChar + name.replace('.',File.separatorChar) + ".class";
        try {
            InputStream is = new FileInputStream(path);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int num = 0;
            while ((num = is.read(buffer)) != -1) {
                stream.write(buffer, 0, num);
            }
            return stream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

 

实现类的热部署

这是给自己布置的对类加载学习的总结练习。下面说下自己准备实现的思路:

  1. 实现一个server服务,监听具体目录下的所有class文件的更新情况
  2. 当出现class文件更新时,使用自定义的classLoader进行加载