JVM类加载和双亲委派机制

这篇具有很好参考价值的文章主要介绍了JVM类加载和双亲委派机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把类加载到JVM,本文主要说明类加载机制和其具体实现双亲委派模式。

一、类加载机制

类加载过程

类加载的过程是将类的字节码加载到内存中的过程,主要包括:加载-->链接-->初始化,其中链接还包括验证、准备、解析3个步骤。

  1. 加载:将class文件加载到内存,在方法区生成运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应Class实例的引用,生成的Class实例对象放到堆中;
  2. 验证:验证这个class文件是否合法,包括文件格式的校验,元数据类型的校验等;
  3. 准备:为类变量分配内存空间,但此时只是初始化为默认值而非真实值,但对于final变量此时会初始化为真实值;
  4. 解析:将符号引用(相对引用)转换为直接引用,符号引用是class文件的相对表达方式,直接引用就是在该系统里地址指针,比如hello()方法为符号引用,0x12345678为直接引用;
  5. 初始化:初始化类变量成真实值,初始化静态代码段,实际执行的是类构造器 方法。

JVM类加载和双亲委派机制,JVM

类加载过程是懒加载的策略,只有当该类被使用了才会被初始化,实际就是调用classLoader的 方法执行的过程;会触发类的初始化操作条件为:(1)需要创建新的对象,执行了new操作;(2)调用了类的静态变量或静态方法;(3)通过反射机制来获取某个类的时候;

利用new实例化对象的过程

Java的对象实例化的过程是调用 方法,在进行new操作的时候会执行实例化操作,实例化的过程主要 是调用构造方法的过程。在进行对象实例化前,已经初始化静态变量和静态代码段,实例化过程会初始化变量和代码块、调用构造方法进行实例化,利用new操作实例化对象的过程:

二、双亲委派

类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器:

  • 引导类加载器Bootstrap ClassLoader:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等;
  • 扩展类加载器Extension ClassLoader:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包;
  • 应用程序类加载器Application ClassLoader:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类;
  • 自定义加载器Customer ClassLoader:负责加载用户自定义路径下的类包;

以下代码打印出各个类关联的ClassLoader情况:

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 bootstrapLoader : " + bootstrapLoader);
        System.out.println("the extClassloader : " + extClassloader);
        System.out.println("the appClassLoader : " + appClassLoader);

        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"));

    }
}

类加载的具体实现是通过双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。

简单理解就是:能父加载器做的事就父加载器做,父加载器做不了的事情才自己来做

设计双亲委派机制的好处是:

  1. 保障类的唯一性:ClassLoader的双亲委派模型保障一个类在类加载器的唯一性,父类已经加载了该类,子类就不再加载,保障被加载类的唯一性。
  2. 实现沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改。

类加载器实现原理

先说明类加载器本身初始化的逻辑:

  1. 在sun.misc.Launcher类中创建JVM启动器实例。
  2. 在Launcher构造方法内部,其创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)。
  3. JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。
//Launcher的构造方法

public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
      // 构造扩展类加载器,在构造的过程中将其父加载器设置为null
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
         //构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
        //Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
        //设置当前线程的类加载器为应用类加载器
    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    //...
}

再说明一下AppClassLoader的loadClass方法,该方法是进行class文件加载的方法,但最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。
//ClassLoader的loadClass方法,里面实现了双亲委派机制
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 检查当前类加载器是否已经加载了该类
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {  //如果当前加载器父加载器不为空则委托父加载器加载该类
                    c = parent.loadClass(name, false);
                } else {  //如果当前加载器父加载器为空则委托引导类加载器加载该类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                //都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
                c = findClass(name);

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

用户可以设置自定义类加载器来打破默认的双亲委派模式,主要是实现loadClass()方法,在这个方法中可以自定义加载class文件的逻辑。

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);
                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 {
        MyClassLoader classLoader = new MyClassLoader("xxx");
        //尝试用自己改写类加载机制去加载自己写的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());
    }
}

打破双亲委托的方法:

通过线程上下文类加载器Thread Context ClassLoader#setContextClassLoader(ClassLoader c)可以指定当前线程的类加载器,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。

本文由博客一文多发平台 OpenWrite 发布!文章来源地址https://www.toymoban.com/news/detail-704437.html

到了这里,关于JVM类加载和双亲委派机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [JVM] 3. 类加载子系统(2)-- 类加载器、双亲委派机制(JDK1.8及之前)及其他

    JDK1.8及之前和JDK9及之后的双亲委派模型是不一样的,这里学习了1.8及以前的双亲委派模型,记录笔记 1.8之前主要是这几种类加载器: 负责将存放在 JAVA_HOMElib 目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名,如rt.jar)类库加载到虚

    2024年02月16日
    浏览(48)
  • 【jvm】双亲委派机制

    一、说明 1.java虚拟机对class文件采用的是按需加载的方式,当需要使用该类时才会将它的class文件加载到内存生成class对象 2.加载某个类的class文件时,java虚拟机采用双亲委派模式,即把请求交给由父类处理,是一种任务委派模式 3.jvm中表示两个class对象是否为同一个类存在两

    2024年02月11日
    浏览(42)
  • 【JVM】打破双亲委派机制

           📝个人主页:五敷有你        🔥 系列专栏:JVM ⛺️ 稳中求进,晒太阳 ClassLoader包含了四个核心方法 双亲委派机制核心代码阅读 阅读双亲委派机制的核心代码,分析如何通过自定义类加载器打破双亲委派机制。 打破双亲委派机制的核心就是讲下面的代码重写

    2024年02月19日
    浏览(49)
  • JVM类加载&双亲委派-JVM(二)

    上篇文章说了java类的加载,验证、准备、解析、初始化。类的初始化必须是类加载完才执行,所以类的构造方法初始化是在静态方法之后执行。 JVM类加载机制-JVM(一) 一、类加载和双亲委派机制 前面类加载主要通过类加载器实现,类加载器有这几种: 引导类加载器:负责

    2024年02月11日
    浏览(43)
  • 类加载 - 双亲委派机制详解

    启动类加载器(Bootstrap Class Loader):它是Java虚拟机的一部分,负责加载Java核心类库,如java.lang包中的类。它是最顶层的类加载器,由C++实现,不是Java类。 扩展类加载器(Extension Class Loader):它负责加载Java的扩展类库,位于JRE的lib/ext目录下。它是由Java编写的类加载器,是

    2024年02月12日
    浏览(39)
  • Java JVM类加载阶段 双亲委派模式

    加载 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有: _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用 _super 即父类 _fields 即成员变量 _methods 即方法 _constants 即常量池 _class_loader 即类加载器

    2024年01月25日
    浏览(45)
  • JVM类加载基本流程及双亲委派模型

    一个运行起来的Java进程就是一个JVM虚拟机,这就需要从操作系统中申请一片内存区域。JVM申请到内存之后,会把这个内存划分为几个区域,每个区域都有各自的作用。 一般会把内存划分为 四个区域 : 方法区 (也称 \\\"元数据区\\\"), 堆区 , 栈区 , 程序计数器 。 (1)方法区:

    2024年04月23日
    浏览(44)
  • JVM的类加载的过程以及双亲委派模型

    目录 1、加载 (加载字节码文件,生成.class对象) 2、验证(验证Class文件是否符合规范)  3、准备 (为静态变量分配内存并设置变量初始值) 4、解析(初始化常量池中的一些常量)  5、初始化(初始化对象,并为静态变量赋值)  总结: 双亲委派模型:   JVM的类加载器

    2023年04月20日
    浏览(49)
  • 深度解析JVM类加载器与双亲委派模型

    Java虚拟机(JVM)是Java程序运行的核心,其中类加载器和双亲委派模型是JVM的重要组成部分。本文将深入讨论这两个概念,并解释它们在实际开发中的应用。 1. 什么是类加载器? 类加载器是JVM的一部分,负责加载Java类的字节码文件。Java应用程序中的每个类都必须由类加载器

    2024年01月17日
    浏览(74)
  • 【Java】深入了解双亲委派机制(常说的类加载机制)

    ava虚拟机(JVM)的类加载机制是Java应用中不可或缺的一部分。本文将详细介绍JVM的双亲委派机制,并阐述各关键点。 双亲委派机制(Parent-Delegate Model)是Java类加载器中采用的一种类加载策略。该机制的核心思想是:如果一个类加载器收到了类加载请求,默认先将该请求委托

    2024年02月04日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包