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

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

类别:

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

层级关系:
在这里插入图片描述
其中各层级类加载器并无直接的父子继承关系,而是根据其中的parent属性来判断父加载器是谁。
自定义类加载器需继承ClassLoader类,对于ClassLoader类其官方解释如下:
在这里插入图片描述

二. 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对象,作为方法区这个类的各种数据的访问入口。
    "加载"是"类加载"过程的一个阶段,此阶段完成的功能是:
    1)通过类的全限定名来获取定义此类的二进制字节流
    2)将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构
    3)在内存中生成代表此类的java.lang.Class对象,作为该类访问入口.
  2. 验证:连接阶段第一步,校验字节码文件是否符合Java规范 ,不会危害虚拟机安全,使得虚拟机免受恶意代码的攻击.大致完成以下四个校验动作:
    1)文件格式验证
    2)源数据验证
    3)字节码验证
    4)符号引用验证
  3. 准备:连接阶段第二步,给类的变量分配内存,并赋默认值(仅包含类变量,不包含实例变量)
  4. 解析:连接阶段第三步,虚拟机将常量池中的符号引用替换为直接引用,把一些静态方法替换为指向数据所存内存地址的指针或句柄 。解析动作主要针对类或接口,字段,类方法,方法类型等等。
  5. 初始化:类加载过程的最后一步,对类的静态变量初始化为指定的值,执行静态代码块、构造器。在该阶段,才真正意义上的开始执行类中定义的java程序代码
  6. 使用:使用该类所提供的功能
  7. 卸载:从内存中释放

五. 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类包,由谁发起调用?什么时候调用?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值