【JVM】 类加载器(ClassLoader)

ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部,在堆中创建实例,在方法区中创建Class文件的数据结构,把堆中的实例链接到方法区的数据结构上。(如下图所示)然后交给Java虚拟机进行链接、初始化等操作、因此,ClassLoader在整个装载(加载)阶段,只能影响到类的加载,而无法通过ClassLoader去改变类的链接和初始化行为。

 

因为Java虚拟机的设计者当初在设计类加载器的时候,并没有考虑将它绑定在Jvm内部,这样做的好处就是能够更加灵活和动态地执行类加载操作,所以如今类加载器却在OSGI(热部署)、字节码加密解密领域、版本隔离(Tomcat)等可以得到应用。

1 类的加载方式

class文件的显式加载与隐式加载的方式是指JVM加载class文件到内存的方式(在日常开发以上两种方式一般会混合使用)

  1. 显式加载:指的是在代码中通过调用ClassLoader加载class对象,如直接使用Class.forName(name)或this.getClass().getClassLoader().loadClass()加载class对象。
  2. 隐式加载:则是不直接在代码中调用ClassLoader的方法加载class对象,而是通过虚拟机自动加载到内存中,如在加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,此时额外引用的类将通过JVM自动加载到内存中。比如 new User()。

举例如下:

public class UserTest {  
    public static void main(String[] args) {  
        User user = new User(); //隐式加载  
  
        try {  
            Class clazz = Class.forName("com.atguigu.java.User"); //显式加载  
            ClassLoader.getSystemClassLoader().loadClass("com.atguigu.java.User");//显式加载  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
        }  
  
    }  
}  

2 命名空间

虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。

对于任意一个类,都需要由加载它的类加载器和这个本身一同确认其在JAVA虚拟机中的唯一性。每一个加载器,都拥有一个独立的类命名空间:比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义

解释代码如下,实例中在程序中通过不同类加载器加载同一个User对象,对象地址不相等。

/** 
 * @Author jlu 
 * @Date 9:44 2022/5/22 
 * @Description: 自定义加载器实践 
 * 注意:使用之前在目录下编译User对象 javac User.java 获得User的class文件 
 **/  
public class UserClassLoader extends ClassLoader {  
    private String rootDir;  
  
    public UserClassLoader(String rootDir) {  
        this.rootDir = rootDir;  
    }  
  
    /** 
     * 编写findClass方法的逻辑 
     */  
    @Override  
    protected Class<?> findClass(String name) throws ClassNotFoundException {  
        // 获取类的class文件字节数组  
        byte[] classData = getClassData(name);  
        if (classData == null) {  
            throw new ClassNotFoundException();  
        } else {  
            //直接生成class对象  
            return defineClass(name, classData, 0, classData.length);  
        }  
    }  
  
    /** 
     * 编写获取class文件并转换为字节码流的逻辑 * @param className * @return 
     */  
    private byte[] getClassData(String className) {  
        // 读取类文件的字节  
        String path = classNameToPath(className);  
        try {  
            InputStream ins = new FileInputStream(path);  
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            byte[] buffer = new byte[1024];  
            int len = 0;  
            // 读取类文件的字节码  
            while ((len = ins.read(buffer)) != -1) {  
                baos.write(buffer, 0, len);  
            }  
            return baos.toByteArray();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
  
    /** 
     * 类文件的完全路径 
     */  
    private String classNameToPath(String className) {  
        return rootDir + "\\" + className.replace('.', '\\') + ".class";  
    }  
  
    public static void main(String[] args) {  
        String rootDir = "C:\\code\\java\\java-tutorial\\tutorials-in-java\\tutorial-java-jvm\\src\\main\\java";  
  
        System.out.println("不同的类加载器加载同一个对象User,通过==进行比较,结果为:");  
        try {  
            //创建自定义的类的加载器1  
            UserClassLoader loader1 = new UserClassLoader(rootDir);  
            Class clazz1 = loader1.findClass("jvm.User");  
  
            //创建自定义的类的加载器2  
            UserClassLoader loader2 = new UserClassLoader(rootDir);  
            Class clazz2 = loader2.findClass("jvm.User");  
  
            System.out.println(clazz1 == clazz2); //clazz1与clazz2对应了不同的类模板结构。  
            System.out.println("创建自定义的类的加载器1加载User,所生成的地址:");  
            System.out.println(clazz1.getClassLoader());  
            System.out.println("创建自定义的类的加载器2加载User,所生成的地址:");  
            System.out.println(clazz2.getClassLoader());  
  
            //系统类加载器加载User对象  
            System.out.println("系统类加载器加载User对象所得到的地址:");  
            Class clazz3 = ClassLoader.getSystemClassLoader().loadClass("jvm.User");  
            System.out.println(clazz3.getClassLoader());  
  
            System.out.println("自定义对象的父加载器,即系统类加载器加载User对象所得到的地址:");  
            System.out.println(clazz1.getClassLoader().getParent());  
  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
        }  
    }  
}  

输出结果:

不同的类加载器加载同一个对象User,通过==进行比较,结果为:  
false  
创建自定义的类的加载器1加载User,所生成的地址:  
jvm.UserClassLoader@1b6d3586  
创建自定义的类的加载器2加载User,所生成的地址:  
jvm.UserClassLoader@74a14482  
系统类加载器加载User对象所得到的地址:  
sun.misc.Launcher$AppClassLoader@18b4aac2  
自定义对象的父加载器,即系统类加载器加载User对象所得到的地址:  
sun.misc.Launcher$AppClassLoader@18b4aac2  

3 SecureClassLoader与URLClassLoader

1) 接着SecureClassLoader扩展了 ClassLoader,新增了几个与使用相关的代码源(对代码源的位置及其证书的验证)和权限定义类验证(主要指对class源码的访问权限)的方法,一般不会直接跟这个类打交道,更多是与它的子类URLClassLoader有所关联

2) ClassLoader 是一个抽象类,很多方法是空的没有实现,而 URLClassLoader 这个实现类为这些方法提供了具体的实现,并新增了 URLClassPath 类协助取得 Class 字节流等功能。在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承 URLClassLoader 类,这样就可以避免去编写 findClass() 方法及其获取字节码流的方式,使自定义类加载器编写更加简洁

4 Class.forName()与ClassLoader.loadClass()(显示加载)

1) Class.forName():是一个静态方法,最常用的是Class.forName(String className);根据传入的类的全限定名返回一个 Class 对象。该方法在将 Class 文件加载到内存的同时,会执行类的初始化。如:Class.forName(“java.HelloWorld”);

2)ClassLoader.loadClass():这是一个实例方法,需要一个 ClassLoader 对象来调用该方法。该方法将 Class 文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化。该方法因为需要得到一个 ClassLoader 对象,所以可以根据需要指定使用哪个类加载器。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wL魔法师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值