精华内容
下载资源
问答
  • 类加载机制
    千次阅读
    2021-10-16 19:14:38

            在前面 Java虚拟机:对象创建过程与类加载机制、双亲委派模型 文章中,我们介绍了 JVM 的类加载机制以及双亲委派模型,双亲委派模型的类加载过程主要分为以下几个步骤:

    • (1)初始化 ClassLoader 时需要指定自己的 parent 是谁
    • (2)先检查类是否已经被加载过,如果类已经被加载了,直接返回
    • (3)若没有加载则调用父加载器 parent 的 loadClass() 方法进行加载
    • (4)若父加载器为空则默认使用启动类加载器 bootstrap ClassLoader 进行加载
    • (5)如果父类加载失败,抛出 ClassNotFoundException 异常后,再调用自己的 findClass() 方法进行加载。

            前面文章也提到,如果想要破坏这种机制,那么就自定义一个类加载器(继承自 ClassLoader),并重写其中的 loadClass() 方法,使其不进行双亲委派即可。最经典例子就是 Tomcat 容器的类加载机制了,它实现了自己的类加载器 WebApp ClassLoader,并且打破了双亲委派模型,在每个应用在部署后,都会创建一个唯一的类加载器。

    1、Tomcat 的类加载器结构图:

    (1)Common ClassLoader:加载 common.loader 属性下的 jar,一般是 CATALINA_HOME/lib 目录下,主要是 tomcat 使用以及应用通用的一些类

    (2)Catalina ClassLoader:加载 server.loader 属性下的 jar,默认未配置路径,返回其父加载器即 Common ClassLoader,主要是加载服务器内部可⻅类,这些类应⽤程序不能访问;

    (3)Shared Classloader:加载 share.loader 属性下的jar,默认未配置路径,返回其父加载器即 Common ClassLoader,主要是加载应⽤程序共享类,这些类对 Tomcat 自己不可见;

    只有指定了 tomcat/conf/catalina.properties 配置文件的 server.loader 和 share.loader 项后,才会真正建立 Catalina ClassLoader 和 Shared ClassLoader 的实例,否则在用到这两个类加载器的地方都会用 Common ClassLoader 的实例代替,而默认的配置文件中是没有设置这两个 loader 项的

    (4)WebApp ClassLoader:Tomcat 可以存在多个 WebApp ClassLoader 实例,每个应⽤程序都会有⼀个独⼀⽆⼆的 WebApp ClassLoader,⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。

    2、Tomcat 的类加载流程说明:

    当 Tomcat 使用 WebAppClassLoader 进行类加载时,具体过程如下:

    (1)先在本地 cache 缓存中查找该类是否已经加载过,看看 Tomcat 有没有加载过这个类

    (2)如果 Tomcat 没有加载过这个类,则从系统类加载器的 cache 缓存中查找是否加载过

    (3)如果没有,则使用 ExtClassLoader 类加载器类加载,重点来了,Tomcat 的 WebAppClassLoader 并没有先使用 AppClassLoader 来加载类,而是直接使用了 ExtClassLoader 来加载类。不过 ExtClassLoader 依然遵循双亲委派,它会使用 Bootstrap ClassLoader 来对类进行加载,保证了 Jre 里面的核心类不会被重复加载。

    比如在 Web 中加载一个 Object 类。WebAppClassLoader → ExtClassLoader → Bootstrap ClassLoader,这个加载链,就保证了 Object 不会被重复加载。

    (4)如果没有加载成功,WebAppClassLoader 就会调用自己的 findClass() 方法由自己来对类进行加载,先在 WEB-INF/classes 中加载,再从 WEB-INF/lib 中加载。

    (5)如果仍然未加载成功,WebAppclassLoader 会委派给 SharedClassLoader,SharedClassLoad 再委派给 CommonClassLoader,CommonClassLoader 委派给 AppClassLoader,直到最终委派给 BootstrapClassLoader,最后再一层一层地在自己目录下对类进行加载。

    (6)都没有加载成功的话,抛出异常。

    3、源码解析:

    (1)WebAppClassLoader 的 loadClass() 方法源码:

    WebappClassLoader 应用类加载器的 loadClass 在他的父类 WebappClassLoaderBase 中

    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            Class<?> clazz = null;
            //1. 先在本地cache查找该类是否已经加载过
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
            //2. 从系统类加载器的cache中查找是否加载过
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
            // 3. 尝试用ExtClassLoader类加载器类加载(ExtClassLoader 遵守双亲委派,ExtClassLoader 会使用 Bootstrap ClassLoader 对类进行加载)
            ClassLoader javaseLoader = getJavaseClassLoader();
            try {
                clazz = javaseLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
            // 4. 尝试在本地目录搜索class并加载
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
            // 5. 尝试用系统类加载器(AppClassLoader)来加载
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
         }
        //6. 上述过程都加载失败,抛出异常
        throw new ClassNotFoundException(name);
    }

    (2)WebAppClassLoader 的 findClass() 方法源码:

    public Class<?> findClass(String name) throws ClassNotFoundException {
        // Ask our superclass to locate this class, if possible
        // (throws ClassNotFoundException if it is not found)
        Class<?> clazz = null;
    
        // 先在自己的 Web 应用目录下查找 class
        clazz = findClassInternal(name);
    
        // 找不到 在交由父类来处理
        if ((clazz == null) && hasExternalRepositories) {  
            clazz = super.findClass(name);
        }
        if (clazz == null) {
             throw new ClassNotFoundException(name);
        }
        return clazz;
    }

    4、为什么tomcat要实现自己的类加载机制:

            WebAppClassLoader 加载类的时候,故意打破了JVM 双亲委派机制,绕开了 AppClassLoader,直接先使用 ExtClassLoader 来加载类。最主要原因是保证部署在同一个 Web 容器上的不同 Web 应用程序所使用的类库可以实现相互隔离,避免不同项目的相互影响。当然还有其他原因,如:

    (1)保证 Web 容器自身的安全不受部署的 Web 应用程序影响,所以 Tomcat 使用的类库要与部署的应用的类库相互独立

    (2)保证部分基础类不会被同时加载,有些类库 Tomcat 与部署的应用可以共享,比如说 servlet-api

    (3)保证部署在同一个 Web 容器的应用之间的类库可以共享,这听起来好像主要原因相互矛盾,但其实这很合理,类被类加载器加载到虚拟机后,会存放在方法区的永久代中,如果类库不能共享,虚拟机的方法区就会很容易出现过度膨胀的风险。比如这时候如果有大量的应用使用 spring 来管理,如果 spring 类库不能共享,那每个应用的 spring 类库都会被加载一次,将会是很大的资源浪费。

    小结:Tomcat 实际上只有 WebAppClassLoader 加载器中打破了双亲委派,其他类加载器还是遵循双亲委派的。 这样做最主要原因是保证同个 Web 容器中的不同 Web 应用程序所使用的类库相互独立,避免相互影响

    参考文章:https://mp.weixin.qq.com/s/OwWUDxHY4Th6decmJeMTgA

    更多相关内容
  • Java 类加载机制详解

    2020-12-22 19:37:55
     二、加载机制  在Java中,采用双亲委派机制来实现的加载。那什么是双亲委派机制?在Java Doc中有这样一段描述:  The ClassLoader class uses a delegation model to search for classes and resources
  • 主要介绍了Java 类加载机制详细介绍的相关资料,需要的朋友可以参考下
  • java类加载机制

    2018-10-31 13:40:11
    java 类加载机制,课程笔记。
  • 类加载机制

    千次阅读 2021-06-15 16:18:15
    1、概念 2、类加载步骤 3、类加载器 4、双亲委派 5、类的卸载 6、类中成员加载顺序

    目录

    1、概念

    2、类加载过程

    3、类加载器

    4、双亲委派

    5、类的卸载

    6、对象创建过程


    1、概念

    类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内(将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类),然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

     instanceKlass 描述 java 类,它的重要 field 有:

    • _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
    • _super 即父类
    • _fields 即成员变量
    • _methods 即方法
    • _constants 即常量池
    • _class_loader 即类加载器
    • _vtable 虚方法表
    • _itable 接口方法表

    注意:

    • instanceKlass 这样的【元数据】是存储在方法区(1.8 后的元空间内),但 _java_mirror(就是java.lang.Class对象) 是存储在堆中
    • static 变量在 JDK 7 之前存储于 instanceKlass 末尾(方法区),从 JDK 7 开始,存储于 _java_mirror 末尾(堆)

    2、类加载过程

    类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中准备、验证、解析3个部分统称为连接(Linking)。如图所示。

    加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。

    1、加载(Loding)

    1、通过一个类的全限定名(比如com.zxhx.test.HelloWorld.class)来获取其定义的二进制字节流。

    2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

    3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

    2、链接(Linking)

    链接包含三个部分:验证(Verification),准备(Preparation),解析(Resolution)。

    1. 验证(Verification):确保被加载类的正确性。这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
    2. 准备(Preparation):准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。(如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成;如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成)
    3. 解析(Resolution):把类中的符号引用转化为直接引用(比如说方法的符号引用,是有方法名和相关描述符组成,在解析阶段,JVM把符号引用替换成一个指针,这个指针就是直接引用,它指向该类的该方法在方法区中的内存位置)。解析包含:类或接口的解析、字段解析、类方法解析、接口方法解析。

    3、初始化(Initialization)

    在链接的准备阶段,类的静态变量已赋过一次初始值(默认值),而在初始化阶段,则是为静态变量赋指定值。例子:在准备阶段num1和num2的初始值为0,在初始化阶段,会将num1设置为10,num2设置为20。

    public class Test {
        private static int num1 = 10;
        private static int num2 = 20;
    
    }
    • 所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器里头,存放到一个特殊的方法中,这个方法就是<clinit>方法,即类/接口初始化方法,该方法只能在类加载的过程中由JVM调用;
    • 编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量;
    • 如果超类还没有被初始化,那么优先对超类初始化,但在<clinit>方法内部不会显示调用超类的<clinit>方法,由JVM负责保证一个类的<clinit>方法执行之前,它的超类<clinit>方法已经被执行。
    • JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。(所以可以利用静态内部类实现线程安全的单例模式)
    • 如果一个类没有声明任何的类变量,也没有静态代码块,那么可以没有类<clinit>方法;

     何时触发初始化?

    1. 为一个类型创建一个新的对象实例时(比如new、反射、序列化)
    2. 调用一个类型的静态方法时(即在字节码中执行invokestatic指令)
    3. 调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式
    4. 调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法)
    5. 初始化一个类的派生类时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外)
    6. JVM启动包含main方法的启动类时。

    3、类加载器

    1、启动类加载器(Bootstrap ClassLoader)

    1、用来加载Java的核心库,主要加载的是JVM自身所需要的类,使用C++实现,并非继承于java.lang.ClassLoader,是JVM的一部分

    2、负责加载JAVA_HOME\jre\lib目录中的,或者-Xbootclasspath参数指定的路径中的,且被虚拟机认可[注1]的类。开发者无法直接获取到其引用

    3、JVM是按文件名识别的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包放在lib目录下也没有作用,同时启动加载器只加载包名为java,javax,sun等开头的类。且java是特殊包名,开发者的包名不能以java开头,如果自定义了一个java.***包来让类加载器加载,那么就会抛出异常java.lang.SecurityException: Prohibited package name: java.***

    2、扩展类加载器(Extension ClassLoader)

    用来加载Java的扩展库。负责加载JAVA_HOME\jre\lib\ext目录中的,或通过系统变量java.ext.dirs指定路径中的类库。由java语言实现。开发者可以直接使用。

    3、应用程序类加载器(Application ClassLoader)

    负责加载用户路径(classpath)上的类库。开发者可以直接使用。可以通过ClassLoader.getSystemClassLoader()获得。一般情况下程序的默认类加载器就是该加载器。

    4、自定义类加载器

    要创建用户自己的类加载器,只需要继承java.lang.ClassLoader类,然后覆盖它的findClass(String name)方法即可,即指明如何获取类的字节码流。

    如果要符合双亲委派规范,则重写findClass方法(用户自定义类加载逻辑);要破坏的话,重写loadClass方法(双亲委派的具体逻辑实现)

    4、双亲委派

    当一个类加载器收到类加载请求的时候,它首先不会自己去加载这个类的信息,而是把该请求转发给父类加载器,依次向上。所以所有的类加载请求都会被传递到父类加载器中,只有当父类加载器中无法加载到所需的类,子类加载器才会自己尝试去加载该类。当当前类加载器和所有父类加载器都无法加载该类时,抛出ClassNotFindException异常。

    作用:
    1、防止重复加载同一个.class
    2、保证核心.class不被篡改,提高系统安全性

    5、类的卸载

    1、 有JVM自带的三种类加载器(根、扩展、系统(application))加载的类始终不会卸载。因为JVM始终引用这些类加载器,这些类加载器使用引用他们所加载的类,因此这些Class类对象始终是可到达的。
    2、由用户自定义类加载器加载的类,是可以被卸载的。

    JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载(unload):

    • 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例
    • 加载该类的ClassLoader已经被GC
    • 该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法 

    6、对象创建过程

     

     

     

     

     

     

     

     

     

    1. 首次创建对象时,类中的静态方法/静态字段首次被访问时,Java 解释器必须先查找类路径,以定位 .class 文件;
    2. 然后载入 .class(这将创建一个 Class 对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在 Class 对象首次加载的时候进行一次;
    3. 当用 new 方法创建对象时,首先再堆上为对象分配足够的存储空间;
    4. 这块存储空间会被清零,这就自动地将对象中的所有基本类型数据都设置成了缺省值(对数字来说就是 0,对 boolean 和 str 也相同),而引用则被设置成了 null;
    5. 执行所有出现于字段定义处的初始化动作(非静态对象的初始化);
    6. 执行构造器。

    clinit方法

    Java 在编译之后会在字节码文件中生成 clinit 方法,称之为类构造器。类构造器同实例构造器一样,也会将静态语句块,静态变量初始化,收敛到 clinit 方法中,收敛顺序为:

    1. 父类静态变量(静态语句块)初始化
    2. 子类静态变量(静态语句块)初始化

    若父类为接口,则不会调用父类的 clinit 方法。一个类可以没有 clinit 方法。

    init方法

    Java 在编译之后会在字节码文件中生成 init 方法,称之为实例构造器,该实例构造器会将语句块,变量初始化,调用父类的构造器等操作收敛到 init 方法中,收敛顺序为:

    1. 父类变量(语句块)初始化
    2. 父类构造函数
    3. 子类变量(语句块)初始化
    4. 子类构造函数

    收敛到 init 方法的意思是:将这些操作放入到 init 中去执行。

    clinit 方法是在类加载过程中执行的,而 init 是在对象实例化执行的,所以 clinit 一定比 init 先执行。当首次创建一个对象时,对象中成员初始化的顺序是:

    1. 父类静态变量和静态语句块(执行顺序由代码编写顺序确定)初始化
    2. 子类静态变量和静态语句块(执行顺序由代码编写顺序确定)初始化
    3. 父类实例变量和语句块(执行顺序由代码编写顺序确定)初始化
    4. 父类构造函数
    5. 子类实例变量和语句块(执行顺序由代码编写顺序确定)初始化
    6. 子类构造函数
    展开全文
  • 12.1 Java 类加载机制 前面我们给大家介绍过JVM 的功能提供运行时环境垃圾回收机制和提供中立的体 系结构在提供运行时环境中有个子功能是ClassLoader 类加载器它主要用于将主类 即包含了main 方法的类加载到JVM 的...
  • Java的类加载机制:加载,连接,初始化。JAVA类加载器: Bootstrap ClassLoader : 根类加载器, Extension ClassLoader: 扩展类加载器, System ClassLoader : 系统类加载器, Java反射
  • 面试题:请介绍 JVM 类加载机制

    千次阅读 2022-04-10 11:27:38
    JVM 类加载机制Java 代码执行流程类的生命周期加载验证准备解析初始化clinit 和 init 方法类加载的时机被动引用类加载器双亲委派机制 我们在前面分析JVM架构解析的时候,简单介绍了 Java 类加载机制,本文带大家深入...

    我们在前面分析JVM架构解析的时候,简单介绍了 Java 类加载机制,本文带大家深入分析一下。

    Java 代码执行流程

    在这里插入图片描述
    根据上图所示,Java 代码执行步骤如下:

    • 步骤 1: 获取 Java 源代码;
    • 步骤 2: 编译器把 java 文件转变成 class 文件。编译过程大致可以分为 1 个准备过程和 3 个处理过程:
      • 准备过程:初始化插入式注解处理器
      • 解析与填充符号表过程,包括:词法、语法分析等
      • 注解处理过程
      • 语义分析与字节码生成过程
    • 步骤 3: 若要运行此 Java 程序,JVM 中会有一个叫类加载器(class loader)的内置程序把字节码从硬盘载入 JVM;
    • 步骤 4: JVM 中还有一个叫字节码校验器(bytecode verifier)的内置程序检测是否存在运行期错误(例如栈溢出)。若通过检测,字节码校验器就会将字节码传递给解释器(interpreter);
    • 步骤 5: 解释器会对字节码进行逐行翻译,将其翻译成当前所在系统可以理解的机器码(machine code);
    • 步骤 6:将机器码交给操作系统,操作系统会以 main 方法作为入口开始执行程序。至此,一个Java程序就这样运行起来了。

    下面我们就开始重点介绍 Java 的类加载机制。

    类的生命周期

    一个类在 JVM 里的生命周期有 7 个阶段,分别是加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)。

    在这里插入图片描述
    其中前五个部分(加载,验证,准备,解析,初始化)统称为类加载,下面我们就详细介绍一下这五个过程。

    加载

    加载(Loading)阶段是整个「类加载」(Class Loading)过程中的一个阶段,各位不要混淆。

    加载的主要作用是将外部的 .class 文件,加载到 Java 的方法区内。

    这个阶段 JVM 需要完成以下三个操作:

    1. 通过一个类的全限定名(包名 + 类名)来获取定义此类的二进制字节流;
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
    3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

    加载 class 文件有以下几种方式:

    1. ZIP 压缩包中读取,这很常见,成为日后 jarwar格式的基础;
    2. 通过网络获取,典型场景:Web Applet
    3. 运行时计算生成,使用最多的是:动态代理技术;
    4. 由其他文件生成,典型场景是 JSP 应用;
    5. 从加密文件中获取,典型的防 Class 文件被反编译的保护措施。

    验证

    验证是连接阶段的第一步,这一阶段的目的是确保 class 文件里的字节流信息符合当前虚拟机的要求,不会危害虚拟机的安全。

    从代码量和耗费的执行性能的角度上讲,验证阶段的工作量在虚拟机的类加载过程中占了相当大的比重。

    如果输入的字节流如不符合 Class 文件格式的约束,将抛出一个 java.lang.VerifyError 异常或其子类异常。

    从整体上看,验证阶段大致会完成如下四个阶段的检验动作:文件格式验证元数据验证字节码验证符号引用验证

    1. 文件格式验证:主要验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。比如:
      • 是否以魔数 0xCAFEBABE 开头;
      • 主、次版本号是否在当前 Java 虚拟机接受范围之内;
      • 常量池的常量中是否有不被支持的常量类型(检查常量 tag 标志)
    2. 元数据验证:是对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java语言规范》的要求。比如:
      • 这个类的父类是否继承了不允许被继承的类(被 final 修饰的类)
      • 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
      • 类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的 final 字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等)
    3. 字节码验证:这一阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的;
    4. 符号引用验证:最后一个阶段的校验行为发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在 连接的第三阶段——解析阶段中发生。

    准备

    准备阶段是为定义的类变量(即静态变量,被 static 修饰的变量)分配内存并初始化为标准默认值(比如 null 或者0 值)。

    从概念上讲,这些变量所使用的内存都应当在方法区中进行分配,但必须注意到方法区 本身是一个逻辑上的区域,在JDK 7及之前,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;而在JDK 8及之后,类变量则会随着Class对象一起存放在Java堆中,这时候“类变量在方法区”就完全是一种对逻辑概念的表述了。

    准备阶段,有两个关键点需要注意:

    • 首先是这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中;
    • 然后就是初始化为标准默认值

    假设一个类变量的定义如下:

    public static int value = 123;
    

    准备阶段的值会被初始化为 0,后面在类初始化阶段才会执行赋值为 123;但是下面如果使用 final 修饰静态常量,某些 JVM 的行为就不一样了。

    假设上面类变量 value 的定义修改为:

    public static final int value = 123;
    

    编译时 Javac 将会为 value 生成 ConstantValue 属性,在准备阶段虚拟机就会根据 ConstantValue 的设置将 value 赋值为 123。

    如果类字段的字段属性表中存在 ConstantValue 属性,那在准备阶段变量值就会被初始化为 ConstantValue 属性所指定的初始值。<<深入理解Java虚拟机>>

    解析

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

    介绍解析之前,我们简单了解一下符号引用直接引用

    • 符号引用是一种定义,可以是任何字面上的含义,而直接引用就是直接指向目标的指针、相对偏移量。
    • 直接引用的对象都存在于内存中,你可以把通讯录里的女友手机号码,类比为符号引用,把面对面和你吃饭的人,类比为直接引用。

    简单的来说就是我们编写的代码中,当一个变量引用某个对象的时候,这个引用在 .class 文件中是以符号引用来存储的(相当于做了一个索引记录)。

    在解析阶段就需要将其解析并链接为直接引用(相当于指向实际对象)。如果有了直接引用,那引用的目标必定在堆中存在。

    加载一个 class 时, 需要加载所有的 super 类和 super 接口。

    个人理解,在编译的时候一个每个 java 类都会被编译成一个 class 文件,但在编译的时候,被引用的类、方法或者变量还没有被加载到内存中,虚拟机并不知道所引用类的地址,所以就用符号(比如com.example.Test)引用来代替,而在这个解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。

    解析阶段负责把整个类激活,串成一个可以找到彼此的网,过程不可谓不重要。那这个阶段都做了哪些工作呢?

    主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行。

    我们来看几个经常发生的异常,就与这个阶段有关。

    • java.lang.NoSuchFieldError 根据继承关系从下往上,找不到相关字段时的报错。
    • java.lang.IllegalAccessError 字段或者方法,访问权限不具备时的错误。
    • java.lang.NoSuchMethodError 找不到相关方法时的错误。

    初始化

    类的初始化阶段是类加载过程的最后一个步骤。初始化阶段就是执行类构造器 <clinit>() 方法的过程。

    <clinit>()方法是由编译器自动收集类中的所有类变量赋值动作和静态语句块(static{})中的语句合并产生的,收集顺序是按在源文件中的出现顺序决定的。静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但不能访问。

    下面我们看一段代码,这是一道面试题,大家可以思考一下,下面的代码,会输出什么?

    public class A {
         static int a = 0 ;
         static {
             a = 1;
             b = 1;
         }
         static int b = 0;
         public static void main(String[] args) {
             System.out.println(a);
             System.out.println(b);
         }
     }
    

    运行结果:

    1
    0
    

    a 和 b 唯一的区别就是它们的 static 代码块的位置。

    这就引出一个规则:static 语句块,只能访问到定义在 static 语句块之前的变量。

    所以下面的代码是无法通过编译的。

    static {
       b = b + 1;
    }
    static int b = 0;
    

    规则二:Java 虚拟机会保证在子类的 <clinit>() 方法执行前,父类的 <clinit>() 方法已经执行完毕。

    因此在 Java 虚拟机中第一个被执行的 <clinit>()方法的类型肯定是 java.lang.Object。正因如此,下面的代码字段 B 的值将会是 2 而不是 1。

    public class Parent {
        public static int A = 1;
        static {
            A = 2;
        }
        static class Sub extends Parent{
            public static int B = A;
        }
        public static void main(String[] args) {
            System.out.println(Sub.B);
        }
    }
    

    clinit 和 init 方法

    <clinit> 是类(Class)初始化执行的方法,<init> 是对象初始化执行的方法(构造函数)。

    看下面一段代码,主要是为了让大家弄明白类的初始化对象的初始化之间的差别。

    public class A {
         static {
             System.out.println("1");
         }
         public A(){
             System.out.println("2");
             }
         }
         public class B extends A {
             static{
             System.out.println("a");
         }
         public B(){
             System.out.println("b");
         }
         public static void main(String[] args){
             A ab = new B();
             ab = new B();
         }
     }
    

    打印结果:

    1
    a
    2
    b
    2
    b
    

    其中 static 字段和 static 代码块,是属于类的,在类的加载的初始化阶段就已经被执行。类信息会被存放在方法区,在同一个类加载器下,这些信息有一份就够了,所以上面的 static 代码块只会执行一次,它对应的是 <clinit> 方法。

    而对象初始化就不一样了。

    通常,我们在 new 一个新对象的时候,都会调用它的构造方法,就是 <init>,用来初始化对象的属性。每次新建对象的时候,都会执行。

    在这里插入图片描述所以,上面代码的 static 代码块只会执行一次,对象的构造方法执行两次。再加上继承关系的先后原则,不难分析出正确结果。

    类加载的时机

    关于在什么情况下需要开始类加载过程的第一个阶段「加载」,JVM 规范中并没有进行强制约束,但是对于初始化阶段,JVM 规范规定了只有六种情况必须立即对类进行「初始化」(加载、验证、准备自然需要在此之前开始):

    1. 遇到 newgetstaticputstaticinvokestatic 这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型 Java 代码场景有:
      • 使用 new 关键字实例化对象的时候;
      • 读取或设置一个类型的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;
      • 调用一个类型的静态方法的时候;
    2. 使用 java.lang.reflect 包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化;
    3. 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化;
    4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
    5. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类;
    6. 当一个接口中定义了 JDK 8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

    这六种会触发类型进行初始化的场景,JVM 规范中使用了一个非常强烈的限定语:有且只有,这六种场景中的行为称为对一个类型进行主动引用

    除此之外,所有引用类型的方式都不会触发初始化,称为被动引用。

    被动引用

    我们举三种被动引用的例子:

    实例1:通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。

    /**
     * 通过子类引用父类的静态字段,不会导致子类初始化
     **/
    public class SuperClass {
        static {
            System.out.println("SuperClass init!");
        }
    
        public static int value = 123;
    }
    
    public class SubClass extends SuperClass {
        static {
            System.out.println("SubClass init!");
        }
    }
    
    public class NotInitialization {
        public static void main(String[] args) {
            System.out.println(SubClass.value);
        }
    }
    

    运行结果:

    SuperClass init!
    123
    

    上述代码运行之后,只会输出SuperClass init!,而不会输出SubClass init!

    对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。

    实例2:通过数组定义来引用类,不会触发此类的初始化

    public class NotInitialization {
        public static void main(String[] args) {
            SuperClass[] sca = new SuperClass[10];
        }
    }
    

    运行之后发现没有输出 SuperClass init!,说明并没有触发类 SuperClass 的初始化阶段。

    实例3:常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化

    public class ConstClass {
        static {
            System.out.println("ConstClass init!");
        }
    
        public static final String HELLOWORLD = "hello world";
    }
    
    public class NotInitialization {
        public static void main(String[] args) {
            System.out.println(ConstClass.HELLOWORLD);
        }
    }
    

    运行结果:

    hello world
    

    上述代码运行之后,也没有输出ConstClass init!,这是因为虽然在Java源码中确实引用了 ConstClass 类的常量 HELLOWORLD,但其实在编译阶段通过常量传播优化,已经将此常量的值 hello world 直接存储在 NotInitialization 类的常量池中,以后 NotInitialization 对常量 ConstClass.HELLOWORLD 的引用,实际都被转化为NotInitialization 类对自身常量池的引用了。

    也就是说,实际上 NotInitialization 的 Class文件之中并没有 ConstClass 类的符号引用入口,这两个类在编译成 Class 文件后就已不存在任何联系了。

    类加载器

    类加载过程可以描述为:通过一个类的全限定名来获取描述该类的二进制字节流。实现这个动作的代码被称为类加载器(Class Loader)。

    系统自带的类加载器分为三种:

    • 启动类加载器(BootstrapClassLoader)
    • 扩展类加载器(ExtClassLoader)
    • 应用类加载器(AppClassLoader)

    启动类加载器:它用来加载 Java 的核心类(存放<JAVA_HOME>\lib目录,或者被-Xbootclasspath 参数所指定的路径),是用原生 C++ 代码来实现的,是虚拟机自身的一部分。

    我们在代码层面无法直接获取到启动类加载器的引用,所以不允许直接操作它,如果获取它的对象,将会返回 null。

    扩展类加载器:以 Java 代码的形式实现的。负责加载 <JAVA_HOME>\lib\ext 目录中,或者被 java.ext.dirs 系统变量所指定的路径中所有的类库。

    应用程序类加载器:它负责在 JVM 启动时加载来自 Java 命令的 -classpath 或者 -cp 选项、java.class.path 系统属性指定的 jar 包和类路径。

    在应用程序代码里可以通过 ClassLoader 的静态方法 getSystemClassLoader() 来获取应用类加载器。

    如果没有特别指定,即在没有使用自定义类加载器情况下,用户自定义的类都由此加载器加载。

    此外还可以自定义类加载器

    如果用户自定义了类加载器,则自定义类加载器都以应用类加载器作为父加载器。应用类加载器的父类加载器为扩展类加载器。这些类加载器是有层次关系的,启动加载器又叫根加载器,是扩展加载器的父加载器,但是直接从 ExClassLoader 里拿不到它的引用,同样会返回 null。

    在这里插入图片描述

    上图展示的各种类加载器之间的层次关系被称为类加载器的双亲委派模型(Parents Delegation Model)

    双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载 器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。

    双亲委派机制

    当一个自定义类加载器需要加载一个类,比如 java.lang.String,它很懒,不会一上来就直接试图加载它,而是先委托自己的父加载器去加载,父加载器如果发现自己还有父加载器,会一直往前找,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

    如果启动类加载器已经加载了某个类比如 java.lang.String,所有的子加载器都不需要自己加载了。

    双亲委派模型的实现:

        protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            // 首先,检查请求的类是否已经被加载过了 
            Class c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父类加载器抛出ClassNotFoundException 
                    // 说明父类加载器无法完成加载请求 
                }
                if (c == null) {
                    // 在父类加载器无法加载时 
                    // 再调用本身的findClass方法来进行类加载 
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    

    这个模型的好处在于 Java 类有了一种优先级的层次划分关系。比如 Object 类,这个毫无疑问应该交给最上层的加载器进行加载,即使是你覆盖了它,最终也是由系统默认的加载器进行加载的。

    如果没有双亲委派模型,就会出现很多个不同的 Object 类,应用程序会一片混乱。

    如果你还想看更多优质原创文章,欢迎关注我的公众号「ShawnBlog」。

    展开全文
  • JVM类加载机制

    千次阅读 2022-01-21 10:05:49
    当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、链接、初始化3个步骤来对该类进行初始化...类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供

    当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、链接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化
    在这里插入图片描述

    一、类加载过程

    1.加载

    加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

    类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

    通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。

    • 从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。
    • 从JAR包加载class文件,这种方式也是很常见的,JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
    • 通过网络加载class文件。
    • 把一个Java源文件动态编译,并执行加载。

    类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

    总结:
    ①通过一个类的全限定名(或其他方式)来获取定义此类的二进制字节流。
    ②将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    ③在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

    2.链接

    当类被加载之后,JVM内存为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。

    1)验证验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。Java是相对C++语言是安全的语言,例如它有C++不具有的数组越界的检查。这本身就是对自身安全的一种保护。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

    文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。
    
    元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。
    
    字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。
    
    符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。
    

    2)准备类准备阶段负责为类的静态变量分配内存,并设置默认初始值。
    在准备阶段,进行分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

    3)解析将类的二进制数据中的符号引用替换成直接引用。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。

    3.初始化

    初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

    二、类加载时机

    1. 创建类的实例,也就是new一个对象
    2. 访问某个类或接口的静态变量,或者对该静态变量赋值
    3. 调用类的静态方法
    4. 反射(Class.forName(“com.lyj.load”))
    5. 初始化一个类的子类(会首先初始化子类的父类)
    6. JVM启动时标明的启动类,即文件名和类名相同的那个类

    除此之外,下面情形需要特别指出:

    对于一个final类型的静态变量,如果该变量的值在编译时就可以确定下来,那么这个变量相当于“宏变量”。Java编译器会在编译时直接把这个变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化。反之,如果final类型的静态Field的值不能在编译时确定下来,则必须等到运行时才可以确定该变量的值,如果通过该类来访问它的静态变量,则会导致该类被初始化。

    三、类加载器

    类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。例如,如果在pg的包中有一个名为Person的类,被类加载器ClassLoader的实例kl负责加载,则该Person类对应的Class对象在JVM中表示为(Person.pg.kl)。这意味着两个类加载器加载的同名类:(Person.pg.kl)和(Person.pg.kl2)是不同的、它们所加载的类也是完全不同、互不兼容的。

    JVM预定义有三种类加载器,当一个 JVM启动的时候,Java开始使用如下三种类加载器:

    1)根类加载器(bootstrap class loader):也叫引导类加载器,它用来加载 Java 的核心类,是用C++原生代码来实现的,并不继承自 java.lang.ClassLoader,这个类加载器负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
    启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可。

    下面程序可以获得根类加载器所加载的核心类库,并会看到本机安装的Java环境变量指定的jdk中提供的核心jar包路径:

    public class ClassLoaderTest {
     
    	public static void main(String[] args) {
    		
    		URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
    		for(URL url : urls){
    			System.out.println(url.toExternalForm());
    		}
    	}
    }
    

    运行结果:
    在这里插入图片描述
    2)扩展类加载器(extensions class loader):它负责加载JRE的扩展目录,<JAVA_HOME>/lib/ext目录或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。
    根据“扩展类加载器”这个名称,就可以推断处这是一种Java系统类库的扩展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能,在JDK9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。
    由于扩展类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。

    3)系统类加载器(Application Class Loader):也称为应用程序类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。(责加载用户类路径ClassPath上所有的类库。)程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。
    开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器。,如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader。

    自定义类加载器:除了上面3种JVM提供的类加载器之外,程序员还可以自己定义类加载器。(简单了解)
    在这里插入图片描述自定义加载器实现步骤:
    在这里插入图片描述

    四、类加载机制:

    1.JVM的类加载机制主要有如下3种。

    • 全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
    • 双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。
    • 缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

    2.双亲委派机制
    在这里插入图片描述
    双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,当⽗类加载器为null时,会使⽤启动类加载器 BootstrapClassLoader 作为⽗类加载器。这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。
    在这里插入图片描述
    双亲委派机制的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关系可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改

    如何打破双亲委派模型
    打破双亲委派模型
    方法1:自定义类加载器,重写ClassLoader类中的loadClass()方法;
    方法2:使用线程上下文类加载器 Thread Context ClassLoader

    如果不想打破双亲委派模型,就重写ClassLoader类中的findClass()方法即可。

    展开全文
  • java类加载机制.xmind

    2020-06-09 12:33:51
    该文件是JVM中关于类加载机制的知识整理的思维导图,包括类加载机制概述、类加载的生命周期、加载时机、加载过程、类加载、类的初始化和实例化等几个大方面进行了讲解,其中类加载中还对JVM三种预定义类加载器进行了...
  • jvm 类加载机制 双亲委派模型 文档 jvm 类加载机制 双亲委派模型 文档
  • 2、类加载机制 3、类执行机制 我们这里主要介绍编译和类加载这两种机制。 一、源码编译 代码编译由JAVA源码编译器来完成。主要是将源码编译成字节码文件(class文件)。字节码文件格式主要分为两部分:常量池和方法...
  • 类加载机制(整个过程详解)

    千次阅读 2022-02-14 18:04:02
    类加载机制是在我们的真个java的运行阶段中的其中一个阶段。 二:什么是快乐星球(类加载机制) 我们编写的 Java 文件都是以.java 为后缀的文件,编译器会将我们编写的.java 的文件编译成.class 文件,简单来说类加载...
  • 类加载机制详解

    千次阅读 2020-02-11 20:07:41
    以下是《深入理解Java虚拟机第二版》对类加载机制的定义原文: 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加....
  • Android热修复技术 --- 类加载机制

    千次阅读 2022-03-27 13:34:30
    热修复前言 类加载机制
  • 主要介绍了Java类加载器和类加载机制,结合实例形式分析了java类加载器与类加载机制原理、实现方法及相关操作技巧,需要的朋友可以参考下
  • Javaweb安全——Java类加载机制

    千次阅读 2022-04-01 11:54:19
    ClassLoader(类加载机制) Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言。Java程序在运行前需要先编译成class文件,Java类初始化的时候使用类加载器创建。 加载器分为两种:由 Java 虚拟机提供的引导...
  • 类加载机制及反射

    2018-01-30 11:14:23
    类加载机制及反射 类加载机制及反射
  • java 注解 反射 字节码 类加载 包括pdf资料以及全部源码文件,具有非常大参考价值和学习意义
  • Flink进阶系列--类加载机制

    千次阅读 2022-04-10 16:36:15
    要讲解 Flink 的类加载机制,首先你得对 JDK 的类加载机制有所了解。 推荐阅读我之前写的1篇博客: 基于源码深入了解Java的类加载机制(JDK8和JDK11双版本) 接着看一下 FLink 的类加载器继承结构: ...
  • Java类加载机制

    万次阅读 多人点赞 2019-03-02 10:21:04
    Java类加载机制 类加载的时机 隐式加载 new 创建类的实例, 显式加载:loaderClass,forName等 访问类的静态变量,或者为静态变量赋值 调用类的静态方法 使用反射方式创建某个类或者接口对象的Class对象。 初始化某个...
  • Java类加载机制浅析

    2021-01-20 02:52:15
    所谓类加载机制就是 虚拟机把Class文件加载到内存 并对数据进行校验,转换解析和初始化 形成可以虚拟机直接使用的Java类型,即java.lang.Class   1、装载(Load)  查找和导入class文件 (1)通过一个类的全限定名
  • title: 类加载机制(一):简述类加载过程 date: 2019-03-13 10:06:10 categories: Java虚拟机 tags: 类加载机制 类加载机制(一):简述类加载过程 引言 Java源文件经过编译之后,生成了一连串的16...
  • Java和SpringBoot类加载机制

    千次阅读 2020-08-07 14:39:55
    SPI使用案例二、Java类加载机制1、双亲委派模型2. 双亲委派模型缺陷3. 使用线程上下文类加载器(ContextClassLoader)加载4. 使用类加载器加载资源文件,比如jar包三、spring中SPI机制实现Springboot中的类SPI扩展...
  • 深入浅出类加载机制

    2021-01-20 03:10:32
    类加载机制1.1 加载1.2 验证1.3 准备1.4 解析1.5 初始化1.5.2 为什么静态方法不能调用非静态方法1.6 使用1.7 拆卸2. 类加载器2.1 类加载器种类2.1.1 Java虚拟机自带的类加载器2.1.2 用户自定义的类加载器2.2 双亲...
  • 面试官,不要再问我“Java虚拟机类加载机制”了

    万次阅读 多人点赞 2019-10-27 16:28:39
    关于Java虚拟机类加载机制往往有两方面的面试题:根据程序判断输出结果和讲讲虚拟机类加载机制的流程。其实这两类题本质上都是考察面试者对Java虚拟机类加载机制的了解。 面试题试水 现在有这样一道判断程序输出结果...
  • Java类加载机制.pdf

    2019-12-15 17:34:37
    java的类加载机制,类加载顺序,类加载的体系结构,类加载过程,双亲委派模型及机制等相关内容。做架构师或者高级开发,类加载是必须要掌握的内容。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 596,863
精华内容 238,745
关键字:

类加载机制

友情链接: Desktop.zip