您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java类加载的护城河:深入探究双亲委派机制

时间:2023-08-21 13:35:16  来源:今日头条  作者:java小悠

前言

关于类加载,有两个非常重要的内容,就是类加载器和双亲委派机制,也是面试时常见考核问题。

一、类加载器

还是以这个简单的代码为例:

arduino复制代码package com.jvm.test; 
public class Book { 
    public static void mAIn(String[] args) { 
        String name = "《三体》"; 
        System.out.printf("一本你不看,都不知道何为“惊艳”二字的书:" + name); 
   } 
}

上面的类的加载是要通过类加载器来实现的。

JAVA中有几种类加载器:

  • 引导类加载器(BootstrapClassLoader): 这是JVM的内置类加载器,负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等,即对应如java.lang包中的类等。
    它是所有其他类加载器的父加载器,是类加载器层次结构的顶层,由JVM本身实现,由C++实现。需要注意的是:这里的“父”,不是我们Java中的类继承的父类,这里的“父加载器”可以理解是逻辑上的概念,更多是指加载器之间的协作机制。
  • 扩展类加载器(ExtClassLoader)::这个类加载器负责加载Java的扩展类库,位于<JAVA_HOME>/lib/ext目录下的JAR文件。
    他的父加载器是引导类加载器, 通过扩展类加载器,可以实现对JVM的扩展功能。
  • 应用程序类加载器(AppClassLoader):也被称为系统类加载器,它负责加载应用程序的类,也就是我们自己编写的Java代码的加载器。
    它的父加载器是扩展类加载器。
  • 自定义类加载器(CustomClassLoader): 除了上述的三种主要的类加载器,Java还提供自定义类加载器的能力,允许开发人员根据需求实现自己的类加载逻辑。
    负责加载用户自定义路径下的类包。

了解了这几种不同的加载器,如我们上述实例的代码的类的加载,应该也很容易得知是由 应用程序类加载器 来加载。

接下来看一个类加载器的示例:

ini复制代码package com.jvm.classloader;

import sun.misc.Launcher;

import java.NET.URL;

public class TestJDKClassLoader {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());

        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());

        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());

        System.out.println();

        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extClassloader = appClassLoader.getParent();
        ClassLoader bootstrapLoader = extClassloader.getParent();
        
        System.out.println("the appClassLoader : " + appClassLoader);
        System.out.println("the extClassloader : " + extClassloader);
        System.out.println("the bootstrapLoader : " + bootstrapLoader);

        System.out.println();
        System.out.println("bootstrapLoader加载以下路径文件:");
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urls.length; i++) {
            System.out.println(urls[i]);
        }

        System.out.println();
        System.out.println("extClassloader加载以下路径文件:");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.println();
        System.out.println("appClassLoader加载以下路径文件:");
        System.out.println(System.getProperty("java.class.path"));
    }
}

可以思考一下输出结果是啥?
输出结果:

bash复制代码null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader

the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
the extClassloader : sun.misc.Launcher$ExtClassLoader@6ce253f1
the bootstrapLoader : null

bootstrapLoader加载以下路径文件:
file:/Library/Java/JavaVirtualmachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/classes

extClassloader加载以下路径文件:
/Users/lan/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

appClassLoader加载以下路径文件:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/DNSns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools.jar:/Users/lan/lihy/study/tuling/jvm/jvm-full-gc/target/classes:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.1.2.RELEASE/spring-boot-starter-web-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-starter/2.1.2.RELEASE/spring-boot-starter-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot/2.1.2.RELEASE/spring-boot-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.1.2.RELEASE/spring-boot-autoconfigure-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.1.2.RELEASE/spring-boot-starter-logging-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/lan/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/lan/.m2/repository/org/Apache/logging/log4j/log4j-to-slf4j/2.11.1/log4j-to-slf4j-2.11.1.jar:/Users/lan/.m2/repository/org/apache/logging/log4j/log4j-api/2.11.1/log4j-api-2.11.1.jar:/Users/lan/.m2/repository/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar:/Users/lan/.m2/repository/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar:/Users/lan/.m2/repository/org/yaml/snakeyaml/1.23/snakeyaml-1.23.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.1.2.RELEASE/spring-boot-starter-json-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.9.8/jackson-databind-2.9.8.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.9.0/jackson-annotations-2.9.0.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.9.8/jackson-core-2.9.8.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.9.8/jackson-datatype-jdk8-2.9.8.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.9.8/jackson-datatype-jsr310-2.9.8.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.9.8/jackson-module-parameter-names-2.9.8.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-starter-Tomcat/2.1.2.RELEASE/spring-boot-starter-tomcat-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.14/tomcat-embed-core-9.0.14.jar:/Users/lan/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.14/tomcat-embed-el-9.0.14.jar:/Users/lan/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.14/tomcat-embed-websocket-9.0.14.jar:/Users/lan/.m2/repository/org/hibernate/validator/hibernate-validator/6.0.14.Final/hibernate-validator-6.0.14.Final.jar:/Users/lan/.m2/repository/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar:/Users/lan/.m2/repository/org/jboss/logging/jboss-logging/3.3.2.Final/jboss-logging-3.3.2.Final.jar:/Users/lan/.m2/repository/com/fasterxml/classmate/1.4.0/classmate-1.4.0.jar:/Users/lan/.m2/repository/org/springframework/spring-web/5.1.4.RELEASE/spring-web-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-beans/5.1.4.RELEASE/spring-beans-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-webmvc/5.1.4.RELEASE/spring-webmvc-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-aop/5.1.4.RELEASE/spring-aop-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-context/5.1.4.RELEASE/spring-context-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-expression/5.1.4.RELEASE/spring-expression-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar:/Users/lan/.m2/repository/org/springframework/spring-core/5.1.4.RELEASE/spring-core-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-jcl/5.1.4.RELEASE/spring-jcl-5.1.4.RELEASE.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar

Process finished with exit code 0

通过前面对几种类加载的了解,对这个输出结果应该问题不大。
但是可能有几个小疑问:
1、 System.out.println(
String.class.getClassLoader());这个语句,为何输出时null?
因为System类是Java核心类库中的类,它是由引导类加载器加载。引导类加载器是JVM的内置加载器,由C++实现,因此在Java中就输出为null。

2、System.out.println("the bootstrapLoader : " + bootstrapLoader);为何也输出为null? 这里extClassloader.getParent()获取扩展类加载器的父加载器,即引导类加载器,其由C++实现,因此在Java中就输出也就为null。

类加载初始化过程

 

如上类运行加载全过程图,可知在JVM启动的过程中,会有一系列的初始化操作,包括创建类加载器、加载核心类库等。在这个初始化的过程,C++(其实是JVM自身调用,因为JVM底层是C++实现,从底层的角度,就是C++代码调用Java)调用Javasun.misc.Launcher类的构造方法 Launcher()创建实例。
在Launcher构造方法的内部,会创建两个类加载器:

  • sun.misc.Launcher.ExtClassLoader(扩展类加载器)
  • sun.misc.Launcher.AppClassLoader(应用程序加载器)

Launcher构造器核心源码

 

JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。

二、什么是双亲委派?

双亲委派是一种类加载的机制。如果一个类加载器接到加载类的请求时,它首先不会自己尝试加载这个类,而是把这个请求任务委托给其父加载器去完成,依次递归,如果父加载器可以完成类加载任务,就成功返回。只有父加载器无法完成此加载任务时,才自己去加载。

JVM类加载器的层级结构图:

 

我们直接先来看一下应用程序类加载器(AppClassLoader)加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法, 该方法核心源码:

 

该方法的大体逻辑为:

  1. 首选,会检查自己加载器缓存,查看自己是否已经加载过目标类,如果已经加载过,直接返回。
  2. 如果此类没有被加载过,则判断一下是否有父加载器;如果有父加载器,则由父加载器(即 调用c = parent.loadClass(name, false);),如果没有父加载器则调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前的类加载器的findClass方法(即c = findClass(name);)来完成类加载。

双亲委派机制简单点说就是:先找父亲加载,不行再由儿子自己加载。

三、为什么要设计双亲委派机制?

  • 沙箱安全机制:核心类库由引导类加载器(Bootstrap ClassLoader)加载,防止恶意类替代核心类。不同类加载器加载的类相互隔离,防止类之间的冲突。例如自己写的java.lang.String类是不会被加载的。
  • 保证类的唯一性: 双亲委派机制确保在JVM中同一个类只会被加载一次,避免了类的重复加载,保证了类的唯一性。
  • 保证类的一致性: 类加载器的层次结构保证了类的一致性。当一个类加载器需要加载一个类时,它首先会委派给其父加载器去尝试加载。如果父加载器无法加载该类,子加载器才会尝试加载。这种委派链式查找保证了类的一致性。

如果你对唯一性 和 一致性有些混淆,那我们可以借助以下的例子进行帮助理解:

唯一性: 就像每个人的身份证号码都是独一无二的。在类加载机制中,就像每个类在Java中都有唯一的类加载器来加载,保证不同的类拥有不同的加载器,避免了类之间的冲突和混淆。

一致性: 无论在什么情况下使用身份证,一个人的身份证号码都是不变的。在类加载中,一致性指的是无论通过哪个类加载器加载同一个类,其类定义,在整个应用中都是一致性。

运行尝试加载自己写的java.lang.String类:

typescript复制代码package java.lang;

public class String {
    public static void main(String[] args) {
        System.out.println(">>> Hello String Class >>>");
    }
}

运行结果:

arduino复制代码错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
   public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application

Process finished with exit code 1

问题分析:
当运行自己定义的java.lang.String 类时,首先会由系统类加载器(应用程序类加载器)尝试加载这个类。由于类加载的双亲委派机制,当应用程序类加载器在其类加载缓存无法找到java.lang.String 类时,它会委托父加载器(扩展类加载器)尝试加载。同样,扩展类加载器也无法找到,会继续委托给引导类加载器。由于引导类加载器负责加载 Java 核心类库,它会在自己的类路径中找到系统提供的 java.lang.String 类。因此,最终执行的是核心类库中的 java.lang.String 类,该类没有定义 main 方法,导致执行报错。

这个示例,证实了双亲委派机制上述所说的沙箱安全机制特性,它阻止了开发人员在核心类库中创建同名类来替代原有的核心类。这样的机制确保了核心类库的稳定性和一致性,同时也防止了开发人员意外地覆盖核心类的行为。

全盘负责委托机制: “全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。

四、自定义类加载器

自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
代码示例:

java复制代码package com.jvm.classloader;

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

public class MyClassLoaderTest {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

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

        private byte[] loadByte(String name) throws Exception {
            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;
        }

        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();
            }
        }

        public static void main(String args[]) throws Exception {
            // 初始化自定义类加载器,会先初始化父类ClassLoader,
            // 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
            MyClassLoader classLoader = new MyClassLoader("/Users/lan/data/test");
            // 在路径/Users/lan/data/test下创建test/com/tuling/jvm 几级目录,将Book1类的复制类Book1.class丢入该目录
            Class clazz = classLoader.loadClass("com.jvm.test.Book1");
            Object obj = clazz.newInstance();

            Method method = clazz.getDeclaredMethod("getName", null);
            method.invoke(obj, null);
            System.out.println(clazz.getClassLoader().getClass().getName());

        }
    }
}

注意:如果classpath下有com.jvm.test.Book1的.class,先删除。
因为自定义加载器的父加载器是程序类加载器(AppClassLoader),基于类加载的双亲委派机制,比如我们示例中的com.jvm.test.Book1,会被委托给程序类加载器加载,如果classpath下存在此Book1.class,输出结果将是:sun.misc.Launcher$AppClassLoader。
因此,为了自定义加载器能按预期从路径其类加载路径/Users/lan/data/test下加载Book1,需要先删除classpath下的Book1.class。

五、如何打破双亲委派

来一个沙箱安全机制示例,尝试打破双亲委派机制,主要是通过重写类加载loadClass方法,实现自己的加载逻辑,不委派给双亲加载。然后用自定义类加载器加载我们自己实现的java.lang.String.class。
代码示例:

java复制代码package com.jvm.classloader;

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

public class MyClassLoaderTest {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

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

        private byte[] loadByte(String name) throws Exception {
            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;
        }

        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();
            }
        }

        /**
         * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
         * @param name
         * @param resolve
         * @return
         * @throws ClassNotFoundException
         */
        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) {
                    // 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.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }

                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }

        public static void main(String args[]) throws Exception {
            // 初始化自定义类加载器,会先初始化父类ClassLoader,
            // 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
            MyClassLoader classLoader = new MyClassLoader("/Users/lan/data/test");  // 在路径/Users/lan/data/test下创建java/lang 几级目录,将java.lang.String.class丢入该目录
            // 尝试用自己改写类加载机制去加载自己写的java.lang.String.class
            Class clazz = classLoader.loadClass("java.lang.String");
            Object obj = clazz.newInstance();

            Method method = clazz.getDeclaredMethod("sout", null);
            method.invoke(obj, null);
            System.out.println(clazz.getClassLoader().getClass().getName());

        }
    }
}

输出结果:

php复制代码java.lang.SecurityException: Prohibited package name: java.lang
	at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:28)
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:53)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.main(MyClassLoaderTest.java:72)
Exception in thread "main" java.lang.ClassNotFoundException
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:31)
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:53)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.main(MyClassLoaderTest.java:72)

从输出结果,可以看出即使我们自定义的类加载器打破了双亲委派机制,仍然无法成功加载 java.lang.String类。

这是因为尽管自定义类加载器打破了双亲委派机制,但是由于 Java 虚拟机的安全性设计,它仍然通过检查类名是否以 "java." 开头,禁止加载这些类。这种安全性设计保障了 Java 的稳定性和安全性,防止恶意代码对核心功能造成损害。

安全检测的核心源码

 

另外,java.lang.String.class位于jre/lib/rt.jar。

 

解压此jar包,即可获取到String.class:

 

写到最后

今天,介绍了Java类加载器及双亲委派机制,做下小结:

  1. 类加载与加载器分类: 类的加载是通过类加载器来实现的,主要涉及以下几种加载器:
  • 引导类加载器(BootstrapClassLoader)
  • 扩展类加载器(ExtClassLoader)
  • 应用程序类加载器(AppClassLoader)
  • 自定义类加载器(CustomClassLoader)
  1. 双亲委派机制的作用: JVM采用双亲委派机制来加载类,具体表现为:在加载类时,会首先自底向上地委派给父加载器,检查是否已加载过,若未找到,再自顶向下尝试加载。这种机制的目的:
  • 沙箱安全机制
  • 类的唯一性保证
  • 类的一致性保证
  1. 自定义加载器的能力: JVM允许用户自定义加载器,通常通过重写ClassLoader的findClass方法来实现。这样的自定义加载器可以从指定路径中加载特定的类。
    然而,需要注意的是,自定义加载器虽然能够打破双亲委派机制,但它仍然无法加载以java.开头的核心类库中的类。
  2. 如果打破双亲委派: 用户可以通过重写类加载方法,不委派给双亲加载,来实现打破双亲委派。
    实际应用中,例如Tomcat、JDBC等,这些场景需要在共享的类加载环境中加载不同版本的类,因此采取了自定义的类加载机制,打破了传统的双亲委派机制。


原文链接:
https://juejin.cn/post/7268820544996163639



Tags:Java类   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Java类的加载顺序是怎样的
1、虚拟机首次加载类时,会对静态初始块,静态对象,静态方法进行一次初始化。2、只有调用new方法时才会创建实例。3、类实例创建过程:首先执行父类初始化块部分,然后是父类的构造方...【详细内容】
2024-01-03  Search: Java类  点击:(59)  评论:(0)  加入收藏
深入探究Java类继承与接口实现
类索引、父类索引和接口索引是在JavaClass文件中用来确定类的继承关系的重要数据。通过这些索引,Java虚拟机可以准确地建立类之间的继承关系,实现多态性和接口的实现。首先,类...【详细内容】
2023-11-17  Search: Java类  点击:(198)  评论:(0)  加入收藏
Java类加载的护城河:深入探究双亲委派机制
前言关于类加载,有两个非常重要的内容,就是类加载器和双亲委派机制,也是面试时常见考核问题。一、类加载器还是以这个简单的代码为例:arduino复制代码package com.jvm.test; pub...【详细内容】
2023-08-21  Search: Java类  点击:(219)  评论:(0)  加入收藏
为什么Java类不支持多继承而接口可以?
每个用Java的人都知道java不支持多继承,但为什么呢?无论从抽象还是多态等层面思考,感觉都是行得通的,那么为什么不支持呢? 很多人都是分析一旦一个类继承了多个父类,那么父类中如...【详细内容】
2020-09-22  Search: Java类  点击:(204)  评论:(0)  加入收藏
Java类加载机制实现流程及原理详解
这篇文章主要介绍了Java类加载机制实现流程及原理详解,是我在网上看到的,简单易懂,是我喜欢的&lsquo;快餐&rsquo;,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一...【详细内容】
2020-06-16  Search: Java类  点击:(293)  评论:(0)  加入收藏
如何理解JAVA类装载器ClassLoader?高级开发才懂的技术点
类装载器就是录找类的字节码文件并构造出类的JVM内部表示对象的组件,在JAVA中,类装载器把一个类装入JVM中,需要如下步骤:1、装载:查找和导入Class文件2、链接:执行校验,准备和解析...【详细内容】
2019-11-22  Search: Java类  点击:(514)  评论:(0)  加入收藏
Java类的加载机制
1、什么是类的加载类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区...【详细内容】
2019-09-12  Search: Java类  点击:(902)  评论:(0)  加入收藏
Java类加载及对象创建过程详解
类加载过程类加载的五个过程:加载、验证、准备、解析、初始化。 加载在加载阶段,虚拟机主要完成三件事: 通过一个类的全限定名来获取定义此类的二进制字节流。 将这个字节流...【详细内容】
2019-07-25  Search: Java类  点击:(1116)  评论:(0)  加入收藏
▌简易百科推荐
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  vivo互联网技术    Tags:Java 8   点击:(14)  评论:(0)  加入收藏
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20    51CTO  Tags:Java代码   点击:(21)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18    51CTO  Tags:Java   点击:(24)  评论:(0)  加入收藏
Java生产环境下性能监控与调优详解
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,...【详细内容】
2024-02-04  大雷家吃饭    Tags:Java   点击:(56)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  编程技术汇  今日头条  Tags:Java   点击:(68)  评论:(0)  加入收藏
Java中的缓存技术及其使用场景
Java中的缓存技术是一种优化手段,用于提高应用程序的性能和响应速度。缓存技术通过将计算结果或者经常访问的数据存储在快速访问的存储介质中,以便下次需要时可以更快地获取。...【详细内容】
2024-01-30  编程技术汇    Tags:Java   点击:(72)  评论:(0)  加入收藏
JDK17 与 JDK11 特性差异浅谈
从 JDK11 到 JDK17 ,Java 的发展经历了一系列重要的里程碑。其中最重要的是 JDK17 的发布,这是一个长期支持(LTS)版本,它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性...【详细内容】
2024-01-26  政采云技术  51CTO  Tags:JDK17   点击:(88)  评论:(0)  加入收藏
Java并发编程高阶技术
随着计算机硬件的发展,多核处理器的普及和内存容量的增加,利用多线程实现异步并发成为提升程序性能的重要途径。在Java中,多线程的使用能够更好地发挥硬件资源,提高程序的响应...【详细内容】
2024-01-19  大雷家吃饭    Tags:Java   点击:(105)  评论:(0)  加入收藏
这篇文章彻底让你了解Java与RPA
前段时间更新系统的时候,发现多了一个名为Power Automate的应用,打开了解后发现是一个自动化应用,根据其描述,可以自动执行所有日常任务,说的还是比较夸张,简单用了下,对于office、...【详细内容】
2024-01-17  Java技术指北  微信公众号  Tags:Java   点击:(95)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  刘汪洋  51CTO  Tags:Java   点击:(74)  评论:(0)  加入收藏
站内最新
站内热门
站内头条