探索Java的神秘运行机制:揭秘整体运行流程

news2025/1/17 5:51:14

目录

1. 背景

2.整体流程

2.1 一看整体流程

2.1 再看运行流程

3. 类的加载过程

3.1 初探类的加载过程

4. 类加载机制

4.1 类加载器

4.2 双亲委派机制

5. 小结:

1. 背景

        学习了这么多年的Java,把自己的理解写成JVM系列,以便于后面的温习,以及帮助更多的java开发人员,同时文章内容也会不定期的修改,以便让这个系列更好,更加的易于理解,提高文章的质量。

2.整体流程

       开篇先梳理下,我们之前写的第一个Hello World!,当我们运行出来后,在控制台打印的那种激动心情,但是他的背后是如何运行的呢?那么问题来了,我们写的java代码到底是如何运行起来的,中间又经历了什么?带着我这些问题,我们继续往下看。

2.1 一看整体流程

        首先假设我们写好了一个java代码,在IDEA或者Eclipse工具上,会有一个“.java“后缀的代码文件,我们写好后,想要部署到线上的机器上去运行,一般都是把代码通过IDEA工具编译生成".class"后缀文件后,再进行打包,打包成“.jar”后缀的jar包,或者 “.war” 后缀的war包; 最后再通过 tomcat部署 ,或者 java 命令进行运行;是不是这样的一个过程;粗略的看确实是这样一个过程,来看下图,来回忆下这个过程:

         

上面的过程中,java代码通过idea工具编译后,生成“.class” 后缀的,字节码文件(字节码是能够被虚拟机认识的16进制字节,其中还有一些字节码指令等等的内容),那么,我们编译好的文件是如何运行起来的呢?

2.1 再看运行流程

这个时候我们就要使用一个“Java -jar”的命令,来运行程序,实际上此时就会启动一个JVM进程,这个JVM就会负责运行这些“.class” 字节码文件,相当于负责运行我们写好的系统。

对于上面的过程,IDEA编译打包代码的过程,我们暂时不在这块过多的说明,我们把重点放到运行这块上来,这块才是核心内容,下面就说下打包后,部署运行的内部是如何运行的?

        当我们运行了一个"java -jar" 命令后,就是启动了一个JVM进程,就会运行我们编写的字节码文件对吧,我们按照逆推的方式来理解,既然要执行字节码,我们是不是要先找到字节码文件,那么怎么找呢,找到后怎么读取执行,针对这两个问题,我们引出了两个概念 “类加载器”,“字节码执行引擎”,类加载器很好理解,字面意思,就是把类加载到JVM中,以供后续代码运行使用;

字节码执行引擎,就是JVM会基于自己的字节码引擎来执行类加载器,加载到内存中的类。

为了方便我们理解上面的过程,请看下图的过程。

        好了,说过了大概的整体JVM运行流程,我们一起来探索下类加载的过程吧。

3. 类的加载过程

先想一个问题:JVM在什么情况下会加载一个类呢?

        类加载的过程非常的繁琐复杂,我们在工作中,只要把握住核心的工作原理就可以了,

一个类从加载到使用,一般会经历下面的这个过程:

        加载->验证->准备->解析->初始化->使用->卸载

 所以,首先我们要搞明白上面的问题,就是JVM在什么情况下会去加载一个类呢?翻译过来就是说,啥时候会从".class"字节码文件中加载这个类到JVM内存中,毕竟,内存是有限的,在过去内存是很贵的,另外加载过程很繁琐,也很消耗资源,我们肯定要减少消耗,想通了这,答案就很简单啦,就是在你的代码中用到这个类的时候去加载

3.1 初探类的加载过程

        我们明白了类的加载时机,只有在需要的时机去加载,那么加载后又做了什么处理,下面分别介绍下另外三个概念,验证、准备、解析、初始化

        验证阶段:简单来说,这个阶段就是根据Java虚拟机规范,来校验你加载进来的".class"文件中的内容,是否符合指定的规范。这个相信很好理解,假如说,你的".class"文件被人篡改了,里面的字节码压根儿不符合规范,那么JVM是没法去执行这个字节码的,所以把".class"加载到内存里之后,必须先验证一下,校验他必须完全符合JVM规范,后续才能交给JVM来运行。

        准备阶段:这个也容易理解,我们写好的类里面,都会有些类变量,就比如下面“Contended” 这个类:

public class Contended {
    public static final int CACHE_LINE;
}

        假设你有这么一个“Contended类,他的"Contended.class"文件内容刚刚被加载到内存之后,会进行验证,确认这个字节码文件的内容是规范的接着就会进行准备工作。这个准备工作,其实就是给这个"Contended"类分配一定的内存空间,然后给他里面的类变量(也就是static修饰的变量)分配内存空间,来一个默认的初始值,比如上面的例子中,就会给“CACHE_LINE”这个变量分配内存空间,给了‘‘0’’ 这个初始值。

        解析阶段:这个阶段干的事儿,实际上是把符号引用替换为直接引用的过程,其实这个部分的内容很复杂,涉及到JVM的底层,这里不做过多的介绍了。

        初始化阶段:前面说了在准备阶段时,就会把我们的"Contended"类给分配好内存空间,另外他的一个类变量"CACHE_LINE"也会给一个默认的初始值"0",那么接下来,在初始化阶段,就会正式执行我们的类初始化的代码了。

到底什么是类的初始化代码?我们来继续看下面的代码:

public class Contended {
    public static final int CACHE_LINE = Integer.getInteger("Intel.CacheLineSize", 64);
}

我们可以看到,对于“CACHE_LINE” 这个类变量,我们是打算通过“Integer.getInteger("Intel.CacheLineSize", 64);” 这段代码获取一个值,来赋值给他,但是他会在准备阶段就给他赋值么,显然是不会的,在准备阶段只会给他分配一个内存空间,并且赋值“0”给他而已。

        那么,这个变量的值是在什么时候给他赋值呢,答案就是“初始化“这个阶段。在这个阶段,就会执行类的初始化代码,比如上面的 Integer.getInteger("Intel.CacheLineSize", 64);代码就会在 这里执行,完成一个配置项的读取,然后赋值给这个类变量"CACHE_LINE"。我们搞明白了类的初始化是什么,就得来看看类的初始化的规则了。

        什么时候会初始化一个类?

        一般来说有以下一些时机:比如"new Contended("来实例化类的对象了,此时就会触发类的加载到初始化的全过程,把这个类准备好,然后再实例化一个对象出来;或者是包含"main()"方法的主类,必须是立马初始化的。

        此外,这里还有一个非常重要的规则,就是如果初始化一个类的时候,发现他的父类还没初始化,那么必须先初始化他的父类。

小结:是在实例化对象的时候,类的实例化的过程也就变成了,类被加载到内存后,通过验证、准备解析以及初始化赋值后,再接着进行实例化对象;遇到有父类的时候,Object类是所有类的父类,也就是要先初始化Object类之后才会初始化这个类。

好了,类的加载过程我们先到这吧,此时又有问题了,既然是这个类是用到时候去加载,那么如果我在这个类中,同时用到了多个引用怎么办,就没有个先后顺序么?别担心,这个JVM 早就想到了,他就是类加载机制,双亲委派机制,类加载器都有那些?双亲委派机制又是什么?我们下面接着来看。

4. 类加载机制

        通过上面内容相信大家对整个类加载从触发时机到初始化的过程都明白了,我们接着说下类的加载器概念,类加载器就是加载的工具,类的加载过程必须依靠类加载器来进行实现。

4.1 类加载器

        既然类加载器这么重要,我们就先看下类的加载器都有那些:

        启动类加载器Bootstrap ClassLoader,他主要是负责加载我们在机器上安装的Java目录下的核心类的。就是在你java安装目录下 "lib" 目录下的核心类库。

        相信大家都知道,如果你要在一个机器上运行自己写好的Java系统,无论是windows笔记本,还是linux服务器,是不是都得装一下JDK?那么在你的Java安装目录下,就有一个"lib"目录,大家可以自己去找找看,这里就有Java最核心的一些类库支撑你的Java系统的运行。所以,一旦你的JVM启动,那么首先就会依托启动类加载器,去加载你的Java安装目录下的"lib"目录中的核心类库。

        扩展类加载器Extension ClassLoader,这个类加载器也是在你Java安装目录下,有一个"lib\ext"目录。这里面有一些类,就是需要使用这个类加载器来加载的,支撑你的系统的运行。

        应用程序类加载器Application ClassLoader,这类加载器就负责去加载"ClassPath"环境变量所指定的路径中的类,其实你就可以理解成是我们自己实现的java代码就可以了。

        自定义类加载器:这个类加载器就是说,你也可以自己去实现一些类加载器,去根据你自己的需求加载你的类。

4.2 双亲委派机制

        我们看完了类加载器,那么类加载要有个加载的规则吧,那就是双亲委派规则:JVM的类加载器是有亲子层级结构的,就是说启动类加载器是最上层的,扩展类加载器在第二层,第三层是应用程序类加载器,最后一层是自定义类加载器。然后基于这种亲子层级结构,就是双亲委派机制:就是先去找父亲去加载,不行的话再由儿子来加载,这样就能避免多层级的加载器结构重复加载某些类。双亲委派规则可以看下图:

我们在上面说了一堆东西,那具体是怎么个操作呢,我们看下一个例子:

        假设你的JVM虚拟机要加载“Contended” 类,此时他作为应用程序类,会去找他的上级也就父亲,告诉他,你先在你的背包里面找找,万一在你包里呢,父类作为扩展类,还有上级,他想想还是交给父亲吧,万一他那边有呢,这样就把事情交给了启动类加载器,启动类上面没有了,他是最高级别,他就开始找,找了半天没有找到,他就告诉儿子,扩展类,我这边没有,你在你那边找找吧,扩展类就开始找,找了半天发现也没有找到,就交给了亲儿子应用程序类,应用程序类说,好吧,我就在我背包里面找吧,结果找到了,就可以加载实例化了。

 上面的一个寻找的过程就是双亲委派的一个过程,有人问了,为啥要这样设计呢,不这样设计不行么?这个机制规则不能够打破么?这样设计主要是避免加载到内存中的字节码有重复的存在,避免造成不必要的内存消耗。另外一个问题,我们当然可以打破双亲委派规则,方法很简单,就是我们自定义一个类加载器,让他加载某个类的时候不走启动类就可以了,最典型的案例就是tomcat了。当然有的人会说JDBC的驱动好像也是定义了,自定义加载器可以加载各个厂商自定义的类,确实是这样,但是他并没有打破,他加载其他的类还是走的双亲委派的机制,只是引用了自定义加载器的特性而已。

5. 小结:

        好了,今天就先说到这个地方吧。下面我们一起来梳理下作下小结:

        1、Java的整体运行流程是我们编写的JAVA代码,通过IDEA编译成.class字节码文件,然后打包生成.jar 结尾的jar包,通过java -jar 命令运行,启动jvm虚拟机,这样的一个过程;

       2、jvm启动后,会先去找带有main 函数的启动入口类进行加载,就会开启类的加载过程,同时类的加载需要遵循双亲委派机制,先通过父类加载,然后在交由子类进行加载;

        3、要实例化一个对象,先通过根类加载器,去查询有没有这个类,发现没有,再交给扩展类,最后交给应用程序类加载器,找到我们写的java代码编译生成的字节码类,把字节码类加载到内存中,然后经过校验语法,在内存中开辟出了一个空间,然后基本类型数据给赋值,然后通过解析把这些字节码的符号引用,变成直接引用就是内存的指针地址,然后对类的变量进行初始化赋值操作,最后进行对象的实例化。

这里做个小说明哈就是JVM启动后的main函数入口类,和超类Object 这俩谁先加载的顺序是不固定的,取决于类加载器的执行顺序和程序的执行流程,这点要注意,别思考着陷入了误区。

        我们理解了类的加载过程,以及要遵循的加载规则双亲委派机制,加载到内存中,进行实例话,清楚了,又感觉有些问题,比如这个类,在内存中到底是什么样子的,是怎么进行放置的,类加载使用后,不使用了,会怎么样的操作,保留那些,清空销毁那些?这些规则又是什么?这些问题我们放到后面来讲。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1392349.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

openGauss学习笔记-200 openGauss 数据库运维-常见故障定位案例-表文件大小无变化

文章目录 openGauss学习笔记-200 openGauss 数据库运维-常见故障定位案例-表文件大小无变化200.1 VACUUM FULL一张表后,表文件大小无变化200.1.1 问题现象200.1.2 原因分析200.1.3 处理办法 openGauss学习笔记-200 openGauss 数据库运维-常见故障定位案例-表文件大小…

刷卡打印机,后台更换别人绑定的卡,无法生效,怎么解决?

环境: 柯美287打印机 问题描述: 刷卡打印机,后台更换别人绑定的卡,无法生效,怎么解决? 解决方案: 别人之前离职的卡刷了把他后面卡号复制好 2.搜索之前那个离职的用户,打开设置…

JavaScript 入门手册

准备好快速学习 JavaScript了吗? 如果是,那么你需要这份 JavaScript 小抄。它以清晰、简洁和初学者友好的方式介绍了 JavaScript 的基础知识。 将它作为提高 JavaScript 技能的参考或指南。 让我们深入学习。 什么是 JavaScript? JavaSc…

mPLUG-Owl2: 7B的多模态大模型

前言 语言模型:LLama2 - 7B视觉模型:Clip-L 0.3B 痛点 作者任务以往的方法(Qformer,linear层)直接将视觉编码器中的视觉特征映射到冻结的LLMs中,通过利用保留的语言能力执行多模态任务。这种策略限制了…

Springboot整合Canal 实践经验

文章目录 前言一、Canal 服务端:1.1 canal.properties:1.2 canal的监听实例: 二、canal客户端2.1 客户端配置要监听的实例:2.2 通过连接获取信息 总结 前言 本文是Springboot整合Canal 实践过程中经验记录; 一、Canal…

Linux/Networked

Enumeration nmap 网站更新之后有了一个引导模式,更利于学习了,之前看ippsec的视频,要不总是没有思路,现在出现的问题多了提示也更多了,还没有使用,一会用用再说 首先,第一个问题是“目标上正…

2 python快速上手

2 python快速上手 快速上手1.编码(密码本)2.编程初体验3.输出4. 初识数据类型4.1 整形(int)4.2 字符串(str)4.3 布尔类型(bool)4.4 类型转换 5. 变量5.1 变量名的规范5.2 变量内存指…

项目架构之Zabbix部署

1 项目架构 1.1 项目架构的组成 业务架构:客户端 → 防火墙 → 负载均衡(四层、七层) → web缓存/应用 → 业务逻辑(动态应用) → 数据缓存 → 数据持久层 运维架构:运维客户端 → 跳板机/堡垒机&#x…

探索Python数据结构与算法:解锁编程的无限可能

文章目录 一、引言1.1 数据结构与算法对于编程的重要性1.2 Python作为实现数据结构与算法的强大工具 二、列表和元组2.1 列表:创建列表、索引、切片和常用操作2.2 元组:不可变序列的特性和使用场景 三、字符串操作和正则表达式3.1 字符串的常见操作和方法…

本地运行LlaMA 2的简易指南

大家好,像LLaMA 2这样的新开源模型已经变得相当先进,并且可以免费使用。可以在商业上使用它们,也可以根据自己的数据进行微调,以开发专业版本。凭借其易用性,现在可以在自己的设备上本地运行它们。 本文将介绍如何下载…

0003.为什么有的电流表需要使用分流器?

以下两款电流表,你仔细看能有什么发现? 除了量程一个是20A,一个是30A,还有什么区别? 仔细观察你会发现30A的电流表上还有一个20A电流表没有的参数75mV. 是的,这就是他们之间最大的差距。 要测量一…

HTML--基本结构构成

基本结构&#xff1a; 文档声明: <!DOCTYPE html> htm标签对 :<html> </html> head标签对&#xff1a; <head> </head> body标签对&#xff1a;<body> </body> 如下结构&#xff1a; <html> <head> <title>这是一…

修改iview的表格table展开的默认icon和样式

修改前 修改后 修改内容 .title_label_list .ivu-icon-ios-add{font-size: 26px;color: #888888; } .title_label_list .ivu-icon-ios-add:hover{color: #11AAAA; } .title_label_list .ivu-icon-ios-add:before {content: "\F341"; } .title_label_list .ivu-icon-…

JVM工作原理与实战(十八):运行时数据区-堆

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、运行时数据区 二、堆 1.堆介绍 2.关键参数 总结 前言 ​JVM作为Java程序的运行环境&#xff0c;其负责解释和执行字节码&#xff0c;管理内存&#xff0c;确保安全&#xff0c…

Qt/QML编程之路:小键盘keyboard(36)

小键盘对于qml应用是经常用到的,在qml里面,就如一个fileDialog也要自己画一样,小键盘keyboard也是要自己画的,对于相应的每个按键的clicked都要一一实现的。 这里有一个示例: 代码如下: import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Window 2.0 im…

【刷题】 leetcode 2 .两数相加

两数相加 两数相加1 思路一 &#xff08;暴毙版&#xff09;2 思路二 &#xff08;本质出发&#xff09; 谢谢阅读Thanks♪(&#xff65;ω&#xff65;)&#xff89;下一篇文章见&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 两数相加 我们来看…

DQN、Double DQN、Dueling DQN、Per DQN、NoisyDQN 学习笔记

文章目录 DQN (Deep Q-Network)说明伪代码应用范围 Double DQN说明伪代码应用范围 Dueling DQN实现原理应用范围伪代码 Per DQN (Prioritized Experience Replay DQN)应用范围伪代码 NoisyDQN伪代码应用范围 部分内容与图片摘自&#xff1a;JoyRL 、 EasyRL DQN (Deep Q-Networ…

Queue详解(Java)

Queue详解 Java 中的队列&#xff08;Queue&#xff09;是一种数据结构&#xff0c;它遵循先进先出&#xff08;FIFO&#xff09;的原则。队列可以用于在一个集合中保存一组元素&#xff0c;并支持在队列的尾部添加元素&#xff0c;以及在队列的头部移除元素。 Java 标准库提…

CleanMyMac X .4.14.7如何清理 Mac 系统?

细心的用户发现苹果Mac电脑越用越慢&#xff0c;其实这种情况是正常的&#xff0c;mac电脑用久了会产生很多的缓存文件&#xff0c;如果不及时清理会影响运行速度。Mac系统在使用过程中都会产生大量系统垃圾&#xff0c;如不需要的系统语言安装包&#xff0c;视频网站缓存文件&…

Spring全家桶

官网 Spring | Home 一、市面上主流的Spring框架以及简介 Spring Framework&#xff1a;Spring Framework是最基础、最核心的Spring框架&#xff0c;提供了IoC&#xff08;控制反转&#xff09;和AOP&#xff08;面向切面编程&#xff09;等功能。它是其他Spring项目的基础&am…