深入分析ClassLoader工作机制

classLoader:类加载器

类加载器的作用:将class加载进jvm;审查类是由哪个类加载器加载的;将类字节码重新解析成JVM统一的对象格式

ClassLoader是个抽象类,一般实现自己的ClassLoader,会继承URLClassLoader

ClassLoader的等级加载机制:

类加载器 加载目录 继承自
启动类加载器(BootstrapClassLoader) %JRE_HOME%\lib 本身是*加载器
扩展类加载器(ExtensionClassLoader) %JRE_HOME%\lib\ext URLClassLoader
应用程序类加载器(ApplicationClassLoader) CLASSPATH指定的jar或目录 URLClassLoader
自定义类加载器(MyClassLoader) 编写人员指定目录 ClassLoader

注意:

BootstrapClassLoader 没有子类,不遵守ClassLoader的加载规则

ExtensionClassLoader 的父类也不是BootstrapClassLoader,且没有父类

ExtensionClassLoader 和ApplicationClassLoader 都继承了URLClassLoader ,URLClassLoader又继承了ClassLoader;ExtensionClassLoader 和ApplicationClassLoader 在Launcher对应,构造函数中首先创建ExtensionClassLoader ,然后以他为父加载器构建 ApplicationClassLoader

JVM加载class文件到内存的两种方式:

隐式加载:不用代码调用类加载器加载需要的类,而是通过jvm自动加载

显示加载:调用类加载器加载类

ClassLoader加载class文件到JVM时需要的步骤:

1.找到class文件,并把这个文件包含 的字节码加载进内存

URLClassLoader通过findClass具体实现:

通过URLClassPath获取要加载的class字节流,URLClassPath是在URLClassLoader的构造函数中创建,并指定搜索路径,找到class文件以后,读取他的byte字节流,通过调用define方法生成类对象。

ClassLoader类型 参数选项 说明
BootstrapClassLoader -Xbootclasspath 设置搜索路径
ExtensionClassLoader -Djava.ext.dirs 同上
ApplicationClassLoader -Djava.class.path=或者-cp 或-classpath 同上

2.字节码验证,准备,解析

3.类的静态属性和初始化赋值,静态代码块等

常见加载类错误分析:

显示加载类的方式:

类Class中的forName() 方法;

类ClassLoader中的loadClass()方法;

类ClassLoader中findSystemClass()方法

ClassNotFoundExpection:

jvm加载类到内存,未找到这个类,需要检查当前目录下有没有指定文件存在

NoClassDefFoundError:

可能是加载类时未使用完整包名+类名,导致未加载成功

UnsatisfiedLinkError:通常在解析native标识的方法时JVM找不到对应的本机库文件

ClassCastException:强制类型转换异常

常用类加载器分析:

tomcat中使用的类加载器是StandardClassLoader 和 WebappClassLoader

StandardClassLoader是在bootstrap类中创建,若未指定父类加载器,默认使用AppClassLoader,StandardClassLoader创建成功将会设置为Tomcat的根ClassLoader。

获取tomcat类中的类加载器不是StandardClassLoader?

StandardClassLoader未覆盖loadClass()方法,所以会由父类加载器AppClassLoader加载。

加载Servlet使用的类加载器是WebappClassLoader

实现自己的ClassLoader:


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

/***
 * 获取当前类工程路径
 *this.getClass().getResource("/").getPath()
 * 获取当前类的绝对路径
 *this.getClass().getResource("").getPath()
 * 获取当前工程路径
 *System.getProperty("user.dir")
 */

public class PathClassLoader extends ClassLoader{

    private java.lang.String packageName = "com.yangjie.test";

    private String classPath;

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

    @Override
    protected Class<?> findClass(java.lang.String name) throws ClassNotFoundException {
        if(name.startsWith(packageName)){
           byte[] classData = getData(name);
           if(classData == null){
               throw new ClassNotFoundException();
           }else {
               return defineClass(name,classData,0,classData.length);
           }
        }else {
            return super.findClass(name);
        }

    }

    private byte[] getData(java.lang.String name) {
        //转义问题,最好将路径转成/,java和linux都可以识别
        String path = classPath + name.replace('.', '/') + ".class";

        InputStream in = null;
        try {
            in = new FileInputStream(path);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024*2];
            int num = 0;
            while((num =in.read(buffer))!=-1){
                stream.write(buffer,0,num);
            }
            return stream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    public static void main(String[] args) {
        ///D:/ajavaDemo/lianxi/target/classes/
        System.out.println( PathClassLoader.class.getResource("/").getPath());
        ///D:/ajavaDemo/lianxi/target/classes/
        System.out.println( PathClassLoader.class.getResource("").getPath());
        //D:\ajavaDemo\lianxi
        System.out.println( System.getProperty("user.dir"));
        PathClassLoader pathClassLoader = new PathClassLoader(PathClassLoader.class.getResource("").getPath());
        try {
            //Class<?> person = pathClassLoader.loadClass("Person");
            Class<?> person = pathClassLoader.findClass("com.yangjie.test.Person");
            System.out.println("字节码名称:"+person.getName());
            System.out.println("类加载器名称:"+person.getClassLoader());
            Method[] methods = person.getMethods();
            for (Method method : methods) {
                System.out.println("方法名称:"+method.getName());
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

ClassLoader主要方法:

findClass:可以自己定义类的加载规则,从而取得类的字节码

defineClass:根据字节码生成Class对象

resolveClass:类在什么时候被JVM链接

loadClass:运行时获取一个类对象,this.getClass().getClassLoader().loadClass("class")

实现类的热部署:

JVM表示一个类是否是同一个类两个条件:

1、两个类的完整类名是否一样,包括包名+类名

2、看类加载是不是同一个实例,即使是同一个类加载器,不是一个实例,加载的类也不一样,所以热部署就可以通过创建不同的ClassLoader实例对象,然后通过不同的实例对象加载同名的类。

package com;

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

public class ClassReloader extends ClassLoader{

    private String classpath;

    public ClassReloader(String classpath){
        this.classpath = classpath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getData(name);
        if(classData == null){
            throw new ClassNotFoundException();
        } else {
           return defineClass(name, classData, 0 ,classData.length);
        }
    }

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

    public static void main(String[] args) {
        String path = "D:/ajavaDemo/lianxi/target/classes/";
        try {
            ClassReloader reloader1 = new ClassReloader(path);
            Class<?> aClass = reloader1.findClass("Student");
            ClassReloader reloader2 = new ClassReloader(path);
            Class<?> bClass = reloader2.findClass("Student");
            System.out.println(aClass.newInstance());
            System.out.println(bClass.newInstance());
            System.out.println(aClass.newInstance().equals(bClass.newInstance()));
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

上一篇:2021-10-24


下一篇:Java中的类加载器ClassLoader