JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制

1、类加载器

站在Java虚拟机的角度看,只有两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机、JDK8中),是虚拟机自身的一部分;另外一种是其他所有类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类 java.lang.ClassLoader

JDK8及以前版本中绝大多数程序都会使用到以下3个系统提供的类加载器来进行加载

  1. 启动类(引导类)加载器:负责加载支撑JVM运行的位于\lib目录下的核心类库,而且是Java虚拟机能够识别的类库加载到虚拟机内存中(如rt.jar、tools.jar、charsets.jar等,名字不符合的类库即使放到lib目录下也不会被加载)。

  2. 扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc. $ExtClassLoader中以Java代码的形式实现的。负责加载\lib\ext目录中或被java.ext.dirs系统变量所制定的路径中所有的类库,是一种Java系统类库的扩展机制

  3. 应用程序类加载器(Application Class Loader):是由sun.misc.launcher$AppClassLoader来实现,由于应用程序加载器是ClassLoader类中getSystemClassLoader()方法的返回值,也称为系统类加载器。负责加载用户类路径(ClassPath)上所有的类库,如应用程序中没有默认自己的类加载器,则使用应用程序加载器为默认加载器。

  4. 自定义加载器:负责加载用户自定义路径下的类包

1.1、Launcher源码

package com.learn.jvm;

import com.sun.crypto.provider.DESKeyFactory;

/**
 * @author liushiwei
 */
public class JvmClassLoader {


    public static void main(String[] args) {
        // 查看String的类加载器
        System.out.println(String.class.getClassLoader());
        // 查看DESKeyFactory扩展包中的加载器
        System.out.println(DESKeyFactory.class.getClassLoader().getClass().getName());
        // 查看当前类加载器
        System.out.println(JvmClassLoader.class.getClassLoader().getClass().getName());

    }
}

1.1.1、Launcher

sun.misc.Launcher类是java的入口,在启动java应用的时候会首先创建Launcher类,创建Launcher类的时候回准备应用程序运行中需要的类加载器。

Java命令执行代码的大体流程,结合图可以查看Launcher类的源码
JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制_第1张图片

类加载器是一个抽象类。给定类的二进制名称,类装入器应该尝试查找或生成构成该类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。

2、双亲委派机制

双亲委派模型的工作过程是:如果一个类加载器收到了类加载请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层都是如此,因此所有加载请求最终都传送给顶层的启动类加载器,只有父加载器反馈自己无法加载这个加载请求时(它的搜索范围没有找到所需的类),子加载器才会尝试自己去完成加载,双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载
JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制_第2张图片
好处

  1. 具备优先级的层次关系,例如java.lang.Object类,它放在rt.jar之中,无论那个类加载器加载这个类,都会向上委派给模型的最顶端启动类加载器加载,因此Object类在程序的各个类加载器中都能保证是一个类,从而保证被加载类的唯一性

  2. 这样便可以防止核心API库被随意篡改,如自定义Object类

    package java.lang;
    
    public class Object {
    
        public static void main(String[] args) {
            System.out.println("Object");
        }
    }
    

    结果在这里插入图片描述

2.1、ClassLoader源码

AppClassLoader的继承关系
JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制_第3张图片
JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制_第4张图片

应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下

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 (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 如果当前加载器父加载器为空则委托引导类加载器加载该类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 如果找不到类 抛出异常ClassNotFoundException
                // 说明父类加载器无法完成加载请求
            }

            if (c == null) {
                long t1 = System.nanoTime();
                // 在父加载器无法加载时,在调用本身的findClass方法进行类加载,
                // 最终调用的是URLClassLoader.findClass方法
                // 真正加载类的逻辑
                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;
    }
}

defineClass:经历验证、解析等
JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制_第5张图片

3、自定义类加载器

加载的类

public class JvmClassLoader1 {
    
    public void getValue(){
        System.out.println("JvmClassLoader1");
    }
}

自定义加载器,及测试内部类

package com.learn.jvm;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;


public class MyClassLoader  extends ClassLoader {

    private  String classPath;

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

    private byte[] loadByte(String name) throws IOException {

        String path = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + path.concat(".class"));
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
            return defineClass(name, data, 0, data.length);

        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
}

class MyClassLoaderDemo{

    public static void main(String[] args) throws Exception {
        // 初始化自定义类加载器,会先初始化父类ClassLoader,
        // 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader

        // D盘创建 demo/com/learn/jvm 几级目录,将JvmClassLoader类的复制类JvmClassLoader1.class丢入该目录
        MyClassLoader myClassLoader = new MyClassLoader("D:/demo");
        Class<?> aClass = myClassLoader.loadClass("com.learn.jvm.JvmClassLoader1");
        // 通过反射调用类中方法
        Object object = aClass.newInstance();
        Method getValues = aClass.getDeclaredMethod("getValue", null);
        getValues.invoke(object);
        System.out.println(aClass.getClassLoader().getClass().getName());
    }
}
  • 执行结果:
    • 如果当前项目路径下有JvmClassLoader1.class文件,则输出AppClassLoader应用程序加载器,因为自定义加载器的父加载器是AppClassLoader

    • 如果当前项目路径下无JvmClassLoader1.class文件,则输出MyClassLoader

为什么自定义的类加载器的父加载器是AppClassLoader

  • 因为初始化自定义类加载器时,会初始化父类ClassLoader,而ClassLoader的构造方法中,会给this.parent赋值,如下l.getClassLoader();中对应的类加载器就是AppClassLoader,满足双亲委派机制
    JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制_第6张图片

4、打破双亲委派机制

例如在Tomact中部署多个项目,每个项目使用的相同但不用版本的组件

自定义类加载器,在加载类时,没有遵循双亲委派机制(先委托父加载器,父加载器没有此类,最后在交给子加载器加载),可以是自己先加载,加载不到在委托父加载器,或不需要父加载器加载。

package com.learn.jvm;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;


public class MyClassLoader  extends ClassLoader {

    private  String classPath;

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


    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t1 = System.nanoTime();
                
                if(name.contains("com.learn.jvm")){
                    // 自定义加载器加载类
                    c = findClass(name);
                }else{
                    // 父加载器加载
                    c = this.getParent().loadClass(name);
                }

                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    private byte[] loadByte(String name) throws IOException {

        String path = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + path.concat(".class"));
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
            return defineClass(name, data, 0, data.length);

        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
}

class MyClassLoaderDemo{

    public static void main(String[] args) throws Exception {
        // 初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader

        // D盘创建 demo/com/learn/jvm 几级目录,将JvmClassLoader类的复制类JvmClassLoader1.class丢入该目录
        MyClassLoader myClassLoader = new MyClassLoader("D:/demo");
        
        // 使用当前类加载器加载
        ClassLoader classLoader = myClassLoader.getClass().getClassLoader();
        
        Class<?> aClass = myClassLoader.loadClass("com.learn.jvm.JvmClassLoader1");
        // 通过反射调用类中方法
        Object object = aClass.newInstance();
        Method getValues = aClass.getDeclaredMethod("getValue", null);
        getValues.invoke(object);
        System.out.println(aClass.getClassLoader().getClass().getName());
    }
}

你可能感兴趣的:(JVM,JVM,类加载器,双亲委派)