JAVA(2)- JVM类加载器

本文详细介绍了Java虚拟机(JVM)内置的三大类加载器:根类加载器(Bootstarp)、扩展类加载器(ExtClassLoader)和系统类加载器(ApplicationClassLoader),并探讨了自定义类加载器的实现。文章还重点讲解了双亲委托机制的工作原理以及如何破坏这一机制。此外,讨论了类加载器的命名空间、运行时包以及类的卸载条件。通过对HelloWorld类的加载示例,展示了类加载过程及其影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

JVM类加载器
扩展:

概念

  1. JVM: Java虚拟机。

1、JVM内置三大类加载器

在这里插入图片描述

1.1根类加载器介绍
  • Bootstarp类加载器:C++编写,负责虚拟机核心类库的加载,有加载整个java.lang包
  • 可通过-Xbootclasspth来指定根加载器的路径
//根加载器获取加载路径
System.getProperty("sun.boot.class.path") 
public class BootstrapClassLoader {

    public static void main(String[] args) {
        System.out.println("Bootstrap:"+String.class.getClassLoader());

        String[] paths = System.getProperty("sun.boot.class.path").split(";");
        for (String path : paths) {
            System.out.println(path);
        }
    }
}

Bootstrap:null
D:\Java\jdk1.8.0_171\jre\lib\resources.jar
D:\Java\jdk1.8.0_171\jre\lib\rt.jar
D:\Java\jdk1.8.0_171\jre\lib\sunrsasign.jar
D:\Java\jdk1.8.0_171\jre\lib\jsse.jar
D:\Java\jdk1.8.0_171\jre\lib\jce.jar
D:\Java\jdk1.8.0_171\jre\lib\charsets.jar
D:\Java\jdk1.8.0_171\jre\lib\jfr.jar
D:\Java\jdk1.8.0_171\jre\classes
1.2扩展类夹杂器
  • ExtClassLoader:主要用于加载JAVA_HOME下的jre\lib\ext子目录里面的类库,由纯java语言实现。java.lang.URLClassLoader的子类,完整类名是sun.misc.Launcher$ExtClassLoader。
//扩展类加载器获取加载路径
System.getProperty("java.ext.dirs")
public class ExtClassLoader {

    public static void main(String[] args) {
        String[] paths = System.getProperty("java.ext.dirs").split(";");
        for (String path : paths) {
            System.out.println(path);
        }
    }
}

D:\Java\jdk1.8.0_171\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
1.3系统类加载器
  • ApplicationClassLoader:加载classpath下的类库资源,是自定义类加载器的默认父加载器,系统类加载器路径一般通过-calsspath或者-cp指定
//系统类加载器获取加载路径
System.getProperty("java.class.path")
public class ApplicationClassLoader {

    public static void main(String[] args) {
        System.out.println(ApplicationClassLoader.class.getClassLoader());
        String[] paths = System.getProperty("java.class.path").split(";");
        for (String path : paths) {
            System.out.println(path);
        }
    }
}

sun.misc.Launcher$AppClassLoader@18b4aac2
D:\Java\jdk1.8.0_171\jre\lib\charsets.jar
D:\Java\jdk1.8.0_171\jre\lib\deploy.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\access-bridge-64.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\cldrdata.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\dnsns.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\jaccess.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\jfxrt.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\localedata.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\nashorn.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\sunec.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\sunjce_provider.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\sunmscapi.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\sunpkcs11.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\zipfs.jar
D:\Java\jdk1.8.0_171\jre\lib\javaws.jar
D:\Java\jdk1.8.0_171\jre\lib\jce.jar
D:\Java\jdk1.8.0_171\jre\lib\jfr.jar
D:\Java\jdk1.8.0_171\jre\lib\jfxswt.jar
D:\Java\jdk1.8.0_171\jre\lib\jsse.jar
D:\Java\jdk1.8.0_171\jre\lib\management-agent.jar
D:\Java\jdk1.8.0_171\jre\lib\plugin.jar
D:\Java\jdk1.8.0_171\jre\lib\resources.jar
D:\Java\jdk1.8.0_171\jre\lib\rt.jar
E:\java\github-repository\roc-go\roc-go-understand\target\classes
E:\java\github-repository\roc-go\roc-go-common\target\classes
……
……
……

2、自定义类加载器

  • 由于双亲委托机制,所以要将HelloWorld.java和HelloWorld.class在当前项目目录下删除

  • HelloWorld.java

public class HelloWorld {

    static {
        System.out.println(" HelloWorld class static code block");
    }

    public String hello(){
        System.out.println(" HelloWorld hello method");
        return "hello";
    }

}


  • 自定义类加载器,加载HelloWorld
//自定义加载器必须是ClassLoader的直接或间接子类
public class MyClassLoader extends ClassLoader {

    //定义默认的class存放路径
    public static final Path DEFAULT_CLASS_DIR = Paths.get("E:\\");

    //允许指定class存放路径
    private final Path classDir;

    //默认加载目录
    public MyClassLoader() {
        super();
        this.classDir = DEFAULT_CLASS_DIR;
    }

    public MyClassLoader(String classDir) {
        super();
        this.classDir = Paths.get(classDir);
    }

    public MyClassLoader(ClassLoader parent, String classDir) {
        super(parent);
        this.classDir = Paths.get(classDir);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //获取类的二进制流
        byte[] classBytes = this.readClassBytes(name);
        if (Objects.isNull(classBytes) || classBytes.length == 0) {
            throw new ClassNotFoundException("can not load the class " + name);
        }
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }

    private byte[] readClassBytes(String name) throws ClassNotFoundException {
        //将包名转换为文件路径分隔符
        String classPath = name.replace(".", "/");
        //拼接上classDir路径 class文件全路径
        Path classFullPath = classDir.resolve(Paths.get(classPath + ".class"));
        if (!classFullPath.toFile().exists()) {
            throw new ClassNotFoundException("The class " + name + " not found .");
        }

        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Files.copy(classFullPath, baos);
            return baos.toByteArray();
        } catch (IOException e) {
            throw new ClassNotFoundException("load the class " + name + " not occur error .", e);
        }
    }

    @Override
    public String toString() {
        return "My ClassLoader";
    }

}

public class TestClassLoader {


    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        MyClassLoader classLoader = new MyClassLoader();
        Class<?> aClass = classLoader.loadClass("com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld");
        System.out.println(aClass.getClassLoader());
        Object instance = aClass.newInstance();
        System.out.println(instance);
        Method hello = aClass.getMethod("hello");
        Object invoke = hello.invoke(instance);
        System.out.println("Result:"+invoke);


    }
}

输出结果:
My ClassLoader
 HelloWorld class static code block
com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld@378fd1ac
 HelloWorld hello method
Result:hello

3、双亲委托机制

概念
  • 当一个类加载器被调用loadClass之后,它并不会直接加载,而是去找父加载器直到最顶层根加载器,开始自上而下进行加载。
    在这里插入图片描述

  • ClassLoader#loadClass源码片段

//resolve ; 是否进行解析
    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 t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //父加载器加载
                        c = parent.loadClass(name, false);
                    } else {
                        //parent为空 从根加载器加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                //如果父加载器加载之后还是 null 
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //丛当前加载器加载
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
  • loadClass流程

    1. 从当前已加载类中通过全路径名来获取该类,如果有则直接返回。
    2. 未被加载,开始判断是否有父加载器有父加载器委托给父加载器加载,否则委托给根加载器加载。
    3. 如果父加载器还是无法加载class、则调用当前类加载器方法findClass(name)加载
    4. 如果类被成功加载,做一些性能数据统计
    5. 由于loadClass制定了resolve为false,所以不会进行连接阶段的执行,这就解释了为什么通过类加载器不会导致类的初初始化
  • 如何不删除HelloWorld.java和HelloWorld.class文件,使用自定义加载器加载。

    • 设置父加载器为null
    • 设置父加载器为ExtClassLoader或BootstrapClassLoader
public class TestClassLoader {


    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        //扩展类加载器
        ClassLoader cl = TestClassLoader.class.getClassLoader().getParent();
        MyClassLoader classLoader = new MyClassLoader(cl); //父类传扩展类或根加载器 会跳过AppClassLoader加载
//        ClassLoader cl = null;
//        MyClassLoader classLoader = new MyClassLoader(); //父类传null 会走向根加载器加载
        Class<?> aClass = classLoader.loadClass("com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld");
        System.out.println(aClass.getClassLoader());
        Object instance = aClass.newInstance();
        System.out.println(instance);
        Method hello = aClass.getMethod("hello");
        Object invoke = hello.invoke(instance);
        System.out.println("Result:"+invoke);
    }
}

输出结果:
My ClassLoader
 HelloWorld class static code block
com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld@378fd1ac
 HelloWorld hello method
Result:hello

4、破坏双亲委托机制

  • 热部署:不停止服务的功能升级或增加新功能

    • 首先要卸载掉加载该模块所有Class的类加载器,卸载类加载器会导致所有类的卸载。无法对三大类加载器进行卸载,只能通过控制自定义类加载器才能做到。
  • 不在项目删除HelloWorld,加载HelloWorld

public class BrokeDelegateClassLoader extends MyClassLoader {

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                if (name.startsWith("java.") || name.startsWith("javax")) {
                    c = getSystemClassLoader().loadClass(name);
                } else {
                    c = super.findClass(name);
                    if (c == null) {
                        try {
                            if (getParent() != null) {
                                c = getParent().loadClass(name);
                            } else {
                                c = getSystemClassLoader().loadClass(name);
                            }
                        } catch (ClassNotFoundException e) {
                            // ClassNotFoundException thrown if class not found
                            // from the non-null parent class loader
                        }
                    }
                }
            }
            if (c == null){
                throw new ClassNotFoundException("The class "+name+" not found");
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
	@Override
    public String toString() {
        return "BrokeDelegateClassLoader";
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        BrokeDelegateClassLoader brokeDelegateClassLoader = new BrokeDelegateClassLoader();
        Class<?> aClass = brokeDelegateClassLoader.loadClass("com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld");
        System.out.println(aClass.getClassLoader());
        Object instance = aClass.newInstance();
        System.out.println(instance);
        Method hello = aClass.getMethod("hello");
        Object invoke = hello.invoke(instance);
        System.out.println("Result:" + invoke);
    }

}

BrokeDelegateClassLoader
 HelloWorld class static code block
com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld@4783da3f
 HelloWorld hello method
Result:hello

5、类加载器

5.1类加载器命名空间
  • 每个加载器实例有各自的命名空间,命名空间是由该加载器及其所有父加载器所构成的,因此每个类加载器中同一个class都是独一无二的。

  • MyClassLoader命名空间

BootstrapClassLoader.ExtClassLoader.AppClassLoader.MyClassLoader
public class NameSpace {

    public static void main(String[] args) throws ClassNotFoundException {
        /***系统类加载器加载同一个类 */
        ClassLoader classLoader = NameSpace.class.getClassLoader();
        Class<?> aClass = classLoader.loadClass("com.today.roc.go.understand.lock.LockThisMain");
        Class<?> bClass = classLoader.loadClass("com.today.roc.go.understand.lock.LockThisMain");
        System.out.println(aClass.hashCode());
        System.out.println(bClass.hashCode());
        System.out.println(aClass == bClass);

        /***
         * 不同类加载器,加载同一个类
         */
        ClassLoader cl = null;
        MyClassLoader myClassLoader = new MyClassLoader(cl);
        BrokeDelegateClassLoader brokeDelegateClassLoader = new BrokeDelegateClassLoader();

        Class<?> aClass1 = myClassLoader.loadClass("com.today.roc.go.understand.oop.Son");
        Class<?> aClass2 = brokeDelegateClassLoader.loadClass("com.today.roc.go.understand.oop.Son");
        System.out.println(aClass1.getClassLoader());
        System.out.println(aClass2.getClassLoader());
        System.out.println(aClass1.hashCode());
        System.out.println(aClass2.hashCode());
        System.out.println(aClass1 == aClass2);

        /***
         * 相同类型类加载器,加载同一个类
         */
        ClassLoader c2 = null;
        MyClassLoader myClassLoader3 = new MyClassLoader(c2);
        MyClassLoader myClassLoader4 = new MyClassLoader(c2);
        Class<?> aClass3 = myClassLoader3.loadClass("com.today.roc.go.understand.oop.Parent");
        Class<?> aClass4 = myClassLoader4.loadClass("com.today.roc.go.understand.oop.Parent");
        System.out.println(aClass3.getClassLoader());
        System.out.println(aClass4.getClassLoader());
        System.out.println(aClass4.hashCode());
        System.out.println(aClass2.hashCode());
        System.out.println(aClass3 == aClass4);

        /***
         * 769287236
         * 769287236
         * true
         * My ClassLoader
         * BrokeDelegateClassLoader
         * 2121055098
         * 2084435065
         * false
         * My ClassLoader
         * My ClassLoader
         * 806353501
         * 2084435065
         * false
         */
    }
}
5.2类加载器运行时包
  • 由类加载启动命名空间和类的全限定名称共同组成的
BrokeDelegateClassLoader brokeDelegateClassLoader = new BrokeDelegateClassLoader();
        Class<?> aClass = brokeDelegateClassLoader.loadClass("com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld");

//运行时包
BootstrapClassLoader.ExtClassLoader.AppClassLoader.MyClassLoader.BrokeDelegateClassLoader
.com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld

  • 出于安全和封装的考虑,类加载器通过不同的运行时包,判断类之间是否可以访问,比如String void getChars是包可见,此时其它的运行时包不可见,也不会出现其它非java.lang下的类访问getChars方法
5.3初始化类加载器
  • 问题:为什么开发的程序中能访问java.lang包下的类呢?

  • java.lang包下的类是由根加载器加载的,而我们开发的程序一般是由系统类加载器加载的,为什么我们在程序中能够new Object()活着new String()等任意的java.lang包下的类呢?

  • 如果某个类C被类加载器CL加载,那么CL就被称为C的初始类加载器。JVM为每个类维护了一个列表,记录了将该类加载器作为类初始加载器的所有class,JVM通过列表判断是否已经被加载过来,是否需要首次加载。

  • 根据JVM规范规定,类的加载过程中,所有参与的类加载器,即使没有亲自加载该类,也会被标识为该类的初始类加载器。

-

  • SimpleClass 包含String List ArrayList 等……
public class SimpleClass {

    private static byte[] buffer = new byte[8];

    private static String str = "";

    private static List<String> list = new ArrayList<>();

    static {
        buffer[0] = 1;
        str = "simple";
        list.add("element");
        System.out.println(buffer[0]);
        System.out.println(str);
        System.out.println(list.get(0));
    }

    public static void main(String[] args) {

    }
}

  • 测试
public class InitClassClassLoader {


    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        BrokeDelegateClassLoader classLoader = new BrokeDelegateClassLoader();
        Class<?> aClass = classLoader.loadClass("com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.概念.SimpleClass");
        System.out.println(classLoader.getParent());
        Object o = aClass.newInstance();
    }
}

输出结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
1
simple
element

有加载java.lang.Object List ArrayIist 等
在这里插入图片描述

5.4类的卸载
  • 运行期间加载多少class、可以在启动JVM时指定 -verbose:class 参数观察

  • 某个对象在堆内存中如果没有其它地方引用则会在GC回收,对象在堆内存中的Class对象以及Class在方法区中的数据结构何时被回收?

  • JVM规定一个Class只有在满足下面三个条件才会被GC回收,也就是类被卸载

    • 该类所有实例都被GC,比如Simple.class的所有Simple实例都被回收
    • 加载该类的ClassLoader实例被回收
    • 该类的class实例没有在其他地方被引用
6、总结

定义同名String,在类加载时报错

prohibited package name:java.lang错误
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值