20张图带你了解JVM运行时数据区

news2025/1/19 8:17:30

运行时数据区总览

内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。不同的JVM对于内存的划分方式和管理机制存在着部分差异。下图就是HotSpot的经典的内存布局:

图中的CodeCache在JVM官方文档中被归于元空间,而在阿里的官方文档中被单独摘了出来,此处区别并不影响我们对它的学习。

Java虚拟机在执行Java程序的过程中,会将涉及到的数据划分到不同的内存区域去管理,而这部分区域就是我们接下来要讲的Java虚拟机的运行时数据区。

如上图所示,我们的运行时数据区分为PC寄存器、方法区、堆、本地方法栈和虚拟机栈五个部分。其中上文中所说的元空间就是方法区的具体落地实现。估计有的老铁会问:不是还有直接内存吗?其实直接内存并不属于运行时数据区的一部分,也不是java虚拟机规范中的区域,它的大小不受java堆大小的限制,是使用Native函数库直接分配的堆外内存,会被频繁使用。它存储着堆与本地方法相关的数据,可以避免在Java堆和Native堆中来回复制数据,能够提高效率。

细心的老铁应该会发现,上图中阿Q用了红蓝两种颜色来区分五个部分,其中红色的方法区和堆是线程间共享的,即它们会随着虚拟机启动而创建,随着虚拟机退出而销毁;而蓝色的部分为每个线程单独享有的,即它们与线程是一一对应的,会随着线程开始和结束而创建和销毁。在HotSpot JVM中,每个线程都与操作系统的本地线程直接映射:当一个java线程准备好执行之后,此时一个操作系统的本地线程也同时创建,java线程执行终止后,本地线程也会回收。操作系统负责所有线程的安排调度到任何一个可用的CPU上,一旦本地线程初始化成功,它就会调用Java现成的run()方法。

我们可以翻看官方文档了解一下Runtime类:

Every Java application has a single instance of class Runtime that allows the application to interface with the environment in which the application is running. The current runtime can be obtained from the getRuntime method.

译: 每个Java应用程序都有一个类运行时实例,该实例允许应用程序与运行应用程序的环境交互。当前运行时可以从getRuntime方法获得。

看到这如果大家对运行数据区还没有大致的概念的话,给大家举个小例子,大家一看便知:

如上图所示,厨师正在烹饪佳肴,我们如果把厨师炒菜比作我们的虚拟机执行代码的话,厨师就是我们后文中将要提到的执行引擎,而厨师后方的工具类和食材就相当于我们的运行时数据区。在写这篇文章的过程中发现知识点有点多,所以阿Q把它分为两部分进行讲解,该篇文章先说一下线程的私有区域:PC寄存器、本地方法栈和虚拟机栈。

PC寄存器(程序计数器)

这里的寄存器并不是广义上所指的物理寄存器,而是对物理寄存器的抽象模拟,把它称为PC计数器(或指令计数器)更为合适。

介绍

Java虚拟机可以一次支持多个执行线程,每个Java虚拟机线程都有其自己的PC寄存器即为线程独有。PC寄存器会随着线程的创建而创建,会随着线程的结束而死亡。正因为程序计数器记录的是指令地址,所以它占用的内存空间较少,因此它是运行速度最快的存储区域,也是唯一一个在Java虚拟机规范中没有规定任何OutOtMemoryError(内存溢出)情况的区域。在任何时候,每个Java虚拟机线程都在执行单个方法的代码,即该线程的当前方法。如果线程当前正在执行的方法不是native,则该pc寄存器包含当前正在执行的Java虚拟机指令的地址;如果线程当前正在执行的方法是native,则Java虚拟机的pc寄存器值未定义undefned

作用

PC寄存器的作用就是用来存储指向下一条指令的地址,也就是即将要执行的指令代码,由执行引擎读取该指令并交由cpu执行。它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。我们可以把PC寄存器理解为一个记录着当前线程所执行的字节码的行号指示器,也可以理解为一个游标,来告诉程序按照我指定的顺序执行。接下来用例子来演示下它所处的位置与作用。

例:

如图所示,PC寄存器中存储着指向“操作指令”的“指令地址”。假如现在PC寄存器中存储的指令地址是“5”,则执行引擎会取出对应的操作指令,然后做两件事:一是操作局部变量表、操作数栈等完成数据的存、取、加减等操作;二是将操作指令翻译成CPU能识别的机器指令,最后由CPU执行;此时字节码解释器就会改变PC寄存器中的值为“6”,以此类推。

面试题分析

(1)为什么要使用PC寄存器记录当前线程的执行地址呢?

JVM的多线程是通过CPU时间片轮转(即线程轮流切换并分配处理器执行时间)算法来实现的。也就是说,某个线程在执行过程中可能会因为时间片耗尽而被挂起,而另一个线程获取到时间片开始执行。当被挂起的线程重新获取到时间片的时候,它要想从被挂起的地方继续执行,就必须知道它上次执行到哪个位置,这时候就需要PC寄存器来记录某个线程的字节码执行位置,如果虚拟机是单线程也就没必要用程序计数器记录每个线程的位置了。

(2)PC寄存器为什么会被设定为线程私有呢?

由于jvm的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此为了能够准确的记录各个线程正在执行的当前字节码指令地址,最好的办法自然就是为每一个线程都分配一个PC寄存器。这样各条线程之间计数器互不影响,独立存储。

虚拟机栈

栈的介绍

由于跨平台性的设计,Java的指令都是根据栈来设计的,它遵循“先进先出、后进后出”的原则。它的优点就是跨平台、指令集小,编译器更容易实现。

在这里我们要对“栈”和“堆”做一个简单的区分,其中栈是运行时的单位,它解决的是程序运行的问题,即程序如何执行,或者说是如何处理数据;堆是存储的单位,它解决的是数据存储的问题,即数据怎么放、放在哪。我们举个简单的例子:假如你正在修理汽车,我们可以把修车的操作步骤看做是栈操作,而把汽车的零件一个个放到汽车中就可以看做是堆存储。

虚拟机栈介绍

Java虚拟机栈,早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,所以虚拟机栈是线程私有的,当线程结束时虚拟机栈也就结束了。JVM对虚拟机栈的操作只有进栈和出栈,所以它的访问速度仅次于程序计数器,也是一种快速有效的分配存储方式。对于虚拟机栈来说它不存在垃圾回收问题,但是虚拟机栈的大小是动态的或者固定不变的,所以它会存在栈溢出或者内存溢出问题:

  • 栈溢出:如果采用固定大小的虚拟机栈,那每一个线程的虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过虚拟机栈允许的最大容量,虚拟机栈会抛出StackOverflowError异常。
  • 内存溢出:如果虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那虚拟机将会抛出OutOfMemoryError异常。 栈的大小直接决定了函数调用的最大可达深度,我们可以通过-Xss参数来配置栈内存,追加字母k或K表示KB,m或M表示MB,g或G表示GB,示例:-Xss1m

栈帧的运行原理

虚拟机栈主管Java程序的运行,保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回,那它内部到底是什么构造呢?虚拟机栈内部保存着一个一个的栈帧(Stack Frame),每个栈帧与该线程正在执行的每个方法都是一一对应的。栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧 (Current Frame),与当前栈帧相对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)。执行引擎运行的所有字节码指令只针对当前栈帧进行操作。执行过程如下图:

程序开始执行,首先方法1入栈,为栈帧1,此时栈帧1为当前栈帧;随后方法1调用方法2,方法2入栈,为栈帧2,此时栈帧2为当前栈帧,以此类推;当方法4入栈成为栈帧4并且执行代码,在方法4返回之际,栈帧4会传回方法4的执行结果给栈帧3,接着,虚拟机会丢弃栈帧4即栈帧4出栈,使得栈帧3重新成为当前栈帧,以此类推,直到方法1执行完成,栈帧1出栈,虚拟机栈被回收。

Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令(包含void返回类型);一种是抛出异常(指的是未处理的异常,如果是try...catch过了,算第一种)。不管使用哪种方式,都会导致栈帧出栈。不同线程中所包含的栈帧是不允许存在互相引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。

如图所示,栈帧由局部变量表、操作数栈、动态链接、方法返回地址和一些附加信息组成,接下来就让我们逐个来了解一下吧。

局部变量表 Local Variables

局部变量表也被称之为局部变量数组或本地变量表,实际上是一个“数字”数组,主要用于存储方法的参数和定义在方法体内的局部变量(包括各类基本数据类型、对象引用、returnAddress类型),虚拟机使用局部变量表完成方法返回。因为局部变量表是建立在线程的虚拟机栈上,是线程的私有数据,所以不会存在数据安全问题。另外栈帧的大小主要受局部变量表的影响,而局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maxmum_local_variables数据项中,所以在方法运行期间是不会改变局部变量表的大小的,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。一般来说在,在虚拟机栈大小固定的前提下,它的局部变量表越大,它的栈帧就越大,那它的嵌套调用次数(方法调用数)也就越少,即栈的深度越浅。用几张字节码的图来说明一下局部变量表中的内容:

局部变量表中的数据只有在当前方法中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程,当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。

Solt

参数的存放总是在局部变量数组的索引0开始,到数组长度减1的索引结束,它最基本的存储单元就是Solt(变量槽)。当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量将会按照顺序被复制到局部变量表中的每一个Solt上。JVM会为局部变量表中的每个Slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值。其中32位以内的类型只占用一个solt(包含returnAddress类型,byteshortcharfloat都转化为int类型,而boolean类型是0为false,非0为true),64位的类型(longdouble)占用两个solt。如果需要访问局部变量表中一个64bit的局部变量值时,只需要使用前一个索引即可。

如果当前帧是由构造方法或者实例方法创建的,那么该对象的引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列,而this变量不存在于静态方法的局部变量表中,所以上文中的main方法中不存在this变量。 另外Solt是可以复用的,如果一个局部变量过了其作用域,那么在其作用域之后声明的新的局部变量就很有可能会复用该局部变量的slot,从而达到节省资源的目的。

补充知识点: 变量按照在类中的位置可以分为成员变量和局部变量,其中成员变量又分为类变量和实例变量。

  1. 成员变量在使用前,都会默认初始化赋值,其中类变量是在类加载子系统的准备阶段进行默认赋值,在初始化阶段显示赋值;
  2. 实例变量会随着对象的创建,在堆空间中分配实例变量空间,并进行默认赋值;
  3. 局部变量是不会进行默认赋值的,所以在使用前必须进行显示赋值,否则编译不通过。

局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或者间接引用的对象都不会被回收。

操作数栈 Operand Stack

操作数栈又称为表达式栈,在方法执行的过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈和出栈。操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。每一个操作数栈都会拥有一个明确的栈深度用于存储数据值,其所需要的最大深度在编译期间就定义好了,保存在方法的code属性中,为max_stack的值(与上边局部变量表类似)。栈中的元素可以是任意的Java数据类型,其中32bit的用一个栈单位深度,64bit的用两个栈单位深度。操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,这由编译器在编译期间进行验证,同时在类加载过程中的类检验阶段的数据流分析阶段要再次验证。我们所说的Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。有了上述的理论,估计你会是这样的

阿Q特地制作了一张动态图来说明一下字节码指令执行时PC寄存器、局部变量表和操作数栈的运行过程:在编译期间局部变量表和操作数栈的大小已经确定了:

  1. 首先将要执行的指令地址0存放到PC寄存器中,此时,局部变量表和操作数栈的数据为空;
  2. 当执行第一条指令bipush时,将操作数15放入操作数栈中,然后将PC寄存器的值置为下一条指令的执行地址,即2
  3. 当执行指令地址为2的操作指令时,将操作数栈中的数据取出来,存到局部变量表的1位置,因为该方法是实例方法,所以0位置存的是this的值,PC寄存器中的值变为3;
  4. 同步骤2和3将8先放入操作数栈,然后取出来存到局部变量表中,PC寄存器中的值也由3->5->6
  5. 当执行到地址指令为678时,将局部变量表中索引位置为12的数据重新加载到操作数栈中并进行iadd加操作,将得到的结果值存到操作数栈中,PC寄存器中的值也由6->7->8->9
  6. 执行操作指令istore_3,将操作数栈中的数据取出存到局部变量表中索引为3的位置,执行return指令,方法结束。

如果被调用的方法带有返回值,其返回值会被压入当前栈帧的操作数栈中,并更新pc寄存器中下一条需要执行的字节码指令。

栈顶缓存技术:将栈顶的元素全部缓存到物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率。

动态链接 Dynamic Linking

在Java源文件被编译成字节码文件时,所有的变量和方法引用都作为符号引用保存在class文件的常量池中。

当字节码文件被加载到虚拟机后,字节码文件中的一些数据,如类型信息、域信息、方法信息等,就会被放置到方法区中,而字节码文件中的常量池则会进入方法区中的运行时常量池。每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接。动态链接就是在“类加载”中“链接”的“解析阶段”将符号引用转化为直接引用的过程。

为什么字节码文件需要常量池?因为字节码文件需要数据支持,通常这种数据会很大,以至于不能直接存放到字节码中,换一种方式,可以将指向这些数据的符号引用存到字节码文件的常量池中,这样字节码只需使用常量池就可以在运行时通过动态链接找到相应的数据并使用。

方法返回地址 Return Address

方法返回地址是用来存放调用该方法的PC寄存器的值的。我们都知道,方法的结束有两种方式:一种是正常执行完成;一种是出现未处理的异常,非正常退出。无论哪种方式退出,在方法退出后都返回到该方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层主调方法的执行状态。方法正常退出时,当前线程的pc寄存器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定的,栈帧中一般不会保存这部分信息。本质上,方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层方法的局部变量表、操作数栈,将返回值压入调用者栈帧的操作数栈,设置PC寄存器的值等,让调用者方法继续执行下去。

按照方法完成出口方式的不同又分为正常完成出口和异常完成出口:

  • 正常完成出口的字节码指令中的返回值类型为ireturnbooleanbytecharshortint)、lreturnlong)、freturnfloat)、dreturndouble)、areturn(引用类型)和return(void、实例初始化方法、类和接口的初始化方法)。
  • 在方法执行过程中遇到了异常,并且这个异常没有在方法内进行处理,也就是只要在本方法的异常处理表中没有搜索到匹配的异常处理器,就会导致方法的退出,简称异常完成出口。异常处理表是用来存储方法执行过程中抛出异常时的异常处理的,方便在发生异常的时候找到处理异常的代码。

两种方式的本质区别就是异常完成出口退出时不会给他的上层调用者产生任何的返回值。

一些附加信息

栈帧中还允许携带与Java虚拟机实现相关的一些附加信息,例如对程序调试提供支持的信息。

本地方法栈

要说起本地方法栈,我们先来介绍一下本地方法。

本地方法 Native Method

首先本地方法是不在运行时数据区中的,它的位置如图所示:

本地方法其实就是java调用非java代码的接口,该接口由非java语言实现。本地接口的作用是融合不同的编程语言为java所用,它的初衷是融合C/C++程序。

native可以与所有其他的java标识符连用,但是abstract除外。

为什么要使用Native Method

  1. 与Java环境外交互:有时候java应用需要与java外边的环境进行交互;
  2. 与操作系统进行交互:使用本地方法,我们可以用java实现jre与底层系统的交互;
  3. Sun's Java:Sun的解释器是用C实现的,这使得它能像一些普通的C一样与外部交互。

本地方法栈 Native Method Stack

本地方法栈是用来管理本地方法的调用的,也是线程私有的。他也允许被实现成固定或者可动态扩展的内存大小,在内存溢出方面与虚拟机栈类似。本地方法栈的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载本地方法库。

当某个线程调用本地方法时,他就进入了一个全新的并且不再受虚拟机限制的世界,他和虚拟机拥有同样的权限:

  • 本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区;
  • 可以直接使用本地处理器中的寄存区;
  • 直接从本地内存的堆中分配任意数量的内存。

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

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

相关文章

C 程序设计教程(11)—— 字符数据的输入与输出

C 程序设计教程(11)—— 字符数据的输入与输出 该专栏主要介绍 C 语言的基本语法,作为《程序设计语言》课程的课件与参考资料,用于《程序设计语言》课程的教学,供入门级用户阅读。 目录C 程序设计教程(11&…

【2004NOIP普及组】T2.花生采摘 试题解析

【2004NOIP普及组】T2.花生采摘 试题解析 时间限制: 1000 ms 内存限制: 65536 KB 【题目描述】 鲁宾逊先生有一只宠物猴,名叫多多。这天,他们两个正沿着乡间小路散步,突然发现路边的告示牌上贴着一张小小的纸条:“欢迎免费品尝我种的花生!——熊字”。 鲁宾逊先生…

Android---Chip

Chip Chip 代表一个小块中的复杂实体,如联系人。它是一个圆形按钮,由一个标签,一个可选的芯片图标和一个可选的关闭图标组成。如果 Chip 可检查,则可以点击或切换Chip 。 style"style/Widget.MaterialComponents.Chip.Action…

疫情在家搭建的简单易学的SLAM建图机器人

1 简介 Easy_mqOS 是我仿照ROS 搭建的基于MQTT的简易机器人分布式开发框架,是一种轻量级并且十分容易上手的框架,支持多个节点的主题的订阅和单topic发布,节点之间独立、解耦合。没有复杂的文件配置,一定的make编程基础,像正常启动服务一样&a…

Redis未授权访问漏洞(四)SSH key免密登录

前言 系列文章 Redis未授权访问漏洞(一)先导篇 Redis未授权访问漏洞(二)Webshell提权篇 Redis未授权访问漏洞(三)Redis写入反弹连接定时任务 SSH key免密登录 实战实验 环境准备 实验前我们先来复习一遍ssh-key免密登录的流程 攻击机: Centos7 IP:192.168.142.44 靶…

局部变量的特点以及成员变量的区别

1. 概念在上面的章节中,其实已经跟大家介绍了局部变量的概念。即:局部变量是在定义形参、方法或代码块内部的变量,该变量只在当前方法、代码块中有效。2. 特点局部变量具有如下特点:● 局部变量声明在方法、构造方法或者代码块、形…

Mask RCNN网络源码解读(Ⅴ) --- Mask R-CNN论文解读环境配置以及训练脚本解析

目录 1.源码地址 2.项目配置 2.1 环境配置 2.2 文件结构 2.3 预训练权重下载地址(下载后放入当前文件夹中) 2.4 数据集:本例程使用的有COCO2017数据集和Pascal VOC2012数据集 2.4.1 COCO2017数据集 2.4.2 Pascal VOC2012数据集 2…

matplotlib+cartopy+geopandas,实现专业地图可视化!

知乎上有人问如何实现精细化地图?现有的excel、tableau、powerbi都只能套用有限的模板,是否有工具能实现高度定制化?除了专业的Gis软件外,我能想到相对完美的就是使用Python来实现。如果想制作出版级的地图可视化图表,…

《UEFI内核导读》UEFI Application Binary Interface (ABI)简介

敬请关注:“固件C字营 UEFI根据CPU体系结构和编译器的不同有着不同的“调用约定”统称之为“EFI ABI”。以MSVC和x86/x64举例来说,默认MSVC/x86使用 “C标准cdecl”,MSVC/x64使用“MSVC x64 ABI”。Gcc/x86使用“C标准cdecl”,Gc…

ESP-IDF:使用vector和deque容器进行打分排序例程

ESP-IDF实现例程&#xff1a; /5位选手&#xff0c;分别打十个分数&#xff0c;取中间8个分数&#xff0c;求平均值&#xff0c;然后根据选手的分数排序输出/ #include <stdio.h> #include using namespace std; #include #include #include #include class playe…

mongoDB原子操作事务

原子操作 原子操作&#xff08;atomic operation&#xff09;指的是由多步操作组成的一个操作。如果该操作不能原子地执行&#xff0c;则要么执行完所有步骤&#xff0c;要么一步也不执行&#xff0c;不可能只执行所有步骤的一个子集。不可中断的一个或者一系列操作, 也就是不…

Vue3——第三章(生命周期钩子)

一、setup() Vue3在组合式 API中去掉了在Vue3中的beforeCreate、created两个生命周期&#xff0c;使用setup()来顶替这两个生命周期。 二、onBeforeMount() 注册一个钩子&#xff0c;在组件被挂载之前被调用。当这个钩子被调用时&#xff0c;组件已经完成了其响应式状态的设…

国产电源芯片DP4054 软硬件兼容TP4054 规格书资料

DP4054 是一款完整的采用恒定电流/恒定电压单 节锂离子电池充电管理芯片。其SOT小封装和较少的外部元件数目使其成为便携式应用的理想器件&#xff0c;DP4054 可以适合USB 电源和适配器电源工作。跟进口的TP4054完全兼容&#xff0c;软硬件无需更改直接替换。 管脚配置 功能框…

(十六)一篇文章学会Java的常用API

目录 前言: 一、Object:toStringequals 二、StringBuilder 三、Math 四、System 五、BigDecimal 前言: API的主要目的是提供应用程序与开发人员以访问一组例程的能力&#xff0c;而又无需访问源码&#xff0c;或理解内部工作机制的细节。提供API所定义的功能的软件称作此API的实…

The Sandbox Game Maker 全新版本即将推出,一览可用于娱乐、社交和音乐会的新功能!

0.8 版本的新功能将包括多人游戏功能、光影和视觉效果升级&#xff0c;以及音频和视频流&#xff0c;满足社区的更新需求。 简要概括 0.8 版本包括了 The Sandbox 社区的主要升级需求&#xff0c;具体有&#xff1a; 全新多人游戏功能 全新光影和视觉效果 视频和音频流 支援…

OKhttp-基本工作流程责任链模式原理

OKhttp工作的大致流程 整体流程 &#xff08;1&#xff09;、当我们通过OkhttpClient创立一个okHttpClient 、Request 、Call&#xff0c;并发起同步或者异步请求时&#xff1b; &#xff08;2&#xff09;、okhttp会通过Dispatcher对我们所有的Call&#xff08;RealCall实现…

微服务的版本号要怎么设计?

今天我们来聊一下微服务项目中的版本号要怎么设计。 小伙伴们平时看到的项目版本号&#xff0c;基本上都是分为了三部分 X.Y.Z&#xff0c;版本升级的时候版本号都会变&#xff0c;那么版本号怎么变&#xff0c;这可不是拍脑门决定的&#xff0c;今天我们就一起来探讨一下这个…

Live800:客户服务的三重境界,你做到了吗?

毋庸置疑&#xff0c;赢得客户的青睐是维系自身经济长青的基础。想要客户满意&#xff0c;得到最佳的客户评价&#xff0c;企业就需要为客户提供超出他们期望的服务。有人将客户服务分为三重境界:第一重境界&#xff0c;把分内的服务做精&#xff1b;第二重境界&#xff0c;把额…

libtorch c++复现cycle gan网络

目录 1. 原论文论文&#xff1a;https://arxiv.org/abs/1703.10593 2. 代码 2.1 下采样 2.2 残差块 2.3 上采样模块 2.4 生成器代码 3. 判别器 3.1 判别器组件 3. 2 判别器 4. 训练 4.1 输入数据 4.2 生成器loss函数结构图 4.3 判别器loss结构图 1. 原论文 论文&…

【java查漏补缺】网络编程

网络编程实际上就是通过套接字进行连接后进行通信&#xff0c;本质还是程序进行IO操作。 所谓套接字&#xff0c;实际上就是IP地址加上端口号的组合&#xff0c;通过套接字&#xff0c;可以连接到网络中某一台计算机的某一个进程。 下面就是客户端和服务器的简单例子&#xf…