JVM类的加载相关的问题

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

JVM类的加载相关的介绍

学习类的加载的加载过程对深入理解JVM有十分重要的作用,下面就跟我一起学习JVM类的加载过程吧!



一、类的加载过程

JVM的类的加载过程分为五个阶段:加载、验证、准备、解析、初始化
加载
  加载阶段就是将编译好的的class文件通过字节流的方式从硬盘或者通过网络加载到JVM虚拟机当中来。(我们平时在Idea中书写的代码就是放在磁盘中的,也可以通过网络加载到虚拟机)

验证
  验证阶段主要是验证class文件的格式或者内容是否符合java规范和虚拟机规范,比如最常见的字节码文件中的cafe baby,以及通过class文件是u2或者u4的的方式解析字节码中各部分的内容是否符合规范。

准备
  准备阶段就是给静态变量(static修饰的变量)赋默认值的过程,int类型的赋值为0,对象类型赋值为null,这都是默认值,需要注意的是常量,也就是被final修饰的static,在类编译的时候就已经赋值完成了。

解析
 解释阶段做的事情用一句话来说就是将符号引用转变为直接引用(句柄)
 句柄:如下如所示虚拟机有两种引种对象数据的方式,一种是通过句柄池的方式,一种是通过直接指针的方式,句柄池的方式就是在引用和真实地址之间多了一层句柄,这样的好处就是当引用的真实对象发生改变时,栈中存放的值不需要改动,但是多了一层就会增加系统的复杂性,所以hotsport虚拟机用的是直接地址指针的方式(如下图)。
直接引用:数据在内存中的真实地址
符号引用: 包括一些方法名和字面量等,这些符号引用在加载过程中有的可以确定下来的,比如static修饰的main方法,这些可以确定下来的引用在解析过程中就会转变为指向真实地址的指针,还有一些是只有在实际运行期间才可以确定下来的,比如多态,接口等,这些就需要到真正调用的时候才转为直接地址引用。(这也就是静态连接和动态连接的区别)

JVM类的加载相关的问题,JVM,jvm

初始化
类的初始化是将给静态变量赋值(这时候的赋值才是赋代码中书写的值)静态代码块收集在一起进行执行的过程(其实就是执行字节码中的clinit方法,注意这个方法不是类的构造方法)执行顺序根据静态变量和静态代码块在类中的顺序决定。代码如下

public class Hello {
	//准备阶段给a赋值为0.初始化的时候给a赋值为3
    public static int a=3;
    //根据顺序先执行a=3,在实行静态方法
    static {
        String b="b";
    }
    public static void main(String[] args) {

    }

}

注意:类的初始化,实例化对象


二、双亲委派机制

双亲委派机制是我们学习类的加载和面试过程中逃不掉的问题,接下来我会通过介绍类加载器的种类,jvm虚拟机使通过什么方式调用这些类加载器完成类的加载的,双亲委派机制是什么,打破双亲委派机制的例子有哪些。

1、类加载器的种类

类的加载过程,同通过类加载器实现的。类的加载器主要有如下几种:
引导类加载器:c++代码实现,负责接在java的核心类库,比如rt.jar等
扩展类加载器:加载java的一些扩展类,负责加载jre下ext扩展目录下的jar报
应用类加载器:负责加载classpath下的类,也就是我们自己写的类
自定义类加载器:我们自己写的写的类加载器,负责加载自定义路径下的类

2、为什么JVM要分成不同的类的加载器

介绍为什么要搞这么多类加载器之前,就需要先说明JVM是如何使用这些类加载器的(也就是双亲委派机制)。双亲委派机制就是(引导类加载器->扩展器类加载器->应用类加载器->自定义类加载器 逐层上层是下层类加载器的父亲,注意这里不同于extends的继承关系,是类加载器中有一个parent属性,指向它的父亲是谁)

JVM类的加载相关的问题,JVM,jvm

JVM类的加载相关的问题,JVM,jvm

3、类的加载过程

抛开自定义加载器先不谈,当我们自己编的类加载的时候,会先去应用类加载器中查找该类是否已经加载了(如果加载了直接返回),如果没有则向上委托扩展类加载器看它是否已经加载(加载了直接返回),如果没有则继续向上委托(加载了直接返回),若没有加载并且是需要加载核心JAVA的核心类库,则加载如果不是核心类库,则通知下层去加载,如果是JAVA的扩展包的类就通知扩展类加载器加载,如果是用户自定义的类就再由扩展类加载器继续向下通知应用程序加载器加载。这就是JVM的双亲委派机制。

4、为什么要通过双亲委派机制

JVM为什么要通过这种麻烦的方式,来实现类的加载的,原因如下:

  • 沙箱机制:JAVA不希望用户或者黑客随便更改核心类库,所以再加载核心类库和扩展包的时候,JAVA要保证加载的是自己核心类库的东西,而不是别人更改过的类。,就是为了安全
  • 避免了类的重复加载

5、从代码层次分析双亲委派机制

JAVA的双亲委派的机制的主要概念:

  • 引导类加载器由JAVA虚拟机启动以后,调用底层C++代码加载
  • 扩展类加载器和应用类加载器由一个被称为启动器的加载,该类就是Launcher, 扩展类加载器和应用类加载器的上下层父子关系由parent属性确立
  • Launcher类是由引导类加载器加载的

类的关系图

AppClassLoader和ExtClassLoader都是Launcher(启动器)的内部类,由Launcher调用构造方法的时候,创建AppClassLodader和ExtClassLoder
JVM类的加载相关的问题,JVM,jvm
                      图一

JVM类的加载相关的问题,JVM,jvm
                     图二

AppClassLoader和ExtClassLoder都是继承自URLClassLoader

JVM类的加载相关的问题,JVM,jvmJVM类的加载相关的问题,JVM,jvm

URLClassLoader最终继承自ClassLoader

JVM类的加载相关的问题,JVM,jvm
                     图三
                     
类的父子关系整理清楚了,下面看Launcher是怎么创建AppcClassLloader和ExtClassLoader的?
可以看到,在创建AppClassLoader时,将创建好的ExtClassLoader传给了AppClassLoader的get**方法。这个方法最终直接会调用到它的构造方法,我们直接看它的构造放的代码。
JVM类的加载相关的问题,JVM,jvm
                     图四

我们看下图AppClassLoader的构造方法,会调用super,也就是父类的构造方法,根据图三的类的关系图,会最终调用到ClassLoader类的构造方法,我们继续进入到它的构造方法中看它干了什么。
JVM类的加载相关的问题,JVM,jvm
                     图五

好的我们看到了parent属性,也就是将最初传入的ExtClassLoader的实例设置为APPClassLoader的父亲。
tuliuJVM类的加载相关的问题,JVM,jvm
                    图六

总结:引导类加载器加载了启动器Launcher,Launcher创建了AppClassLoader和ExtClasslLoader,并好父子关系(通过parent)


你以为这就完了吗?,不可能,绝对不可能!双亲委派的代码还没说呢,加载类之前向上委托那一系列的操作,在哪能还没说呢,怎么可能就这么结束了呢!!!


我们来看类是怎么加载的,我们都知道,加载一个类的时候我们会调用ClassLoader的loaderClass方法,那我们就看看父子关系建立好以后,是怎么实现向上委托的?
JVM类的加载相关的问题,JVM,jvm
                     图七

 public Class<?> loadClass(String name) throws ClassNotFoundException {
 		//调用了重载的loadClass方法,多传入一个flase
        return loadClass(name, false);
    }
 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    //加锁是为了保证加载类class文件时,必须是安全的,不会因为并发加载多个类进来
        synchronized (getClassLoadingLock(name)) {
            // 先根据类的全限定名查找类是否已经加载过了,加载过了c!=null直接返回。不走下面的代码
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                /**
                * 注意:这里就是双亲委派的关键代码
     			*代码走到这里会让他的parent去递归调用本方法,直到parent==null时(ExtClassload),这里就符合双亲委派机制,向上委托
     			*/
     				//AppClassLoader执行这行代码
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                        //扩展类加载器执行这行代码
                    } else {
                    //因为ExtClassLoader的partent==null执行这行代码,调用引导类加载器,加载java核心类库的代码返回c==null 代码继续向下执行
                        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();
                    //因为之前是递归调用的,第一次是ExtClassLoader调用这个方法,去加载java扩展包的类
                    //第二次是AppClassLoader调用这个方法,AppCLassLoader是加载classpath路径下我们自己自己写的类,具体实现的方法在URLClassLoader,我们去看URLClassLoader是怎么实现的,如图八
                    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;
        }
    }

JVM类的加载相关的问题,JVM,jvm
                     图八

加载class流文件
JVM类的加载相关的问题,JVM,jvm
                       图九

总结

  • 引导类加载器由底层C++代码创建
  • 引导类加载器加载启动器Launcher
  • Launcher的构造方法创建ExtClassLoade和AppClassLoader,并通过parent属性确认父子父子关系
  • ClassLoader的loadClass方式实现了双亲委派机制
  • findClass方法调用defindClass实现classpath下的class文件的加载

JVM类的加载相关的问题,JVM,jvm
                       图十文章来源地址https://www.toymoban.com/news/detail-702245.html


三、自定义类加载器

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

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

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

    }

    public static void main(String args[]) throws Exception {
        //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        //D盘创建 test/com/test/jvm 几级目录,将User类的复制类User1.class丢入该目录
        Class clazz = classLoader.loadClass("com.test.jvm.User1");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}



四、打破双亲委派机制

1.tomcat打破双亲委派机制

2.SPI 打破双气双亲委派机制

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

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

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

相关文章

  • 1、JVM相关知识点-类加载机制

    当我们用java命令运行某个类的main函数启动程序时,首先需要通过 类加载器 把主类加载到 JVM 通过Java命令执行代码的大体流程如下: 其中loadClass的类加载过程有如下几步: 加载 验证 准备 解析 初始化 使用 卸载 加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会

    2024年04月10日
    浏览(30)
  • Java进阶(1)——JVM的内存分配 & 反射Class类的类对象 & 创建对象的几种方式 & 类加载(何时进入内存JVM)& 注解 & 反射+注解的案例

    1.java运行时的内存分配,创建对象时内存分配; 2.类加载的顺序,创建一个唯一的类的类对象; 3.创建对象的方式,new,Class.forName,clone; 4.什么时候加载.class文件进入JVM内存中,看到new,Class.forName; 5.如何加载?双亲委托(委派)机制:安全;AppClassLoader; 6.反射实质:能

    2024年02月14日
    浏览(27)
  • JVM——类的生命周期

    一个类的完整生命周期如下: Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢? 系统加载 Class 类型的文件主要三步: 加载-连接-初始化 。连接过程又可分为三步: 验证-准备-解析 。 加载 类加载过程的第一步,主要完成下面3件事情

    2024年02月12日
    浏览(27)
  • JVM类的声明周期

    本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明,所有版权属于黑马程序员或相关权利人所有。本博客的目的仅为个人学习和交流之用,并非商业用途。 我在整理学习笔记的过程中尽力确保准确性,但无法保证内容的完整性和时效性。本博客的

    2024年02月05日
    浏览(28)
  • JVM系列-3.类的生命周期

    👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家 📕系列专栏:Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术、JVM原理 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦 🍂博主正在努力完成2023计划中:源码

    2024年01月25日
    浏览(26)
  • 【JVM】JVM类加载机制

    JVM的类加载机制,就是把类,从硬盘加载到内存中 Java程序,最开始是一个Java文件,编译成.class文件,运行Java程序,JVM就会读取.class文件,把文件的内容,放到内存中,并且构造成.class类对象 这里的加载是整个类加载的一个阶段,他和类加载是不同的 在整个类加载的过程中 主要任务就是

    2024年02月07日
    浏览(32)
  • JVM类加载机制-JVM(一)

    我们运行一个.class文件,windows下的java.exe调用底层jvm.dll文件创建java虚拟机(c++实现)。 创建一个引导类加载器实例(c++实现) C++调用java代码Launcher,该类创建其他java类加载器。 Launcher.getClassLoader()调用loaderClass加载运行类Math classLoader.loader(“com.jvm.math”)加载main方法入口

    2024年02月12日
    浏览(30)
  • 【JVM】JVM执行流程 && JVM类加载 && 垃圾回收机制等

    目录 🌷1、JVM是什么? 🌷2、JVM的执行流程(能够描述数据区5部分) 🌷3、JVM类加载过程 🌷4、双亲委派机制:描述类加载的过程 问题1:类加载器 问题2:什么是双亲委派模型?  问题3:双亲委派模型的优点 🌷5、垃圾回收机制(重要,针对的是堆)    问题1:判定对象

    2024年02月15日
    浏览(44)
  • JVM基础(1)——JVM类加载机制

    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 学习必须往深处挖,挖的越深,基础越扎实! 阶段1、深入多线程 阶段2、深入多线程设计模式 阶段3、深入juc源码解析

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

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

    2024年02月11日
    浏览(29)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包