精华内容
下载资源
问答
  • 类加载器
    千次阅读
    2022-03-13 21:21:26

    1 引导类加载器

            引导类加载器(Boostrap ClassLoader),又叫启动类加载器。

            由C/C++语言实现,嵌套在JVM内部。

            用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类。

            并不继承自 java.lang.ClassLoader,没有父加载器。是扩展类加载器和应用类加载器的父类加载器。

            出于安全考虑,Boostrap类加载器只加载包为java、javax、sun等开头的类。

    2 扩展类加载器

            扩展类加载器(Extension ClassLoader),Java语言编写,由sun.misc.Launcher类的内部类ExtClassLoader类实现,派生于ClassLoader类,父加载器为引导类加载器。

            从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的jar包放在此目录下,也会自动由扩展类加载器加载。

    3 系统类加载器

            系统类加载器(System ClassLoader),也称为应用程序类加载器。Java语言编写,由sun.misc.Launcher类的内部类AppClassLoader类实现,派生于ClassLoader类,父加载器为扩展类加载器。

            负责加载环境变量classpath或系统属性java.class.path指定路径下的类库。

            该类加载器是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载。

            通过ClassLoader.GetSystemClassLoader()方法可以获取到该类加载器。

    4 用户自定义类加载器

            用户自定义类加载器(User-Defined ClassLoader),ClassLoader子类。

            用户自定义类加载器实现步骤:

            1、开发人员通过继承抽象类java.lang.ClassLoader类的方式,实现自己的类加载器,以满足一些特殊需求。

            2、不建议覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findCalss()方法中。

            3、在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样可以避免自己去编写findClass()方法以及获取字节码流的方式,使自定义类加载器编写更加简洁。

    更多相关内容
  • Java基础之类加载器

    千次阅读 2022-04-04 17:35:36
    本文首先介绍了Java虚拟机加载程序的过程,简述了Java类加载器的加载方式(双亲委派模式),然后介绍了几种常见的类加载器及其适用场景,最后则一个例子展示了如何自定义类加载器。 基本概念 基本文件类型和概念 ...

    Java类加载器是用户程序和JVM虚拟机之间的桥梁,在Java程序中起了至关重要的作用,理解它有利于我们写出更优雅的程序。本文首先介绍了Java虚拟机加载程序的过程,简述了Java类加载器的加载方式(双亲委派模式),然后介绍了几种常见的类加载器及其适用场景,最后则一个例子展示了如何自定义类加载器。

    基本概念

    基本文件类型和概念

    常见概念介绍:

    [图片上传失败...(image-7c7f00-1649064930562)]

    1. java源文件(.java):.java是Java的源文件后缀,里面存放程序员编写的功能代码,只是一个文本文件,不能被java虚拟机所识别, 但是java语法有其自身的语法规范要求,不符合规范的java程序应该在编译期间报错。

    2. java字节码文件(.class):可以由java文件通过 javac这个命令(jdk本身提供的工具)编译生成,本质上是一种二进制文件,这个文件可以由java虚拟机加载(类加载),然后进java解释执行, 这也就是运行你的程序。
      java字节码文件(.class文件)看起来有点多余,为什么java虚拟机不能直接执行java源码呢?主要是为了实现 多语言支持性:java虚拟机本身只识别.class文件,所以任何语言(python、go等)只要有合适的解释器解释为.class文件,就可以在java虚拟机上执行。下文为java官方对于Class文件和虚拟机关系之间的描述原文。

      The Java Virtual Machine knows nothing of the Java programming language, only of a particular binary format, the class file format. A class file contains Java Virtual Machine instructions (or bytecodes) and a symbol table, as well as other ancillary information. For the sake of security, the Java Virtual Machine imposes strong syntactic and structural constraints on the code in a class file. However, any language with functionality that can be expressed in terms of a valid class file can be hosted by the Java Virtual Machine. Attracted by a generally available, machine-independent platform, implementors of other languages can turn to the Java Virtual Machine as a delivery vehicle for their languages.

    3. java虚拟机:Java Virtual Machine(缩写为JVM),仅识别.class文件,可以把.class文件加载到内存中,生成对应的java对象。还有内存管理、程序优化、锁管理等功能。所有的java程序最终都运行在jvm之上。下文为java官方对于JAVA虚拟机的描述信息

      The Java Virtual Machine is the cornerstone of the Java platform. It is the component of the technology responsible for its hardware- and operating systemindependence, the small size of its compiled code, and its ability to protect users from malicious programs. The Java Virtual Machine is an abstract computing machine. Like a real computing machine, it has an instruction set and manipulates various memory areas at run time. It is reasonably common to implement a programming language using a virtual machine;

    [图片上传失败...(image-8ffdc4-1649064930562)]

    idea程序示例

    下文将用idea中的java项目示例对Java 源程序、 Java 字节码、类实例分别进行示范:

    idea-java源文件

    通常来说,我们在idea中写的java程序都属于java源程序,idea会把文件的[.java]后缀隐藏掉。我们也可以使用任何文本编辑器编写生成[.java]文件。下图展示了一个典型的JAVA文件

    [图片上传失败...(image-ae9829-1649064930562)]

    idea-java字节码

    java文件是不能被java虚拟机所识别的,需要翻译为字节码文件才可以被java虚拟机接受。idea中可以直接点击build项目按钮实现源文件解释为字节码的过程(本质是通过java中的javac工具实现)。

    [图片上传失败...(image-6e74f3-1649064930562)]

    idea-类加载

    在idea中新建java的主类,并在主类中触发测试类的类加载流程(如new一个测试类),通过断点的方式可以查看到加载好的类的信息。

    [图片上传失败...(image-968aed-1649064930562)]

    类加载器介绍

    类加载器的作用

    由上文中的流程图可以看出,类加载器负责读取 Java 字节代码(.class 文件),并转换成 java.lang.Class 类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance() 方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

    虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

    类加载的时机

    java类加载使用动态类加载机制, 程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。JVM运行过程中,首先会加载初始类,然后再从初始类链接触发它相关的类的加载。

    [图片上传失败...(image-a64bff-1649064930562)]

    注意:图中的“引用”指触发类加载,一共有以下几种情况会触发类加载:

    1. 创建类的实例 访问类的静态变量(注意:当访问类的静态并且final修饰的变量时,不会触发类的初始化。),或者为静态变量赋值。

    2. 调用类的静态方法(注意:调用静态且final的成员方法时,会触发类的初始化!一定要和静态且final修饰的变量区分开!!)

    3. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。如:Class.forName(“bacejava.Langx”);

    4. 注意通过类名.class得到Class文件对象并不会触发类的加载。 初始化某个类的子类

    5. 直接使用java.exe命令来运行某个主类(java.exe运行,本质上就是调用main方法,所以必须要有main方法才行)。

      java官方对于类加载的描述:The Java Virtual Machine starts up by creating an initial class or interface using the bootstrap class loader or a user-defined class loader . The Java Virtual Machine then links the initial class or interface, initializes it, and invokes the public static method void main(String[]). The invocation of this method drives all further execution. Execution of the Java Virtual Machine instructions constituting the main method may cause linking (and consequently creation) of additional classes and interfaces, as well as invocation of additional methods.
      The initial class or interface is specified in an implementation-dependent manner. For example, the initial class or interface could be provided as a command line argument. Alternatively, the implementation of the Java Virtual Machine could itself provide an initial class that sets up a class loader which in turn loads an application. Other choices of the initial class or interface are possible so long as they are consistent with the specification given in the previous paragraph.

    类加载器的意义

    类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java Applet 需要从远程下载 Java 类文件到浏览器中并执行。现在类加载器在 Web 容器和 OSGi 中得到了广泛的使用。一般来说,Java 应用的开发人员不需要直接同类加载器进行交互。Java 虚拟机默认的行为就已经足够满足大多数情况的需求了。不过如果遇到了需要与类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就很容易花大量的时间去调试 ClassNotFoundException 和 NoClassDefFoundError 等异常。

    [图片上传失败...(image-950f1-1649064930562)]

    类加载的基本流程

    [图片上传失败...(image-9e5718-1649064930562)]

    1.加载:加载是通过类加载器(classLoader)完成的,它既可以是饿汉式加载类(预加载),也可以是懒加载lazy load(运行时加载)

    2.验证:确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 验证阶段是否严谨,直接决定了Java虚拟机是否能承受恶意代码的攻击。 从整体上看,验证阶段大致上会完成下面四个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。

    3.准备:准备阶段的主要任务是如下两点:为类变量分配内存;设置类变量初始值

    4.解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程

    5.初始化:初始化阶段即虚拟机执行类构造器()方法的过程。

    6.使用:正常使用类信息

    7.卸载:满足类卸载条件时(比较苛刻),jvm会从内存中卸载对应的类信息

    oracle官网对于类加载只粗略划分为了三个阶段,加载(包含上图中的加载、验证和准备)、链接和初始化,以下为java官方对于类加载的描述信息

    The Java Virtual Machine dynamically loads, links and initializes classes and interfaces. Loading is the process of finding the binary representation of a class or interface type with a particular name and creating a class or interface from that binary representation. Linking is the process of taking a class or interface and combining it into the run-time state of the Java Virtual Machine so that it can be executed. Initialization of a class or interface consists of executing the class or interface initialization method

    类加载器详细介绍

    生成类对象的三种方法

    [图片上传失败...(image-c94de2-1649064930562)]

    oracle官网把类加载器划分为两种类型:启动类加载器(BootStrapClassloader)和用户自定义类加载器,用户自定义加载器都继承自ClassLoad类。启动类加载器主要用于加载一些核心java库,如rt.jar。用户自定义加载器则可以加载各种来源的class文件。以下为java官方对于类加载器生成方式的描述信息。

    There are two kinds of class loaders: the bootstrap class loader supplied by the Java Virtual Machine, and user-defined class loaders.Every user-defined class loader is an instance of a subclass of the abstract class ClassLoader. Applications employ user-defined class loaders in order to extend the manner in which the Java Virtual Machine dynamically loads and thereby creates classes. User-defined class loaders can be used to create classes that originate from user-defined sources. For example, a class could be downloaded across a network, generated on the fly, or extracted from an encrypted file.

    数组本身也是一个对象,但是这个对象对应的类不通过类加载器加载,而是通过JVM生成。以下为java官方对于数组对象的描述信息

    Array classes do not have an external binary representation; they are created by the Java Virtual Machine rather than by a class loader.

    综上所述:类的生成方式一共有三种:

    1. 启动类加载器

    2. 用户自定义类加载器

    3. JVM生成数组对象

      The Java Virtual Machine uses one of three procedures to create class or interface C denoted by N:
      • If N denotes a nonarray class or an interface, one of the two following methods is used to load and thereby create C:
      – If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C .
      – If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C.
      • Otherwise N denotes an array class. An array class is created directly by the Java Virtual Machine, not by a class loader. However, the defining class loader of D is used in the process of creating array class C.

    启动类加载器

    启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
    双亲委派模型中,如果一个类加载器的父类加载器为null,则表示该类加载器的父类加载器是启动类加载器

    Bootstrap class loader. It is the virtual machine’s built-in class loader, typically represented as null, and does not have a parent.
    The following steps are used to load and thereby create the nonarray class or interface C denoted by N using the bootstrap class loader. First, the Java Virtual Machine determines whether the bootstrap class loader has already been recorded as an initiating loader of a class or interface denoted by N. If so, this class or interface is C, and no class creation is necessary. Otherwise, the Java Virtual Machine passes the argument N to an invocation of a method on the bootstrap class loader to search for a purported representation of C in a platform-dependent manner. Typically, a class or interface will be represented using a file in a hierarchical file system, and the name of the class or interface will be encoded in the pathname of the file. Note that there is no guarantee that a purported representation found is valid or is a representation of C. This phase of loading must detect the following error:
    • If no purported representation of C is found, loading throws an instance of
    ClassNotFoundException.

    用户自定义类加载器

    用户自定义类加载器可以分为两种类型:

    1. java库中的平台类加载器和应用程序类加载器等
    2. 用户自己写的类加载器,比如通过网络加载类等机制

    数组类加载器

    数组的Class类是由jvm生成的,但是数组类的Class.getClassLoader() 和数组元素的类加载器保持一致,如果数组的元素是基本类型,那么数组类的类加载器会为空。

    Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.

    用户自定义类加载器介绍

    基本类加载器ClassLoader

    ClassLoader 类是所有类加载器的基类。ClassLoader 类基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。除此之外, ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等。不过本节只讨论其加载类的功能。为了完成加载类的这个职责, ClassLoader 提供了一系列的方法,比较重要的方法如 java.lang.ClassLoader 类介绍 所示。关于这些方法的细节会在下面进行介绍。

    A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system. Every Class object contains a reference to the ClassLoader that defined it.

    ClassLoader默认支持并发加载,可以通过ClassLoader.registerAsParallelCapable方法主动取消并发加载操作,ClassLoader实现并发加载的原理如下:当ClassLoader加载类时,如果该类是第一次加载,则会以该类的完全限定名称作为Key,一个new Object()对象为Value,存入一个ConcurrentHashMap的中。并以该object对象为锁进行同步控制。同一时间如果有其它线程再次请求加载该类时,则取出map中的对象object,发现该对象已被占用,则阻塞。也就是说ClassLoader的并发加载通过一个ConcurrentHashMap实现的。

        // java加载类时获取锁的流程
        protected Object getClassLoadingLock(String className) {
            // 不开启并发加载的情况下,使用ClassLoader对象本身加锁
            Object lock = this;
            // 开启并发加载的情况下,从ConcurrentHashMap中获取需要加载的类对象进行加锁。
            if (parallelLockMap != null) {
                Object newLock = new Object();
                lock = parallelLockMap.putIfAbsent(className, newLock);
                if (lock == null) {
                    lock = newLock;
                }
            }
            return lock;
        }
    
    

    在某些不是严格遵循双亲委派模型的场景下,并发加载可能造成类加载器死锁:
    举例:A和B两个类使用不同的类加载器,A类的静态初始化代码块包含了B类的初始化操作(new B),B类的初始化代码块也包含了A类的初始化操作(new A);并发加载A和B的情况下,就有可能出现死锁的情况。而且加锁操作发生在JVM层面,无法用常用的java类加载工具查看到死锁情况。

    Class loaders that support concurrent loading of classes are known as parallel capable class loaders and are required to register themselves at their class initialization time by invoking the ClassLoader.registerAsParallelCapable method. Note that the ClassLoader class is registered as parallel capable by default. However, its subclasses still need to register themselves if they are parallel capable. In environments in which the delegation model is not strictly hierarchical, class loaders need to be parallel capable, otherwise class loading can lead to deadlocks because the loader lock is held for the duration of the class loading process (see loadClass methods).

    方法说明
    getParent()返回该类加载器的父类加载器(下文介绍的双亲委派模型会用到)。
    findClass(String name)查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例()。
    loadClass(String name)加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。和findClass的不同之处在于:loadClass添加了双亲委派和判断
    findLoadedClass(String name)查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。
    defineClass(String name, byte[] b, int off, int len)把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的
    resolveClass(Class<?> c)链接指定的 Java 类。

    真正完成类的加载工作是通过调用 defineClass 来实现的;而启动类的加载过程是通过调用 loadClass 来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类 com.example.Outer 引用了类 com.example.Inner ,则由类 com.example.Outer 的定义加载器负责启动类 com.example.Inner 的加载过程。方法 loadClass() 抛出的是 java.lang.ClassNotFoundException 异常;方法 defineClass() 抛出的是 java.lang.NoClassDefFoundError 异常。类加载器在成功加载某个类之后,会把得到的 java.lang.Class 类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass 方法不会被重复调用。

    权限管理类加载器SecureClassLoader

    在ClassLoader的基础上添加了代码源和安全管理器。

    This class extends ClassLoader with additional support for defining classes with an associated code source and permissions which are retrieved by the system policy by default.

    内置类加载器BuiltinClassLoader

    (建议看看java9 jigsaw模块化特性)BuiltinClassLoader加载器使用的委派模型与常规委派模型不同,该类加载器支持从模块加载类和资源。当请求加载一个类时,这个类加载器首先将类名映射到它的包名。如果有一个模块定义给包含这个包的BuiltinClassLoader,那么类加载器将直接委托给该类加载器。如果没有包含包的模块,那么它将搜索委托给父类装入器,如果在父类中找不到,则会搜索类路径。这种委托模型与通常的委托模型的主要区别在于,它允许平台类加载器委托给应用程序类加载器,这一点应该和java9 jigsaw模块化特性有关(破坏了双亲委派模型)。

    The delegation model used by this ClassLoader differs to the regular delegation model. When requested to load a class then this ClassLoader first maps the class name to its package name. If there is a module defined to a BuiltinClassLoader containing this package then the class loader delegates directly to that class loader. If there isn’t a module containing the package then it delegates the search to the parent class loader and if not found in the parent then it searches the class path. The main difference between this and the usual delegation model is that it allows the platform class loader to delegate to the application class loader, important with upgraded modules defined to the platform class loader.

    平台类加载器PlatformClassLoader

    从JDK9开始,扩展类加载器被重命名为平台类加载器(Platform ClassLoader),部分不需要 AllPermission 的 Java 基础模块,被降级到平台类加载器中,相应的权限也被更精细粒度地限制起来。它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。

    Platform class loader. All platform classes are visible to the platform class loader that can be used as the parent of a ClassLoader instance. Platform classes include Java SE platform APIs, their implementation classes and JDK-specific run-time classes that are defined by the platform class loader or its ancestors.
    To allow for upgrading/overriding of modules defined to the platform class loader, and where upgraded modules read modules defined to class loaders other than the platform class loader and its ancestors, then the platform class loader may have to delegate to other class loaders, the application class loader for example. In other words, classes in named modules defined to class loaders other than the platform class loader and its ancestors may be visible to the platform class loader.

    应用程序类加载器AppClassLoader

    系统类加载器负责将用户类路径(java -classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路下的类库加载到内存中。如果程序员没有自定义类加载器,默认调用该加载器。

    System class loader. It is also known as application class loader and is distinct from the platform class loader. The system class loader is typically used to define classes on the application class path, module path, and JDK-specific tools. The platform class loader is a parent or an ancestor of the system class loader that all platform classes are visible to it.

    用户自定义类加载器

    一般来说,用户自定义类加载器以ClassLoader为基类,重写其中的findClass,使findClass可以从用户指定的位置读取字节码.class文件。不建议用户重写loadClass方法,因为loadClass包含了双亲委派模型和锁等相关逻辑。
    用户自定义类加载器的父加载器可以在构造函数中指定,如果构造函数中没有指定,那么将会调用ClassLoader中的getSystemClassLoader()方法获取默认类加载器:

        @CallerSensitive
        public static ClassLoader getSystemClassLoader() {
            switch (VM.initLevel()) {
                case 0:
                case 1:
                case 2:
                    // the system class loader is the built-in app class loader during startup
                    return getBuiltinAppClassLoader();
                case 3:
                    String msg = "getSystemClassLoader cannot be called during the system class loader instantiation";
                    throw new IllegalStateException(msg);
                default:
                    // system fully initialized
                    asset VM.isBooted() && scl != null;
                    SecurityManager sm = System.getSecurityManager();
                    if (sm != null) {
                        checkClassLoaderPermission(scl, Reflection.getCallerClass());
                    }
                    return scl;
            }
        }
    
    

    Normally, the Java virtual machine loads classes from the local file system in a platform-dependent manner. However, some classes may not originate from a file; they may originate from other sources, such as the network, or they could be constructed by an application. The method defineClass converts an array of bytes into an instance of class Class. Instances of this newly defined class can be created using Class.newInstance.
    The methods and constructors of objects created by a class loader may reference other classes. To determine the class(es) referred to, the Java virtual machine invokes the loadClass method of the class loader that originally created the class.
    For example, an application could create a network class loader to download class files from a server. Sample code might look like:
    ClassLoader loader = new NetworkClassLoader(host, port);
    Object main = loader.loadClass(“Main”, true).newInstance();
    . . .
    The network class loader subclass must define the methods findClass and loadClassData to load a class from the network. Once it has downloaded the bytes that make up the class, it should use the method defineClass to create a class instance. A sample implementation is:
    class NetworkClassLoader extends ClassLoader {
    String host;
    int port;
    public Class findClass(String name) {
    byte[] b = loadClassData(name);
    return defineClass(name, b, 0, b.length);
    }
    private byte[] loadClassData(String name) {
    // load the class data from the connection
    . . .
    }
    }

    类加载器的特殊逻辑

    双亲委派模型

    而通常java中的类加载默认是采用双亲委派模型,即加载一个类时,首先判断自身define加载器有没有加载过此类,如果加载了直接获取class对象,如果没有查到,则交给加载器的父类加载器去重复上面过程。而java中加载器关系如下:

    The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will usually delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself.

    [图片上传失败...(image-ba21d0-1649064930561)]

    双亲委派的具体过程如下:

    1. 当一个类加载器接收到类加载任务时,先查缓存里有没有,如果没有,将任务委托给它的父加载器去执行。
    2. 父加载器也做同样的事情,一层一层往上委托,直到最顶层的启动类加载器为止。
    3. 如果启动类加载器没有找到所需加载的类,便将此加载任务退回给下一级类加载器去执行,而下一级的类加载器也做同样的事情。
    4. 如果最底层类加载器仍然没有找到所需要的class文件,则抛出异常。

    双亲委派模型的意义:确保类的全局唯一性
    如果你自己写的一个类与核心类库中的类重名,会发现这个类可以被正常编译,但永远无法被加载运行。因为你写的这个类不会被应用类加载器加载,而是被委托到顶层,被启动类加载器在核心类库中找到了。如果没有双亲委托机制来确保类的全局唯一性,谁都可以编写一个java.lang.Object类放在classpath下,那应用程序就乱套了。
    从安全的角度讲,通过双亲委托机制,Java虚拟机总是先从最可信的Java核心API查找类型,可以防止不可信的类假扮被信任的类对系统造成危害。

    上下文类加载器

    Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由**启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)**来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。

    简单来说:SPI接口类在java核心库中,本来应该由启动类加载器加载,但是因为SPI实现类机制,所以由上下文类加载器加载SPI接口类,使SPI接口类和实现类由同一个类加载器加载。

    JDBC SPI介绍

    只看文本理解有点困难,此处用JDBC案例进行分析:

    // 加载Class到AppClassLoader(系统类加载器),然后注册驱动类
    // Class.forName("com.mysql.jdbc.Driver").newInstance(); 
    String url = "jdbc:mysql://localhost:3306/testdb";    
    // 通过java库获取数据库连接
    Connection conn = java.sql.DriverManager.getConnection(url, "name", "password"); 
    
    

    以上为我们获取JDBC链接时常用的语句,实验发现将的Class.forName注释掉之后,程序但依然可以正常运行,这是为什么呢?这是因为从Java1.6开始自带的jdbc4.0版本已支持SPI服务加载机制,只要mysql的jar包在类路径中,就可以注册mysql驱动。
    那到底是在哪一步自动注册了mysql driver的呢?重点就在DriverManager.getConnection()中。我们都是知道调用类的静态方法会初始化该类,进而执行其静态代码块,DriverManager的静态代码块就是:

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    
    

    初始化方法loadInitialDrivers()的代码如下:

    private static void loadInitialDrivers() {
        String drivers;
        try {
            // 先读取系统属性
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // 通过SPI加载驱动类
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                    // Do nothing
                }
                return null;
            }
        });
        // 继续加载系统属性中的驱动类
        if (drivers == null || drivers.equals("")) {
            return;
        }
    
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                // 使用AppClassloader加载
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }
    
    

    从上面可以看出JDBC中的DriverManager的加载Driver的步骤顺序依次是:

    1. 通过SPI方式,读取 META-INF/services 下文件中的类名,使用TCCL加载;
    2. 通过System.getProperty(“jdbc.drivers”)获取设置,然后通过系统类加载器加载。
      下面详细分析SPI加载的那段代码。

    JDBC中的SPI介绍:

    SPI机制简介
    SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
    Java SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。

    按照上文中的SPI介绍,我们分析一下JDBC的SPI代码:

    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    Iterator<Driver> driversIterator = loadedDrivers.iterator();
    
    try{
        while(driversIterator.hasNext()) {
            driversIterator.next();
        }
    } catch(Throwable t) {
    // Do nothing
    }
    
    

    注意driversIterator.next()最终就是调用Class.forName(DriverName, false, loader)方法,也就是最开始我们注释掉的那一句代码。好,那句因SPI而省略的代码现在解释清楚了,那我们继续看给这个方法传的loader是怎么来的。

    因为这句Class.forName(DriverName, false, loader)代码所在的类在java.util.ServiceLoader类中,而ServiceLoader.class又加载在BootrapLoader中,因此传给 forName 的 loader 必然不能是BootrapLoader,复习双亲委派加载机制请看:java类加载器不完整分析 。这时候只能使用TCCL了,也就是说把自己加载不了的类加载到TCCL中(通过Thread.currentThread()获取,简直作弊啊!)。上面那篇文章末尾也讲到了TCCL默认使用当前执行的是代码所在应用的系统类加载器AppClassLoader。
    再看下看ServiceLoader.load(Class)的代码,的确如此:

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    
    

    ContextClassLoader默认存放了AppClassLoader的引用,由于它是在运行时被放在了线程中,所以不管当前程序处于何处(BootstrapClassLoader或是ExtClassLoader等),在任何需要的时候都可以用Thread.currentThread().getContextClassLoader()取出应用程序类加载器来完成需要的操作。
    到这儿差不多把SPI机制解释清楚了。直白一点说就是,我(JDK)提供了一种帮你(第三方实现者)加载服务(如数据库驱动、日志库)的便捷方式,只要你遵循约定(把类名写在/META-INF里),那当我启动时我会去扫描所有jar包里符合约定的类名,再调用forName加载,但我的ClassLoader是没法加载的,那就把它加载到当前执行线程的TCCL里,后续你想怎么操作(驱动实现类的static代码块)就是你的事了。
    好,刚才说的驱动实现类就是com.mysql.jdbc.Driver.Class,它的静态代码块里头又写了什么呢?是否又用到了TCCL呢?我们继续看下一个例子。
    com.mysql.jdbc.Driver加载后运行的静态代码块:

    static {
        try {
            // Driver已经加载到TCCL中了,此时可以直接实例化
            java.sql.DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
    
    

    registerDriver方法将driver实例注册到系统的java.sql.DriverManager类中,其实就是add到它的一个名为registeredDrivers的静态成员CopyOnWriteArrayList中 ,到此驱动注册基本完成.

    总结

    通过上面的案例分析,我们可以总结出线程上下文类加载器的适用场景:

    1. 当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。
    2. 当使用本类托管类加载,然而加载本类的ClassLoader未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载器代为托管。

    3.2.3 ServiceLoader

    ServiceLoader是用于加载SPI服务实现类的工具,可以处理0个、1个或者多个服务提供商的情况。

    A facility to load implementations of a service.
    A service is a well-known interface or class for which zero, one, or many service providers exist. A service provider (or just provider) is a class that implements or subclasses the well-known interface or class. A ServiceLoader is an object that locates and loads service providers deployed in the run time environment at a time of an application’s choosing. Application code refers only to the service, not to service providers, and is assumed to be capable of differentiating between multiple service providers as well as handling the possibility that no service providers are located.

    应用程序通过ServiceLoader的静态方法加载给定的服务,如果服务提供者在另外一个模块化的程序中,那么当前模块必须声明依赖服务提供方的服务实现类。ServiceLoader可以通过迭代器方法来定位和实例化服务的提供者,可以通过stream方法来获取一个可以检查和过滤的提供者流,而无需实例化它们。

    An application obtains a service loader for a given service by invoking one of the static load methods of ServiceLoader. If the application is a module, then its module declaration must have a uses directive that specifies the service; this helps to locate providers and ensure they will execute reliably. In addition, if the service is not in the application module, then the module declaration must have a requires directive that specifies the module which exports the service.
    A service loader can be used to locate and instantiate providers of the service by means of the iterator method. ServiceLoader also defines the stream method to obtain a stream of providers that can be inspected and filtered without instantiating them.
    As an example, suppose the service is com.example.CodecFactory, an interface that defines methods for producing encoders and decoders:

    下文举例说明:CodecFactory为一个SPI服务接口。定义了getEncoder和getDecoder两个借口。

     package com.example;
     public interface CodecFactory {
         Encoder (String encodingName);
         Decoder getDecoder(String encodingName);
     }
    
    

    下面的程序通过迭代器的方式获取CodecFactory的服务提供者:

    ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class);
        for (CodecFactory factory : loader) {
            Encoder enc = factory.getEncoder("PNG");
            if (enc != null)
                ... use enc to encode a PNG file
                break;
        }
    
    

    有些时候,我们可能有很多服务提供者,但是只有其中一些是有用的,这种情况下我们就需要对ServiceLoader获取到的服务实现类进行过滤,比如案例中,我们只需要PNG格式的CodecFactory,那么我们就可以对对应的服务实现类添加一个自定义的@PNG注解,然后通过下文过滤得到所需的服务提供者:

     ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class);
     Set<CodecFactory> pngFactories = loader
            .stream()                                              // Note a below
            .filter(p -> p.type().isAnnotationPresent(PNG.class))  // Note b
            .map(Provider::get)                                    // Note c
            .collect(Collectors.toSet());
    
    

    SPI服务设计的原则:
    服务应该服从单一职责原则,通常设计为接口或抽象类,不推荐设计为具体类(虽然也可以这样实现)。不同情况下设计的服务的方法不同,但是都应该遵守两个准则:

    1. 服务开放尽量多的方法,使服务提供方可以更自由的定制自己的服务实现方式。

    2. 服务应该表明自身是直接还是间接实现机制(如“代理”或“工厂”)。当某领域特定的对象实例化相对比较复杂时,服务提供者往往采用间接机制如,CodecFactory服务通过其名称表示其服务提供商是编解码器的工厂,而不是编解码器本身,因为生产某些编解码器可能很复杂。

      A service is a single type, usually an interface or abstract class. A concrete class can be used, but this is not recommended. The type may have any accessibility. The methods of a service are highly domain-specific, so this API specification cannot give concrete advice about their form or function. However, there are two general guidelines:

      1. A service should declare as many methods as needed to allow service providers to communicate their domain-specific properties and other quality-of-implementation factors. An application which obtains a service loader for the service may then invoke these methods on each instance of a service provider, in order to choose the best provider for the application.
      2. A service should express whether its service providers are intended to be direct implementations of the service or to be an indirection mechanism such as a “proxy” or a “factory”. Service providers tend to be indirection mechanisms when domain-specific objects are relatively expensive to instantiate; in this case, the service should be designed so that service providers are abstractions which create the “real” implementation on demand. For example, the CodecFactory service expresses through its name that its service providers are factories for codecs, rather than codecs themselves, because it may be expensive or complicated to produce certain codecs.

    有两种方式可以声明一个服务实现类:

    • 通过模块化的包声明:
      provides com.example.CodecFactory with com.example.impl.StandardCodecs;
      provides com.example.CodecFactory with com.example.impl.ExtendedCodecsFactory;
      -通过指定路径声明:META-INF/services
      如:META-INF/services/com.example.CodecFactory
      添加一行:com.example.impl.StandardCodecs # Standard codecs

    开发自己的类加载器

    虽然在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输 Java 类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来。下面将通过两个具体的实例来说明类加载器的开发。

    文件系统类加载器

    第一个类加载器用来加载存储在文件系统上的 Java 字节代码。完整的实现如清单 6 所示。

    public class FileSystemClassLoader extends ClassLoader {
    
        private String rootDir;
    
        public FileSystemClassLoader(String rootDir) {
            this.rootDir = rootDir;
        }
    
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] classData = getClassData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            }
            else {
                return defineClass(name, classData, 0, classData.length);
            }
        }
    
        private byte[] getClassData(String className) {
            String path = classNameToPath(className);
            try {
                InputStream ins = new FileInputStream(path);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int bytesNumRead = 0;
                while ((bytesNumRead = ins.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesNumRead);
                }
                return baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
        private String classNameToPath(String className) {
            return rootDir + File.separatorChar
                    + className.replace('.', File.separatorChar) + ".class";
        }
     }
    
    

    如清单 6 所示,类 FileSystemClassLoader 继承自类 java.lang.ClassLoader 。在 java.lang.ClassLoader 类介绍 中列出的 java.lang.ClassLoader 类的常用方法中,一般来说,自己开发的类加载器只需要覆写 findClass(String name) 方法即可。 java.lang.ClassLoader 类的方法 loadClass() 封装了前面提到的代理模式的实现。该方法会首先调用 findLoadedClass() 方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的 loadClass() 方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用 findClass() 方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写 loadClass() 方法,而是覆写 findClass() 方法。

    类 FileSystemClassLoader 的 findClass() 方法首先根据类的全名在硬盘上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例。

    网络类加载器

    下面将通过一个网络类加载器来说明如何通过类加载器来实现组件的动态更新。即基本的场景是:Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。当有版本更新的时候,只需要替换掉服务器上保存的文件即可。通过类加载器可以比较简单的实现这种需求。

    类 NetworkClassLoader 负责通过网络下载 Java 类字节代码并定义出 Java 类。它的实现与 FileSystemClassLoader 类似。在通过 NetworkClassLoader 加载了某个版本的类之后,一般有两种做法来使用它。第一种做法是使用 Java 反射 API。另外一种做法是使用接口。需要注意的是,并不能直接在客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。使用 Java 反射 API 可以直接调用 Java 类的方法。而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此接口的不同版本的类。在客户端通过相同的接口来使用这些实现类。网络类加载器的具体代码见 下载 。

    在介绍完如何开发自己的类加载器之后,下面说明类加载器和 Web 容器的关系。

    类加载器与 Web 容器

    对于运行在 Java EE™ 容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。

    绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:

    每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes 和 WEB-INF/lib 目录下面。
    多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。
    当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。

    在此我向大家推荐一个架构学习交流圈。交流学习伪鑫:539413949(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

    展开全文
  • 类加载器详解

    千次阅读 多人点赞 2021-08-28 00:13:28
    从概念上来讲, 自定义类加载器一般指的是程序中由开发人员自定义的一类,类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器 无论类加载器的类型如何...

    类加载器的分类

    JVM支持两种类型的类加载器,分别为引导类加载器(BootstrapClassLoader)和自定义类加载器(User-Defined ClassLoader)

    从概念上来讲, 自定义类加载器一般指的是程序中由开发人员自定义的一类,类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器

    无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个,如下所示:

    image-20210826073741259

    所以具体为引导类加载器(BootstrapClassLoader)和自定义类加载器(包括ExtensionClassLoader、Application ClassLoader、User Defined ClassLoader)

    Application ClassLoader也叫System ClassLoader

    image-20210826074203975

    可以看出ExtensionClassLoader、APPClassLoader都间接继承了ClassLoader。

    四类加载器之间的关系可以看做是阶级关系,分为上下级,并不是子父类的继承关系。

    Boot strapClassLoader由C/C++实现的,其他三类加载器是由Java实现的

    package com.dongguo.jvm02;
    
    /**
     * @author Dongguo
     * @date 2021/8/26 0026-9:07
     * @description:
     */
    public class ClassLoader1 {
        public static void main(String[] args) {
            //获取系统类加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
    
            //获取上层 扩展类加载器
            ClassLoader extClassLoader = systemClassLoader.getParent();
            System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1b6d3586
    
            //获取上层 引用类加载器  获取不到引用类加载器
            ClassLoader bootStrapClassLoader = extClassLoader.getParent();
            System.out.println(bootStrapClassLoader);//null
    
            //对于用户自定义类使用的加载器  默认使用系统类加载器加载
            ClassLoader classLoader = ClassLoader1.class.getClassLoader();
            System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
    
            //String类使用引导类加载器加载,Java核心类库都是使用引导类加载器加载
            ClassLoader StringClassLoader = String.class.getClassLoader();
            System.out.println(StringClassLoader);//null
        }
    }
    

    虚拟机自带的加载器

    启动类加载器(引导类加载器 BootStrap ClassLoader)

    这个类加载使用C/C++语言实现的,嵌套在JVM内部。

    它用来加载Java的核心库(JAVA HOME/jre/lib/rt.jar.resources.jar或sun.boot.class. path路径下的内容) ,用于提供JVM自身需要的类

    并不继承自java.lang.ClassLoader,没有父加载器。

    加载扩展类和应用程序类加载器,并指定为他们的父类加载器。

    出于安全考虑, Bootstrap启动类加载器只加载包名为java, javax.sun等开头的类

    扩展类加载器(Extension ClassLoader)

    Java语言编写,由sun.misc. Launcher$ExtclassLoader实现

    派生于ClassLoader类

    父类加载器为启动类加载器

    从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

    应用类加载器(系统类加载器 AppClassLoader)

    java语言编写,由sun.misc.Launcher$AppClassLoader实现

    派生于classLoader类

    父类加载器为扩展类加载器

    它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库

    该类加载是程序中默认的类加载器,一般来说, Java应用的类都是由它来完成加载

    通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器

    package com.dongguo.jvm02;
    
    import com.sun.net.ssl.internal.ssl.Provider;
    import sun.misc.Launcher;
    
    import java.net.URL;
    
    /**
     * @author Dongguo
     * @date 2021/8/26 0026-9:27
     * @description:
     */
    public class ClassLoader2 {
        public static void main(String[] args) {
            System.out.println("------启动类加载器------");
            //获取BootStrapClassLoader能够加载的API的路径
            URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
            for (URL url : urLs) {
                System.out.println(url.toExternalForm());
            }
            /*
            ------启动类加载器------
            file:/E:/software/java/jdk/jre/lib/resources.jar
            file:/E:/software/java/jdk/jre/lib/rt.jar
            file:/E:/software/java/jdk/jre/lib/sunrsasign.jar
            file:/E:/software/java/jdk/jre/lib/jsse.jar
            file:/E:/software/java/jdk/jre/lib/jce.jar
            file:/E:/software/java/jdk/jre/lib/charsets.jar
            file:/E:/software/java/jdk/jre/lib/jfr.jar
            file:/E:/software/java/jdk/jre/classes
             */
            //从上面的路径中随意选择一个类,查看使用的类加载器
            ClassLoader classLoader = Provider.class.getClassLoader();
            System.out.println(classLoader);//null    //说明使用的是BootStrapClassLoader加载
        }
    }
    

    image-20210826094240834

    package com.dongguo.jvm02;
    
    import sun.misc.Launcher;
    import sun.security.ec.ECKeyFactory;
    
    import java.net.URL;
    import java.security.interfaces.ECKey;
    import java.util.Properties;
    
    /**
     * @author Dongguo
     * @date 2021/8/26 0026-9:47
     * @description:
     */
    public class ClassLoader3 {
        public static void main(String[] args) {
            System.out.println("------扩展类加载器------");
            //获取SystemClassLoader能够加载的API的路径
            String extDirs = System.getProperty("java.ext.dirs");
            for (String path : extDirs.split(";")) {
                System.out.println(path);
            }
            /*
            ------扩展类加载器------
            E:\software\java\jdk\jre\lib\ext
            C:\WINDOWS\Sun\Java\lib\ext
            */
            //从上面的路径中随意选择一个类,查看使用的类加载器
            ClassLoader classLoader = ECKeyFactory.class.getClassLoader();
            System.out.println(classLoader);//sun.misc.Launcher$ExtClassLoader@1b6d3586 //扩展类加载器
        }
    }
    

    image-20210826095309409

    用户自定义的加载器

    用户自定义类加载器

    在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式

    为什么要自定义类加载器?

    隔离加载类

    修改类加载的方式

    扩展加载源

    防止源码泄漏

    用户自定义类加载器实现步骤:

    1,开发人员可以通过继承抽象类java . lang.ClassLoader类的方式,实现自己的类加载器,以满足一些特殊的需求

    2,在JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写loadClass ()方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadClass ()方法,而是建议把自定义的类加载逻辑写在findClass ()方法中

    3·在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLCIassLoader类,这样就可以避免自己去编写findclass ()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。

    自定义用户类加载器大致流程

    package com.dongguo.jvm02;
    
    import java.io.FileNotFoundException;
    
    /**
     * @author Dongguo
     * @date 2021/8/26 0026-10:01
     * @description: 自定义用户类加载器大致流程
     */
    public class CustomClassLoader extends ClassLoader {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
    
                byte[] result = getClassFromCustomPath(name);
                if (result == null) {
                    throw new FileNotFoundException();
                } else {
                    return defineClass(name, result, 0, result.length);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            throw new ClassNotFoundException();
        }
    
        private byte[] getClassFromCustomPath(String name) {
            //细节略
            //根据路径读取二进制流的方式将指定类读取到内存中形成字节数组
            //如果指定路径的字节码文件进行了加,则需要在此方法中进行解密操作。
            return null;
        }
        public static void main(String[] args) {
            CustomClassLoader customClassLoader = new CustomClassLoader();
            try {
                Class<?> clazz = Class.forName("your class path", true, customClassLoader);
                Object obj = clazz.newInstance();
                System.out.println(obj.getClass().getClassLoader());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    关于ClassLoader

    ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader (不包括启动类加载器)

    image-20210826101617539

    获取ClassLoader的途径

    image-20210826101756258

    package com.dongguo.jvm02;
    
    /**
     * @author Dongguo
     * @date 2021/8/26 0026-10:18
     * @description: 获取ClassLoader
     */
    public class ClassLoader4 {
        public static void main(String[] args) {
            try {
                //第一种方法
                ClassLoader classLoader1 = Class.forName("java.lang.String").getClassLoader();
                System.out.println(classLoader1);
                //第二种
                ClassLoader classLoader2 = Thread.currentThread().getContextClassLoader();
                System.out.println(classLoader2);
                //第三种
                ClassLoader classLoader3 = ClassLoader.getSystemClassLoader().getParent();
                System.out.println(classLoader3);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    运行结果
    null
    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$ExtClassLoader@1b6d3586
    

    类加载器的特点

    1.全盘负责:当一个类加载器加载一个类时,该类所依赖的其他类也会被这个类加载器加载到内存中。
    2.缓存机制:所有的Class对象都会被缓存,当程序需要使用某个Class时,类加载器先从缓存中查找,找不到,才从class文件中读取数据,转化成Class对象,存入缓存中。

    3.双亲委派机制

    双亲委派机制

    Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时, Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。

    工作原理

    image-20210826111921911

    1)如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行

    2)如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达项层的启动类加载器;

    3)如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

    自己创建一个java.lang.String类

    package java.lang;
    
    /**
     * @author Dongguo
     * @date 2021/8/26 0026-11:22
     * @description: 自定义的String类
     */
    public class String {
        static {
            System.out.println("执行自定义的String类的静态代码快");
        }
    }
    

    调用自定义的String

    package com.dongguo.jvm02;
    
    /**
     * @author Dongguo
     * @date 2021/8/26 0026-11:20
     * @description:
     */
    public class StringTest {
        public static void main(String[] args) {
            //如果使用自定义的String,会输出静态代码块的内容
            java.lang.String str = new java.lang.String();
            System.out.println("Hello JVM");
        }
    }
    运行结果:
    Hello JVM
    

    发现并没有输出自定义String类的静态代码块内容,说明并非使用的自定义的String类

    没有加载自定义的String类,使用的还是核心api中的String类

    尝试运行自定义的String类

    package java.lang;
    
    /**
     * @author Dongguo
     * @date 2021/8/26 0026-11:22
     * @description: 自定义的String类
     */
    public class String {
        static {
            System.out.println("执行自定义的String类的静态代码快");
        }
    
        public static void main(String[] args) {
            System.out.println("启动自定义的String类");
        }
    }
    

    image-20210826113457186

    类加载器加载自定义的类的加载请求时,由于双亲委派机制,加载请求到达引导类加载器,引导类加载器会去加载Java核心库自带的String类,String类并没有main方法,因此报错

    整个过程并没有加载自定义的java.lang.String类

    反向委托

    image-20210826114147678

    当类加载器加载第三方接口提供的jar时,

    比如SPI接口,SPI属于核心api,首先通过双亲委托机制将类加载请求委托到引导类加载器,引用类加载器加载rt.jar 的SPI核心类,SPI接口中使用有第三方的jar包jdbc.jar。第三方的jar需要使用应用类加载器去加载,这个步骤就叫反向委派,通过线程上下文类加载器(通过调用当前线程的getContextClassLoader()方法获得**Thread.currentThread().getContextClassLoader();**)线程上下文类加载器就是应用类加载器

    优势

    避免类的重复加载

    保护程序安全,防止核心AP1被随意篡改

    自定义类: java.lang.String

    自定义类: java.lang.Shkstart

    在自己创建的java.lang包下创建一个类Shkstart,运行

    package java.lang;
    
    /**
     * @author Dongguo
     * @date 2021/8/26 0026-11:55
     * @description:
     */
    public class Shkstart {
        public static void main(String[] args) {
            System.out.println("hello");
        }
    }
    

    image-20210826115701170

    提示禁止使用包名java.lang,通过双亲委派机制引用类加载器加载Shkstart,发现Shkstart是自定义的类,为防止核心AP1被随意篡改,发生报错。

    沙箱安全机制

    自定义string类,但是在加载自定义string类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件’(rt.jar包中java\lang\string.class) ,报错信息说没有main方法,就是因为加载的是rt.jar包中的string类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。

    其它

    判断两个Class对象是否为同一个类

    在JVM中表示两个Class对象是否为同一个类存在两个必要条件:

    ​ 类的完整类名必须一致,包括包名。

    ​ 加载这个类的Classoader (指ClassLoader实例对象)必须相同。

    换句话说,在JVM中,即使这两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的。

    对类加载器的引用

    JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候, JVM需要保证这两个类型的类加载器是相同的。

    类的主动使用和被动使用

    Java程序对类的使用方式分为:主动使用和被动使用。

    主动使用,又分为七种情况:

    ​ 创建类的实例

    ​ 访问某个类或接口的静态变量,或者对该静态变量赋值

    ​ 调用类的静态方法

    ​ 反射(比如: Class. forName (“com. atguigu.Test”))

    ​ 初始化一个类的子类

    Java虚拟机启动时被标明为启动类的类

    JDK 7开始提供的动态语言支持:java.lang.invoke. MethodHandle实例的解析结果REF getstatic, REF putstatic, REF invokestatic句柄对应的类没有初始化,则初始化

    除了以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化。

    展开全文
  • Android类加载器

    千次阅读 2022-02-10 17:24:35
    Android类加载器

    Java中类加载器 

    1.启动类加载器(Bootstrap ClassLoader):这个类加载器负责放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库,用户无法直接使用。

    2.扩展类加载器(Extension ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader实现。它负责<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。用户可以直接使用。

    3.应用程序类加载器(Application ClassLoader):这个类由sun.misc.Launcher$AppClassLoader实现。是ClassLoader中getSystemClassLoader()方法的返回值。它负责用户路径(ClassPath)所指定的类库。用户可以直接使用。如果用户没有自己定义类加载器,默认使用这个。

    4.自定义加载器:用户自己定义的类加载器。

    双亲委托模式

    某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,每一层的加载器都采用这种方式,直到委托给顶层的启动类加载器为止,如果超类无法加载该类,则会将类的加载内容退回给它的下一层。

    • 双亲委托模式:可以避免重复加载,能有效的确保一个类的全局唯一性
    • 如果不使用这种委托模式,那我们就可以随时使用自定义的类来动态替代一些核心的类,存在非常大的安全隐患,比如定义 java.lang.String 替代系统的String等操作。

    Android中类加载器

    Android中包含以下几种ClassLoader

    • BootClassLoader:用来加载Framework层的字节码文件
    • URLClassLoader:加载.jar文件和文件夹中的class,javaWeb等使用,谷歌不用
    • BaseDexClassLoader:PathClassaLoader、DexClassLoader父类
    • PathClassaLoader:加载已经安装到系统中的apk中的class文件
    • DexClassLoader:加载指定目录中的字节码文件(包括aar,apk,jar)

    BaseDexClassLoader为核心类,androidStudio中是看不到BaseDexClassLoader源码的,提供下源码查看地址:BaseDexClassLoader.java

    BaseDexClassLoader加载过程

    1.构造参数

    DexClassLoader是BaseDexClassLoader子类,下面分析参数。

    /**
     * 构造方法
     */
    public class BaseDexClassLoader extends ClassLoader {
        public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                     String librarySearchPath, ClassLoader parent) {
        }
    }
    
    /**
     * 构造方法
     */
    public class DexClassLoader extends BaseDexClassLoader {
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
            super(dexPath, null, librarySearchPath, parent);
        }
    }

    dexPath:apk/dex/jar文件路径

    optimizedDirectory:是odex将dexPath路径中dex优化后的输出路径,这个路径必须是手机内部路劲。此参数已弃用,自API级别26起不再生效。

    librarySearchPath:需要加载的C/C++库路径

    parent:父加载器(这个比较重要与Android加载class的机制有关)

    2.加载过程

    BaseDexClassLoader构造方法中传入的参数最终会传给DexPathList,BaseDexClassLoader.findClass最终调用DexPathList.findClass。

    /**
     * 构造方法
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String librarySearchPath, ClassLoader parent, boolean isTrusted) {
      super(parent);
      //DexPathList
      this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
    
      if (reporter != null) {
          reportClassLoaderChain();
      }
    }
    
    /**
     * BaseDexClassLoader.findClass
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
      List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
      //DexPathList.findClass
      Class c = pathList.findClass(name, suppressedExceptions);
      if (c == null) {
         ClassNotFoundException cnfe = new ClassNotFoundException(
                        "Didn't find class \"" + name + "\" on path: " + pathList);
         for (Throwable t : suppressedExceptions) {
             cnfe.addSuppressed(t);
         }
         throw cnfe;
      }
      return c;
    }
    • DexPathList.findClass最终调用element.findClass,Element[] dexElements是由makeDexElements方法进行赋值。
    • Thinker热修复方案就是将补丁dex插入到dexElements最前端,这样classLoader就会先加载补丁中修复了bug的class文件,由于classLoader双亲委托,再加载原先有bug的class文件时,发现已经有一摸一样的修复了bug的class被加载了,就会直接返回不会再去加载旧class文件,从而完成修复bug的目的。
    /**
      * List of dex/resource (class path) elements.
      * Should be called pathElements, but the Facebook app uses reflection
      * to modify 'dexElements' (http://b/7726934).
      */
    private Element[] dexElements;
    
    /**
      * DexPathList.findClass
      */
    public Class<?> findClass(String name, List<Throwable> suppressed) {
      //dexElements数组
      for (Element element : dexElements) {
           Class<?> clazz = element.findClass(name, definingContext, suppressed);
           if (clazz != null) {
               return clazz;
           }
      }
    
      if (dexElementsSuppressedExceptions != null) {
          suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
      }
      return null;
    }
    
    /**
      * dexElements初始化赋值
      */
    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
                List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
      /*
       * Open all files and load the (direct or contained) dex files up front.
       */
      for (File file : files) {
          if (file.isDirectory()) {
              // We support directories for looking up resources. Looking up resources in
              // directories is useful for running libcore tests.
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {
              String name = file.getName();
              DexFile dex = null;
              if (name.endsWith(DEX_SUFFIX)) {
                 // Raw dex file (not inside a zip/jar).
                 try {
                    dex = loadDexFile(file, optimizedDirectory, loader, elements);
                    if (dex != null) {
                        elements[elementsPos++] = new Element(dex, null);
                    }
                 } catch (IOException suppressed) {
                     System.logE("Unable to load dex file: " + file, suppressed);
                     suppressedExceptions.add(suppressed);
                 }
              } else {
                 try {
                    dex = loadDexFile(file, optimizedDirectory, loader, elements);
                 } catch (IOException suppressed) {
                      /*
                       * IOException might get thrown "legitimately" by the DexFile constructor if
                       * the zip file turns out to be resource-only (that is, no classes.dex file
                       * in it).
                       * Let dex == null and hang on to the exception to add to the tea-leaves for
                       * when findClass returns null.
                       */
                     suppressedExceptions.add(suppressed);
                 }
                 if (dex == null) {
                    elements[elementsPos++] = new Element(file);
                 } else {
                    elements[elementsPos++] = new Element(dex, file);
                 }
              }
              if (dex != null && isTrusted) {
                  dex.setTrusted();
              }
          } else {
             System.logW("ClassLoader referenced unknown path: " + file);
          }
      }    
      if (elementsPos != elements.length) {
              elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }
    展开全文
  • 类加载器详解(自己实现类加载器)

    千次阅读 2020-05-09 13:31:22
    目的:看懂4,并且自己实现一个类加载器 1.类加载器是什么东西 2.类加载器的种类 3.类加载器的机制 4.自己实现一个类加载器 在这里引用大佬的链接,这个是讲的很详细的,如果心急,不想细细研究那就直接看我总结的吧 ...
  • 类加载器

    千次阅读 2021-04-17 09:35:10
    类加载器 1.类加载器概述 ​ 类加载就是将磁盘上的class文件加载到内存中。虚拟机设计团队把类加载阶段的"通过一个类的全限定名获取描述此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己...
  • JVM - 类加载器

    万次阅读 2022-01-22 19:34:17
    JVM - 类加载器
  • JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。 这里的自定义加载器指的不是开发人员自己定义的类加载器,而是指的所有继承自ClassLoader...
  • 深入理解Java类加载器(ClassLoader)

    万次阅读 多人点赞 2017-06-26 09:34:08
    这便是类加载的5个过程,而类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例,在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器...
  • Java 类加载器详解

    千次阅读 2021-04-08 11:24:45
    Java 类加载器什么是类的加载类的加载过程类的加载过程(生命周期)加载.class文件的方式加载验证:确保被加载的类的正确性。准备:为类的静态变量分配内存,并将其初始化为默认值/零值或常量值。解析:把类中的符号...
  • 类加载机制 java类从被加载到JVM到卸载出JVM,整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸载(Unloading)七个阶段。...
  • JVM类加载器详解

    千次阅读 2021-01-11 23:12:03
    前言 ...类加载器是什么?有什么作用? 1、负责从文件或者网络加载class文件字节流 2、读取字节码中的信息,并在运行时存储到JVM的内存区域 3、检查并确保加载的class文件信息符合JVM字节码规范 ...
  • 上篇说到各类加载器再第二次得到...那么这三个类加载器的实例范围,或者首查找的范围是什么? 1. BootStrapClassLoader的实例范围在sun.boot.class.path中 2. ExtClassLoader的实例范围在java.ext.dirs中 3. AppClas.
  • Java类加载器及Android类加载器基础

    千次阅读 2017-03-07 11:29:24
    引子Android插件化与热更新技术日渐成熟,当你研究这些技术时会发现类加载器在其中占据重要地位。Java语言天生就有灵活性、动态性,支持运行期间动态组装程序,而这一切的基础就是类加载器。Java中的类加载器Java...
  • 谈到Java的类加载器,大家应该都不陌生。但最近在逛面经分享时看到这样一个问题:“手写一个String类能否被类加载器加载?”笔者自己试了下,发现这个问题几乎把类加载器的原理都考了一遍,不信咱们就来碰一碰它。
  • jvm之java类加载机制和类加载器(ClassLoader)的详解

    万次阅读 多人点赞 2018-08-13 15:05:46
    当程序主动使用某个类时,如果该类还未被加载到内存中,则...如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。 一、类加载过程 1.加载 加载指的是将类的class文件...
  • 类加载机制概念 Java虚拟机把描述类的class文件加载到内存,对其进行校验、转换解析、初始化等操作,最终得到可以被虚拟机直接使用的java类型,这就是虚拟机的加载机制。 加载 将class文件读入到内存中,并将其...
  • 线程上下文类加载器

    千次阅读 2019-08-27 17:56:00
    每个类都会使用自己的类加载器(加载自身的类加载器)来去加载所依赖的类,如果ClassA引用了ClassB,NameClassA的类加载器就会加载ClassB(前提是ClassB没有被加载) 线程上下文类加载器(Context Clas...
  • 什么是类加载器类加载器有哪些

    千次阅读 2022-01-21 19:04:19
    实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。 主要有一下四种类加载器: 1) 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。 2) 扩展类加载器(extensions...
  • 自定义类加载器: 1.加载指定目录下的.class文件 2.加载指定目录下的jar包
  • Java 自定义类加载器教程

    千次阅读 2021-08-03 23:33:09
    Java 自定义类加载器教程 除了在面试中遇到类的加载器的概率会高外,在实际的工作中很少接触。但是一个程序员想要成长为大牛就必须对一些 JVM 的底层设计有些了解。在此基础上我们阅读一些源码和框架会显得更轻松。 ...
  • 首先要厘清一个问题,为什么JAVA需要类加载。不明白这个问题,直接说加载机制就是空中楼阁。 JAVA程序员用编程工具编写的代码生成的都是拓展名为.java的文件,显然这个文件是不能直接被计算机识别并...加载需要加载器
  • jvm类加载_类加载器种类

    千次阅读 2021-02-28 16:28:06
    类加载器种类在jvm类加载过程中,有一步叫做加载的流程加载 : 根据类的全限定名获取到其定义的二进制字节流,并将其加载到内存中. 此时需要借助类加载器来帮助完成全限定名 : 包名 + 类名类加载器分为4类 :%JAVA_HOME%...
  • 目录前言一、双亲委派1.1 类加载器结构1.2 双亲委派二、使用步骤1.引入库2.读入数据总结 前言   在深入openjdk源码全面理解Java类加载器(上 – JVM源码篇) 我们分析了JVM是如何启动,并且初始化...
  • 类加载器ClassLoader详解

    千次阅读 2021-02-11 08:48:14
    ClassLoader是java的核心组件,所有的...一、加载分类 class文件的显示和隐式加载的方式指jvm加载class文件到内存的方式 1、显示加载 通过ClassLoader加载class对象 2、隐式加载 不直接调用ClassLoader的方法
  • 由源码深入Java类加载器(双亲委派模型)

    万次阅读 多人点赞 2021-07-13 11:35:28
    JVM类加载器 JVM主要有以下几种类加载器: 引导类加载器 主要加载JVM运行核心类库,位于JRE的lib目录下,如rt.jar中的类。 扩展类加载器 主要加载JVM中扩展类,位于JRE的ext目录下。 应用程序类加载器 主要负责加载...
  • Java class类文件和类加载器详解以及Java代码优化
  • 文章目录1、Java虚拟机的类加载机制概述2、Java虚拟机中的类加载器2.1、查看类加载器加载的路径2.1.1、查看启动类加载器2.1.2、查看扩展类加载器3、类加载器之间的关系3.1、每个类加载器都有一个父加载器3.2、父加载...
  • 类加载器的命名空间

    万次阅读 2019-08-21 12:21:11
    命名空间是类加载器中一个很重要的概念,对于只要学过java的人都知道java万物皆对象,在java中即使是一个“.class”文件,通过类加载器加载到虚拟机内存,那么在内存中会生成一个对应的Class对象 那么问题又来了,...
  • 深入理解JVM中的ClassLoader类加载器

    千次阅读 多人点赞 2021-05-08 19:06:48
    文章目录深入理解JVM中的ClassLoader类加载器JVM的体系结构图JVM的位置JVM结构图中的class files文件JVM结构图中的类加载器ClassLoader的解释类加载器ClassLoader的作用类加载器的种类java类的加载机制双亲委派机制...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,086,410
精华内容 434,564
关键字:

类加载器

友情链接: OpenCV入门教程.rar