2021年度最全面JVM虚拟机,类加载过程与类加载器
前言
类装载器子系统是JVM中非常重要的部分,是学习JVM绕不开的一关。
一般来说,Java 类的虚拟机使用 Java 方式如下:
Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。
类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。
每个这样的实例用来表示一个 Java 类。
通过此实例的 newInstance()方法就可以创建出该类的一个对象。
类的生命周期
我们先来看下类的生命周期,包括:
加载
连接
初始化
使用
卸载
其中加载、连接、初始化属于类加载过程。
使用是指我们new对象进行使用。
卸载指对象被GC垃圾回收掉。
类加载过程
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 种情况下,必须对类进行初始化,只有主动去使用类才会初始化类:
当遇到
new
、getstatic
、putstatic
或invokestatic
这 4 条直接码指令时当遇到一个类,读取一个静态字段(未被
final
修饰)、或调用一个类的静态方法时。当
JVM
执行new
指令时会初始化类。即当程序创建一个类的实例对象。当
JVM
执行getstatic
指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。当
JVM
执行putstatic
指令时会初始化类。即程序给类的静态变量赋值。当
JVM
执行invokestatic
指令时会初始化类。即程序调用类的静态方法。对类进行反射调用时,如果类没初始化,需要触发其初始化。
初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main
方法的那个类),虚拟机会先初始化这个类。
MethodHandle
和 VarHandle
可以看作是轻量级的反射调用机制,而要想使用这 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的父类加载器为
ExtensionClassLoader
,ExtensionClassLoader
的父类加载器为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 statssun.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()
方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。
最后,祝大家早日学有所成,拿到满意offer
2021年度最全面JVM虚拟机,类加载过程与类加载器相关推荐
- 36.JVM内存分哪几个区,每个区的作用是什么、如和判断一个对象是否存活、java垃圾回收机制、垃圾收集的方法有哪些、java类加载过程、类加载机制、双亲委派、Minor GC和Major GC
36.JVM内存分哪几个区,每个区的作用是什么? 37.如和判断一个对象是否存活?(或者GC对象的判定方法) 38.简述java垃圾回收机制? 39.java中垃圾收集的方法有哪些? 40.java类 ...
- JVM虚拟机详解(三)类加载器的分类
JVM虚拟机详解(三)类加载器的分类 1. 类加载器概述 JVM严格来讲支持两种类型的类加载器 .分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defin ...
- 深入JVM虚拟机(四) Java GC收集器
转载自 深入JVM虚拟机(四) Java GC收集器 1 GC收集器 1.1 Serial串行收集器 串行收集器主要有两个特点:第一,它仅仅使用单线程进行垃圾回收:第二,它独占式的垃圾回收. 在串行 ...
- 深入理解Java虚拟机——JVM类加载机制(类加载过程和类加载器)
一.什么是类加载机制? 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 二.类加载的时机 类 ...
- java中类加载机制、类加载过程和类加载器层次
1.类加载机制 jvm把class文件加载到内存,并对数据进行校验.解析和初始化,最终形成jvm可以直接使用的java类型的过程. (1)加载 将class文件字节码内容加载到内存中,并将这些静态数据 ...
- 类加载——类加载时机、类加载过程、类加载器
链接: 总结版:https://www.jianshu.com/p/663d3c59297 详细版:https://blog.csdn.net/justloveyou_/article/details ...
- 类加载过程以及类加载的机制(双亲委派模型)
文章目录 一.类加载的时机 二.类加载过程 三.类加载的机制:双亲委派模型 1.双亲委派模型是什么 2.ClassNotFoundException 和 NoClassDefFoundError 3. ...
- Java反射与类加载过程会擦出什么样的火花
反射与类加载 一,反射机制原理 二,反射机制中的问题解答 三,反射中的常用API 四, 类加载过程详解 一,反射机制原理 反射机制介绍 1.反射机制允许程序在执行期借助于ReflectionAPI取得 ...
- jvm虚拟机创建对象
虚拟机遇到一条new指令,首先去检查这个指令的参数是否能在常量池定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载.解析和初始化过.如果没有必须先执行相应的类加载过程. 类加载检查后,进行 ...
最新文章
- Nacos源码系列——第二章(Nacos核心源码主线剖析下)
- 美媒人工智能(AI)代表了计算的优点,没有人类推理的缺点
- 用DirectX Audio和DirectShow播放声音和音乐(1)
- usb serial converter驱动安装_让你到期的机顶盒再发挥余热,终极办法任意安装软件,三网可用...
- select count(*)和select count(1)的区别(转载)
- ActiveReports报表控件教程之单元格合并
- 一个简单的shell文件备份脚本
- Atitit atimvc rest原理与自定义实现t33 目录 1.1. Web.xml	1 1.2. MvcFilter	2 1.3. jerserMeth	4 原理 过滤器 过滤即可
- C语言编程初体验 作文,我的理想是当编程师作文
- windows取色器(吸取颜色,获取颜色的rgb信息)
- ios闹钟铃声实现代码
- 解决 Windows USB 鼠标键盘断连掉线的问题 和 安全删除硬件并弹出媒体图标 没有弹出移动硬盘的选项
- Uber面向中印开发者公布SDK,支持第三方应用集成服务
- python中绘制柱形图、饼形图等
- 老说程序员如何看产品经理,今天说说产品经理讨厌哪些程序员
- 社群运营,做好社群长期活跃可以从哪些方面入手?
- 2021年上半年软考电子证书可以查询啦!
- linux阿里云ecs发邮件
- 解决android代码编译时内存不足
- 卡特尔16PF性格测试与答案
热门文章
- 【送书活动】10分钟了解Docker,运维和开发视角有什么不同?
- 广州.net俱乐部12月份ABP框架活动场地征集、志愿者征集、合作讲师\副讲师征集...
- 谈谈Circuit Breaker在.NET Core中的简单应用
- PHP-高并发和大流量的解决方案
- C#(Sharp)操作数据库原理及案例精析(强烈建议收藏)
- 【专升本计算机】专升本计算机期末考试复习题(B卷附答案)
- 【测绘程序设计】视距测量神器V1.0(附源程序)
- C语言试题六十二之请编写函数fun除了字符串前导和尾部的*号之外,将串中其他*号全部删除。形参h已指向字符串第一个字母,形参p已指向字符串中最后一个字母。在编写程序时,不得使用c语言提供的字符串函数。
- C++之undefined reference to “ssl::first::first()“
- java.lang.IllegalStateException: attempt to re-open an already-closed object