微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

一深入理解JVM类加载全过程及自定义加载器打破双亲委派机制

一. JVM核心类加载器类别:

  1. 引导类加载器:负责加载支撑JVM运行的位于jre/lib目录下的核心类库,由C++实现创建
  2. 扩展类加载器:负责加载支撑JVM运行的位于jre/ext扩展目录下的jar类包,由JVM启动器实例创建加载
  3. 应用程序类加载器:负责加载Classpath路径下的类包,由JVM启动器实例创建加载
  4. 自定义加载器:负责加载用户自定义路径下的类包

二. JVM类加载器初始化过程:

  1. C++实现创建引导类加载器
  2. C++创建JVM启动器实例 sun.misc.LauncherLauncher创建方式才用了单例模式,保证一个JVM中只有一个Launcher实例
  3. Launcher实例在构造时会创建加载扩展类加载器sun.misc.Launcher.ExtClassLoader实例、应用程序类加载器sun.misc.Launcher.AppClassLoader示例
  4. JVM认使用launcher.getClassLoader()返回的类加载器AppClassLoader的实例加载应用程序类

三. JVM类加载器运行全过程:

在这里插入图片描述

示例代码

	package com.example.demojvm.jvm;
		
	public class JvmTest {
	
	    public static void main(String[] args) {
	        System.out.print("Hello Word!");
	    }
	}
  1. 运行JvmTest.main(),会先使用命令 javac java com.example.demojvm.jvm.JvmTest.java 将JvmTest.java编译成.class文件,然后使用命令 java com.example.demojvm.jvm.JvmTest.class 运行JvmTest.class
  2. 此时底层的C++实现会使用Windows系统下的java.exe程序调用jvm.dll库文件创建Java虚拟机,并创建一个引导类加载器实例。引导类加载器实例会加载 jre/lib 目录下jar包中的java核心类。

    在这里插入图片描述

  3. 引导类加载器会创建JVM启动类实例 sun.misc.Launcher,Launcher类会加载创建扩展类加载器实例sun.misc.Launcher.ExtClassLoader、应用程序类加载器实例sun.misc.Launcher.AppClassLoader
    sun.misc.Launcher类源码 :

    在这里插入图片描述

    Launcher类创建时会创建加载扩展类加载器实例sun.misc.Launcher.ExtClassLoader、应用程序类加载器实例sun.misc.Launcher.AppClassLoader

    在这里插入图片描述

  4. C++实现调用launcher.getClassLoader()获取到应用程序类加载器实例loader,并调用 loader.loadClass()加载运行的类

    在这里插入图片描述

  5. 类加载完成时,JVM会执行JvmTest.main()方法,开始执行代码
  6. 代码执行完毕,程序销毁

四. loadClass过程步骤

在这里插入图片描述

步骤:

  1. 加载:在硬盘上查找并通过IO读入字节码文件,在内存中生成一个代表这个类的class对象,作为方法区这个类的各种数据的访问入口
  2. 验证:校验字节码文件是否符合Java规范 准备:给类的静态变量分配内存,并赋认值
  3. 解析:将符号引用替换为直接引用,把一些静态方法替换为指向数据所存内存地址的指针或句柄
  4. 初始化:对类的静态变量初始化为指定的值,执行静态代码
  5. 使用:运行代码
  6. 卸载:回收运行时常量

五. JVM类加载的双亲委派机制
双亲委派机制原理:

原理:加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载路径下都找不到目标类,则在自己的类加载器中查找并载入目标类

简述:先找父加载器加载,父加载器加载不到再自己加载

JVM类加载过程使用双亲委派机制原因:

  1. 沙箱安全机制

    核心类库只会由引导类加载器去加载,可以防止核心API库被随意篡改

  2. 避免类的重复加载

    如果父加载器已经加载了目标类,子加载器就没必要重复加载,保证被加载类的唯一性

JVM类加载的双亲委派机制加载类过程:

  1. 先检查指定名称的类自己是否已经加载,如果加载过就无需重复加载,直接返回
  2. 如果指定名称的类没有加载过,判断是否有父加载器,如果有父加载器则由父加载器加载(调用parent.loadClass(name, false)),如果没有父加载器则调用bootstrap类加载器来加载(其实就是引导类加载器)
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass()方法来完成类加载

JVM类加载双亲委派机制源码解析:

首先JVM认使用launcher.getClassLoader()返回的类加载器来加载应用程序类,而launcher.getClassLoader()获取到的类加载器其实是在launcher实例化时放进去的应用程序加载器。

appClassLoader.loadClass()先判断是否已加载过目标类,加载过则直接返回,未加载过则调用继承自ClassLoaderloadClass()方法

在这里插入图片描述

进入ClassLoader类的loadClass()方法,可以看到如果存在父加载器,会先调用父加载器的loadClass()方法。debugger可以看到appClassLoader的父加载器是ExtClassLoader类的实例。

在这里插入图片描述

进入ExtClassLoader类,发现ExtClassLoader类并没有实现loadClass()方法,但是继承了ClassLoaderloadClass()方法。debugger进入,可以看到ExtClassLoader实例的parent属性为null,所以走另一个分支,调用findBootstrapClassOrNull()方法

在这里插入图片描述


因为引导类加载器由C++创建加载,在Java中获取不到,所以ExtClassLoader实例的parent属性为空,调用findBootstrapClassOrNull()其实就是调用引导类加载器加载目标类。

可想而知,JvmTest.class位于Classpath下,引导类加载器只负责加载jre/lib目录下的核心类包,肯定加载不到JvmTest.class,那么ExtClassLoader加载器实例就会去自己调用findClass()方法加载。

在这里插入图片描述


同理ExtClassLoader实例也无法加载到JvmTest.class类,那么AppClassLoader实例也会去自己加载JvmTest.class类。AppClassLoader加载路径包括Classpath,最终在AppClassLoader加载器实例下加载到JvmTest.class类。

六. 自定义类加载器
根据JVM类加载过程可以发现,实现双亲委派机制的代码为:

在这里插入图片描述


那么想要自定义类加载器,实现自己想要的加载逻辑,只需要新建一个类继承ClassLoad类,并重写loadClass()方法就好了。

ClassLoaderloadClass()方法拷贝过来,修改类加载机制逻辑:

在这里插入图片描述

加载逻辑修改了,还需要调用defind()方法加载.class文件生成class对象。

实现findClass()方法,在findClass()方法调用defind()方法

在这里插入图片描述

最后在启动类中调用测试:

	public static void main(String[] args) {
	   try {
	        JvmTestClassLoader loader = new JvmTestClassLoader("D:/Project/CHAN/demo-jvm/target/classes");
	        Class<?> clazz = loader.loadClass("com.example.demojvm.jvm.JvmTest");
	        Object obj = clazz.newInstance();
	        ClassLoader classLoader = obj.getClass().getClassLoader();
	        System.out.print("classLoader = " + classLoader);
	    } catch (ClassNotFoundException | InstantiationException | illegalaccessexception e) {
	        e.printstacktrace();
	    }
	}

打印结果:

classLoader = com.example.demojvm.jvm.JvmTest$JvmTestClassLoader@27c170f0

打印结果为自定义JvmTest.JvmTestClassLoader,成功实现自定义类加载器,并打破JVM类加载器双亲委派机制

完整代码

package com.example.demojvm.jvm;

import org.springframework.util.StringUtils;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * JVM类加载机制测试Demo
 * @author 青袂
 * @date 2021-11-12
 * @desc JVM类加载机制测试Demo
 */
public class JvmTest {

    public static void main(String[] args) {
        try {
            JvmTestClassLoader loader = new JvmTestClassLoader("D:/Project/CHAN/demo-jvm/target/classes");
            Class<?> clazz = loader.loadClass("com.example.demojvm.jvm.JvmTest");
            Object obj = clazz.newInstance();
            ClassLoader classLoader = obj.getClass().getClassLoader();
            System.out.print("classLoader = " + classLoader);
        } catch (ClassNotFoundException | InstantiationException | illegalaccessexception e) {
            e.printstacktrace();
        }
    }


    /**
     * 自定义类加载器 实现自己想要的加载逻辑
     */
    static class JvmTestClassLoader extends ClassLoader{
        private String classpath;

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

        /**
         * 根据文件名称读取文件
         */
        private byte[] loadByte(String name) throws IOException {
            if(!StringUtils.hasLength(name)) {
                throw new RuntimeException("name can not be null or ''");
            }
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classpath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        /**
         * 实现自定义加载逻辑
         */
        @Override
        protected Class<?> loadClass(String name, boolean resolve)
                throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        //如果是指定路径下的类,使用自定义加载器去加载;如果不是,则使用父加载器去加载
                        if (name.startsWith("com.example.demojvm")) {
                            c = findClass(name);
                        } else {
                            c = this.getParent().loadClass(name);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printstacktrace();
                    }

                    if (c == null) {
                        long t1 = System.nanoTime();
                        c = findClass(name);

                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClasstime().addelapsedtimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }

        /**
         * 加载目标.class文件 生成对象
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] bytes = loadByte(name);
                return defineClass(name, bytes, 0, bytes.length);
            } catch (IOException e) {
                e.printstacktrace();
                throw new ClassNotFoundException(e.getMessage());
            }
        }
    }
}

解决问题:

  1. ExtClassLoader加载器实例加载jre/ext扩展目录下jar类包,由谁发起调用?什么时候调用

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐