2021年度最全面JVM虚拟机,类加载过程与类加载器

Java架构没有996 等级 1154 1 1

前言

类装载器子系统是JVM中非常重要的部分,是学习JVM绕不开的一关。

一般来说,Java 类的虚拟机使用 Java 方式如下:

Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。

类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。【参考文献】

每个这样的实例用来表示一个 Java 类。

通过此实例的 newInstance()方法就可以创建出该类的一个对象。

类的生命周期

我们先来看下类的生命周期,包括:

  • 加载

  • 连接

  • 初始化

  • 使用

  • 卸载

其中加载、连接、初始化属于类加载过程。

使用是指我们new对象进行使用。

卸载指对象被GC垃圾回收掉。

2021年度最全面JVM虚拟机,类加载过程与类加载器

类加载过程

JVM的类加载的过程是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由JVM的具体实现指定的。【参考文献】

Class 文件需要加载到虚拟机中之后才能运行和使用,系统加载 Class 类型的文件份如下几步:

  • 加载
  • 连接
  • 验证
  • 准备
  • 解析
  • 初始

顺序是这样一个顺序,但是加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。

下面我们来逐步解析

加载

这里的加载是微观上的,是类加载过程中的一小步,也是第一步,类加载过程中的加载是宏观上的。

加载的流程如下:

  • 通过全类名获取定义此类的二进制字节流

  • 将字节流所代表的静态存储结构转换为方法区的运行时数据结构

  • 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口

  • 简单来说就是:加载二进制数据到内存 —> 映射成JVM能识别的结构—> 在内存中生成class文件。

  • 在虚拟机规范上,对这部分的规定并不具体,所以实现方式是很灵活的。

加载阶段我们可以用自定义类加载器去控制字节流的获取方式,是非数组类的可控性最强的阶段,而数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。【参考文献】

关于类加载器是什么,后文再聊。//加入Java开发交流君样:756584822一起吹水聊天

连接

连接分为三步,验证、准备、解析,目的是将上面创建好的Class类合并至JVM中,使之能够执行的过程。

验证

确保class文件中的字节流包含的信息,符合当前虚拟机的要求,保证这个被加载的class类的正确性,不会危害到虚拟机的安全。

准备

为类中的静态字段分配内存,并设置默认的初始值,比如int类型初始值是0。

被final修饰的static字段不会设置,因为final在编译的时候就分配了。

解析

解析阶段的目的,是将常量池内的符号引用转换为直接引用的过程。

解析动作主要针对类、接口、字段、类方法、接口方法、方法类型等。【参考文献】

如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化。)

符号引用就是一组符号来描述目标,可以是任何字面量。

直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

举个例子:

在程序执行方法时,系统需要明确知道这个方法所在的位置。

Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。

当需要调用一个类的方法的时候,只要知道这个方法在方法表中的偏移量就可以直接调用该方法了。

通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。

所以,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。【参考文献】

初始化

初始化就是执行类的构造器方法,是类加载的最后一步,这一步 JVM才开始真正执行类中定义的 Java 程序代码

这个方法不需要定义,是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并来的。

若该类具有父类,jvm会保证父类的init()先执行,然后在执行子类的init()

对于初始化阶段,虚拟机严格规范了有且只有 5 种情况下,必须对类进行初始化,只有主动去使用类才会初始化类:

  • 当遇到 newgetstaticputstaticinvokestatic这 4 条直接码指令时

  • 当遇到一个类,读取一个静态字段(未被final修饰)、或调用一个类的静态方法时。

  • JVM执行new指令时会初始化类。即当程序创建一个类的实例对象。

  • JVM执行 getstatic指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。

  • JVM执行 putstatic指令时会初始化类。即程序给类的静态变量赋值。

  • JVM执行invokestatic指令时会初始化类。即程序调用类的静态方法。

  • 对类进行反射调用时,如果类没初始化,需要触发其初始化。

初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。

当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。

MethodHandleVarHandle 可以看作是轻量级的反射调用机制,而要想使用这 2 个调用, 就必须先使用 findStaticVarHandle 来初始化要调用的类。

「补充,来自issue745」 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

类加载器

三大类加载器

了解了类加载过程后,我们来看看类加载器。【参考文献】

类加载器(ClassLoader)用来加载 Java 类到 Java 虚拟机中。

JVM 中内置了三个重要的 ClassLoader,同时按如下顺序进行加载:

  • BootstrapClassLoader启动类加载器:最顶层的加载类,由C++实现,负责加载 %JAVA_HOME%/lib目录下的核心jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类。
  • ExtensionClassLoader扩展类加载器:主要负责加载目录%JRE_HOME%/lib/ext目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。
  • AppClassLoader 应用程序类加载器:面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。 除了BootstrapClassLoader其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader

类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器。

需要注意的是,Java虚拟机对Class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的Class文件加载到内存生成Class对象。

双亲委派模型

概念

每一个类都有一个对应它的类加载器。在加载类的时候,是采用的双亲委派模型,即把请优求先交给父类处理的一种任务委派模式。

系统中的类加载器在协同工作的时候会默认使用 双亲委派模型 。//加入Java开发交流君样:756584822一起吹水聊天

双亲委派模型的理论很简单,分为如下几步:

  • 即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。

  • 加载的时候,首先会把该请求委派给该父类加载器的 loadClass()处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。【参考文献】

当父类加载器无法处理时,才由自己来处理。

  • AppClassLoader的父类加载器为ExtensionClassLoaderExtensionClassLoader 的父类加载器为null,当父类加载器为null时,会使用启动类加载器BootstrapClassLoader 作为父类加载器。

为什么要使用双亲委派模型

试想一种情况,我们在项目目录下,手动创建了一个java.lang 包,并在该包下创建了一个Object,这时候我们再去启动Java程序,原生Object会被篡改吗?当然是不会的!

因为Object类是Java的核心库类,由BootstrapClassLoader加载,而自定义的java.lang.Object类应该是由AppClassLoader来加载。

BootstrapClassLoader先于AppClassLoader进行加载,根据上面的双亲委派模型的概念,我们可以知道,java.lang.Object类已经被加载,并且AppClassLoader要加载类之前都要先给其父类过目,所以自己写的野类是无法撼动核心库类的。

结论

双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改。

源码分析

双亲委派模型的都集中在java.lang.ClassLoader 的 loadClass()中,相关代码如下所示:

private final ClassLoader parent; 
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查请求的类是否已经被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //父加载器不为空,调用父加载器loadClass()方法处理
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                   //抛出异常说明父类加载器无法完成加载请求
                }

                if (c == null) {
                    long t1 = System.nanoTime();
                    //自己尝试加载
                    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();
                }
            }//加入Java开发交流君样:756584822一起吹水聊天
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

反双亲委派模型

双亲委派模型是Java默认的,假如我们不想用双亲委派,我们要怎么办呢?【参考文献】

我们可以自定义一个类加载器,除了BootstrapClassLoader其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader。如果我们要自定义自己的类加载器,很明显需要继承ClassLoader

从上面的源码我们知道,双亲委派模型的都集中在java.lang.ClassLoader 的 loadClass()中,如果想打破双亲委派模型则需要重写 loadClass() 方法。

如果我们不想打破双亲委派模型,就重写 ClassLoader类中的 findClass()方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。

最新2021整理收集的一些高频面试题(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud、jvm、源码、算法等详细讲解,也有详细的学习规划图,面试题整理等,需要获取这些内容的朋友请加Q君样:756584822 2021年度最全面JVM虚拟机,类加载过程与类加载器

2021年度最全面JVM虚拟机,类加载过程与类加载器

【参考文献】

收藏
评论区

相关推荐

jvm
类的加载 连接与初始化 加载:查找并加载类的二进制数据 连接 验证:确保被加载的类的正确性 准备:为类的静态变量分配内存,并将其初始化为默认值 解析:把类中的符号引用转换成为直接引用 初始化:为类的静态变量赋予正确的初始值 主动使用 创建类的实例 访问某个类或接口的静态变量,或者对该静态变量赋值 调用类的
Java后端开发三年的程序员竟然还被JVM难住!果然JVM面试是有套路的!
前言JVM是面试中必问的部分,本文通过思维导图以面向面试的角度整理JVM中不可不知的知识。先上图:image1、JVM基本概念1.1、JVM是什么JVM 的全称是 「Java Virtual Machine」,也就是我们耳熟能详的 Java 虚拟机。JVM具备着计算机的基本运算方式,它主要负责把 Java 程序生成的
Java里面的十万个为什么
Java里面的十万个为什么 1.不是说 JVM 是运行 Java 程序的虚拟机吗?那 JRE 和 JVM 的关系是怎么样的呢?简单地说,JRE 包含 JVM 。JVM 是运行 Java 程序的核心虚拟机,而运行 Java 程序不仅需要核心虚拟机,还需要其他的类加载器,字节码校验器以及大量的基础类库。JRE 除包含 JVM 之外,还包含运行 Java 程序的其
【单例深思】饿汉式
1、抛出问题1. 为什么饿汉式单例是线程安全的?2. 为什么饿汉式单例没有延迟加载(Lazy Loading)? 2、JVM类加载机制饿汉式单例的实现:javapublic class Singleton private static Singleton singleton new Singleton(); private S
从未有人把JVM原理讲的这么详细
JVM原理1.简述JVM是Java Virtual Machine(Java虚拟机)的缩写,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运
Java开发面试高频考点学习笔记(每日更新)
Java开发面试高频考点学习笔记(每日更新) 1.深拷贝和浅拷贝 2.接口和抽象类的区别 3.java的内存是怎么分配的 4.java中的泛型是什么?类型擦除是什么? 5.Java中的反射是什么 6.序列化与反序列化 7.Object有哪些方法? 8.JVM内存模型 9.类加载机制 10.对象的创建和对象的布局 11.Java的四种引用
2021年度最全面JVM虚拟机,类加载过程与类加载器
前言类装载器子系统是JVM中非常重要的部分,是学习JVM绕不开的一关。一般来说,Java 类的虚拟机使用 Java 方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表
【面试必会】最新阿里+头条+腾讯大厂Java笔试真题
一、内存与线程 1、内存结构内存是计算机的重要部件之一,它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行,内存性能的强弱影响计算机整体发挥的水平。JVM的内存结构规定Java程序在执行时内存的申请、划分、使用、回收的管理策略,通说来说JVM的内存管理指运行时数据区这一大块的管理。 2、线程运行JVM中一个应用是可以有多个线程并行执行,线程
限时发布!纯手打“RocketMQ笔记”
1 JVM的内存区域布局 java代码的执行步骤有三点 java源码文件编译器字节码文件 字节码文件JVM机器码 机器码系统CPU执行 JVM执行的字节码需要用类加载来载入;字节码文件可以来自本地文件,可以在网络上获取,也可以实时生成。就是说你可以跳过写java代码阶段,直接生成字节码交由JVM执行 其中Jav
秋招已经开始准备了!【Java面试题】最新Java开发岗面试知识笔记
在最近两个月不断的面试中,我分类总结了 Java 开发岗位面试中的一些知识点。主要包括以下几个部分: 1. Java 基础知识点 2. Java 常见集合 3. 高并发编程(JUC 包) 4. JVM 内存管理 5. Java 8 知识点 6. 网络协议相关 7. 数据库相关 8. MVC 框架相关 9. 大数据相关 10. Linux 命令相关面试,
2021年度最全面JVM虚拟机,类加载过程与类加载器
前言类装载器子系统是JVM中非常重要的部分,是学习JVM绕不开的一关。一般来说,Java 类的虚拟机使用 Java 方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表
阿里面试被问到【垃圾回收器】,不会怎么办??
垃圾回收器 GC 分类与性能指标 垃圾回收器概述1. 垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商、不同版本的JVM来实现。2. 由于JDK的版本处于高速迭代过程中,因此Java发展至今已经衍生了众多的GC版本。3. 从不同角度分析垃圾收集器,可以将GC分为不同的类型。Java不同版本新特性1. 语法层面:Lambda表达式、switch、
让人茅塞顿开!mysql教程视频百度云
2021全新Java核心知识 由于内容过多,本文篇幅有限,因此小编就不详细展示了,请各位老铁认真的看完本文内容,你一定会有所收获!Java篇目录内容(涵盖Java基础及核心技术、容器、并发、JVM技术)网络篇目录内容(涵盖计算机网络知识以及HTTPS中的TLS)操作系统Linux目录内容(初始初探操作系统Linux以及系统操作)数据结构与算法目录内容(详解布
大厂必备面试题———JVM内存结构
JVM 一,基本介绍解释:java虚拟机,准确的来说是java二进制字节码的运行环境。(jvm是一套规范),用来执行class文件,保证java语言跨平台。Java虚拟机可以看作是一台虚拟的计算机,和真是的计算机一样,有着自己的指令集以及各种运行时内存。jvm就是一个字节码翻译器,它将字节码文件翻译成各个系统对应的的机器码,确保字节码文件能在各个系统上正确的
五面阿里巴巴拿offer后定级P6:分享Java面经及答案总结
一面(电话)说说对JVM的理解treemap和hashmap有什么区别?Java多线程的的5大状态图流转mysql主键和唯一索引的区别说说最近的项目如何实现session共享,用redis如何实现缓存击穿的概念和解决方案说说微服务,微服务之间如何管理二面(现场)java nio常?用的三个类java里面的同步锁了解吗?Countdownlauch和Cylic