当前位置: 首页 > news >正文

Java代码是如何被CPU狂飙起来的?

无论是刚刚入门Java的新手还是已经工作了的老司机,恐怕都不容易把Java代码如何一步步被CPU执行起来这个问题完全讲清楚。但是对于一个Java程序员来说写了那么久的代码,我们总要搞清楚自己写的Java代码到底是怎么运行起来的。另外在求职面试的时候这个问题也常常会聊到,面试官主要想通过它考察求职同学对于Java以及计算机基础技术体系的理解程度,看似简单的问题实际上囊括了JVM运行原理、操作系统以及CPU运行原理等多方面的技术知识点。我们一起来看看Java代码到底是怎么被运行起来的。

Java如何实现跨平台

在介绍Java如何一步步被执行起来之前,我们需要先弄明白为什么Java可以实现跨平台运行,因为搞清楚了这个问题之后,对于我们理解Java程序如何被CPU执行起来非常有帮助。

为什么需要JVM

write once run anywhere曾经是Java响彻编程语言圈的slogan,也就是所谓的程序员开发完java应用程序后,可以在不需要做任何调整的情况下,无差别的在任何支持Java的平台上运行,并获得相同的运行结果从而实现跨平台运行,那么Java到底是如何做到这一点的呢?

其实对于大多数的编程语言来说,都需要将程序转换为机器语言才能最终被CPU执行起来。因为无论是如Java这种高级语言还是像汇编这种低级语言实际上都是给人看的,但是计算机无法直接进行识别运行。因此想要CPU执行程序就必须要进行语言转换,将程序语言转化为CPU可以识别的机器语言。

学过计算机组成原理的同学肯定都知道,CPU内部都是用大规模晶体管组合而成的,而晶体管只有高电位以及低电位两种状态,正好对应二进制的0和1,因此机器码实际就是由0和1组成的二进制编码集合,它可以被CPU直接识别和执行。

但是像X86架构或者ARM架构,不同类型的平台对应的机器语言是不一样的,这里的机器语言指的是用二进制表示的计算机可以直接识别和执行的指令集集合。不同平台使用的CPU不同,那么对应的指令集也就有所差异,比如说X86使用的是CISC复杂指令集而ARM使用的是RISC精简指令集。所以Java要想实现跨平台运行就必须要屏蔽不同架构下的计算机底层细节差异。因此,如何解决不同平台下机器语言的适配问题是Java实现一次编写,到处运行的关键所在。

那么Java到底是如何解决这个问题的呢?怎么才能让CPU可以看懂程序员写的Java代码呢?其实这就像在我们的日常生活中,如果双方语言不通,要想进行交流的话就必须中间得有一个翻译,这样通过翻译的语言转换就可以实现双方畅通无阻的交流了。打个比方,一个中国厨师要教法国厨师和阿拉伯厨师做菜,中国厨师不懂法语和阿拉伯语,法国厨师和阿拉伯厨师不懂中文,要想顺利把菜做好就需要有翻译来帮忙。中国厨师把做菜的菜谱告诉翻译者,翻译者将中文菜谱转换为法文菜谱以及阿拉伯语菜谱,这样法国厨师和阿拉伯厨师就知道怎么做菜了。

因此Java的设计者借助了这样的思想,通过JVM(Java Virtual Machine,Java虚拟机)这个中间翻译来实现语言转换。程序员编写以.java为结尾的程序之后通过javac编译器把.java为结尾的程序文件编译成.class结尾的字节码文件,这个字节码文件需要JVM这个中间翻译进行识别解析,它由一组如下图这样的16进制数组成。JVM将字节码文件转化为汇编语言后再由硬件解析为机器语言最终最终交给CPU执行。

所以说通过JVM实现了计算机底层细节的屏蔽,因此windows平台有windows平台的JVM,Linux平台有Linux平台的JVM,这样在不同平台上存在对应的JVM充当中间翻译的作用。因此只要编译一次,不同平台的JVM都可以将对应的字节码文件进行解析后运行,从而实现在不同平台下运行的效果。

那么问题又来了,JVM是怎么解析运行.class文件的呢?要想搞清楚这个问题,我们得先看看JVM的内存结构到底是怎样的,了解JVM结构之后这个问题就迎刃而解了。

JVM结构

JVM(Java Virtual Machine)即Java虚拟机,它的核心作用主要有两个,一个是运行Java应用程序,另一个是管理Java应用程序的内存。它主要由三部分组成,类加载器、运行时数据区以及字节码执行引擎。

类加载器

类加载器负责将字节码文件加载到内存中,主要经历加载-》连接-》实例化三个阶段完成类加载操作。

另外需要注意的是.class并不是一次性全部加载到内存中,而是在Java应用程序需要的时候才会加载。也就是说当JVM请求一个类进行加载的时候,类加载器就会尝试查找定位这个类,当查找对应的类之后将他的完全限定类定义加载到运行时数据区中。

运行时数据区

JVM定义了在Java程序运行期间需要使用到的内存区域,简单来说这块内存区域存放了字节码信息以及程序执行过程数据。运行时数据区主要划分了堆、程序计数器虚拟机栈、本地方法栈以及元空间数据区。其中堆数据区域在JVM启动后便会进行分配,而虚拟机栈、程序计数器本地方法栈都是在常见线程后进行分配。

不过需要说明的是在JDK 1.8及以后的版本中,方法区被移除了,取而代之的是元空间(Metaspace)。元空间与方法区的作用相似,都是存储类的结构信息,包括类的定义、方法的定义、字段的定义以及字节码指令。不同的是,元空间不再是JVM内存的一部分,而是通过本地内存(Native Memory)来实现的。在JVM启动时,元空间的大小由MaxMetaspaceSize参数指定,JVM在运行时会自动调整元空间的大小,以适应不同的程序需求。

字节码执行引擎

字节码执行引擎最核心的作用就是将字节码文件解释为可执行程序,主要包含了解释器、即使编译以及垃圾回收器。字节码执行引擎从元空间获取字节码指令进行执行。当Java程序调用一个方法时,JVM会根据方法的描述符和方法所在的类在元空间中查找对应的字节码指令。字节码执行引擎从元空间获取字节码指令,然后执行这些指令。

JVM如何运行Java程序

在搞清楚了JVM的结构之后,接下来我们一起来看看天天写的Java代码是如何被CPU飙起来的。一般公司的研发流程都是产品经理提需求然后程序员来实现。所以当产品经理把需求提过来之后,程序员就需要分析需求进行设计然后编码实现,比如我们通过Idea来完成编码工作,这个时候工程中就会有一堆的以.java结尾的Java代码文件,实际上就是程序员将产品需求转化为对应的Java程序。但是这个.java结尾的Java代码文件是给程序员看的,计算机无法识别,所以需要进行转换,转换为计算机可以识别的机器语言。

通过上文我们知道,Java为了实现write once,run anywhere的宏伟目标设计了JVM来充当转换翻译的工作。因此我们编写好的.java文件需要通过javac编译成.class文件,这个class文件就是传说中的字节码文件,而字节码文件就是JVM的输入。

当我们有了.class文件也就是字节码文件之后,就需要启动一个JVM实例来进一步加载解析.class字节码。实际上JVM本质其实就是操作系统中的一个进程,因此要想通过JVM加载解析.class文件,必须先启动一个JVM进程。JVM进程启动之后通过类加载器加载.class文件,将字节码加载到JVM对应的内存空间。

当.class文件对应的字节码信息被加载到中之后,操作系统会调度CPU资源来按照对应的指令执行java程序。

以上是CPU执行Java代码的大致步骤,看到这里我相信很多同学都有疑问这个执行步骤也太大致了吧。哈哈,别着急,有了基本的解析流程之后我们再对其中的细节进行分析,首先我们就需要弄清楚JVM是如何加载编译后的.class文件的。

字节码文件结构

要想搞清楚JVM如何加载解析字节码文件,我们就先得弄明白字节码文件的格式,因为任何文件的解析都是根据该文件的格式来进行。就像CPU有自己的指令集一样,JVM也有自己一套指令集也就是Java字节码,从根上来说Java字节码是机器语言的.class文件表现形式。字节码文件结构是一组以 8 位为最小单元的十六进制数据流,具体的结构如下图所示,主要包含了魔数、class文件版本、常量池、访问标志、索引、字段表集合、方法表集合以及属性表集合描述数据信息。

这里简单说明下各个部分的作用,后面会有专门的文章再详细进行阐述。

魔数与文件版本

魔数的作用就是告诉JVM自己是一个字节码文件,你JVM快来加载我吧,对于Java字节码文件来说,其魔数为0xCAFEBABE,现在知道为什么Java的标志是咖啡了吧。而紧随魔数之后的两个字节是文件版本号,Java的版本号通常是以52.0的形式表示,其中高16位表示主版本号,低16位表示次版本号。。

常量池

在常量池中说明常量个数以及具体的常量信息,常量池中主要存放了字面量以及符号引用这两类常量数据,所谓字面量就是代码中声明为final的常量值,而符号引用主要为类和接口的完全限定名、字段的名称和描述符以及方法的名称以及描述符。这些信息在加载到JVM之后在运行期间将符号引用转化为直接引用才能被真正使用。常量池的第一个元素是常量池大小,占据两个字节。常量池表的索引从1开始,而不是从0开始,这是因为常量池的第0个位置是用于特殊用途的。

访问标志

类或者接口的访问标记,说明类是public还是abstract,用于描述该类的访问级别和属性。访问标志的取值范围是一个16位的二进制数。

索引

包含了类索引、父类索引、接口索引数据,主要说明类的继承关系。

字段表集合

主要是类级变量而不是方法内部的局部变量。

方法表集合

主要用来描述类中有几个方法,每个方法的具体信息,包含了方法访问标识、方法名称索引、方法描述符索引、属性计数器、属性表等信息,总之就是描述方法的基础信息。

属性表集合

方法表集合之后是属性表集合,用于描述该类的所有属性。属性表集合包含了所有该类的属性的描述信息,包括属性名称、属性类型、属性值等等。

解析字节码文件

知道了字节码文件的结构之后,JVM就需要对字节码文件进行解析,将字节码结构解析为JVM内部流转的数据结构。大致的过程如下:

1、读取字节码文件

JVM首先需要读取字节码文件的二进制数据,这通常是通过文件输入流来完成的。

2、解析字节码

JVM解析字节码的过程是将字节码文件中的二进制数据解析为Java虚拟机中的数据结构。首先JVM首先会读取字节码文件的前四个字节,判断魔数是否为0xCAFEBABE,以此来确认该文件是否是一个有效的Java字节码文件。JVM接着会解析常量池表,将其中的常量转换为Java虚拟机中的数据结构,例如将字符串常量转换为Java字符串对象。解析类、接口、字段、方法等信息:JVM会依次解析类索引、父类索引、接口索引集合、字段表集合、方法表集合等信息,将这些信息转换为Java虚拟机中的数据结构。最后,JVM将解析得到的数据结构组装成一个Java类的结构,并将其放入元空间中。

在完成字节码文件解析之后,接下来就需要类加载器闪亮登场了,类加载器会将类文件加载到JVM内存中,并为该类生成一个Class对象。

类加载

加载器启动

我们都知道,Java应用的类都是通过类加载器加载到运行时数据区的,这里很多同学可能会有疑问,那么类加载器本身又是被谁加载的呢?这有点像先有鸡还是先有蛋的灵魂拷问。实际上类加载器启动大致会经历如下几个阶段:

1、以linux系统为例,当我们通过"java"启动一个Java应用的时候,其实就是启动了一个JVM进程实例,此时操作系统会为这个JVM进程实例分配CPU、内存等系统资源;

2、"java"可执行文件此时就会解析相关的启动参数,主要包括了查找jre路径、各种包的路径以及虚拟机参数等,进而获取定位libjvm.so位置,通过libjvm.so来启动JVM进程实例;

3、当JVM启动后会创建引导类加载器Bootsrap ClassLoader,这个ClassLoader是C++语言实现的,它是最基础的类加载器,没有父类加载器。通过它加载Java应用运行时所需要的基础类,主要包括JAVA_HOME/jre/lib下的rt.jar等基础jar包;

4、而在rt.jar中包含了Launcher类,当Launcher类被加载之后,就会触发创建Launcher静态实例对象,而Launcher类的构造函数中,完成了对于ExtClassLoader及AppClassLoader的创建。Launcher类的部分代码如下所示:

public class Launcher {private static URLStreamHandlerFactory factory = new Factory();//类静态实例private static Launcher launcher = new Launcher();private static String bootClassPath = System.getProperty("sun.boot.class.path");private ClassLoader loader;private static URLStreamHandler fileHandler;public static Launcher getLauncher() {return launcher;}//Launcher构造器public Launcher() {ExtClassLoader var1;try {var1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try {this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}Thread.currentThread().setContextClassLoader(this.loader);String var2 = System.getProperty("java.security.manager");if (var2 != null) {SecurityManager var3 = null;if (!"".equals(var2) && !"default".equals(var2)) {try {var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();} catch (IllegalAccessException var5) {} catch (InstantiationException var6) {} catch (ClassNotFoundException var7) {} catch (ClassCastException var8) {}} else {var3 = new SecurityManager();}if (var3 == null) {throw new InternalError("Could not create SecurityManager: " + var2);}System.setSecurityManager(var3);}}...}
复制代码

双亲委派模型

为了保证Java程序的安全性和稳定性,JVM设计了双亲委派模型类加载机制。在双亲委派模型中,启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)以及应用程序类加载器(Application ClassLoader)按照一个父子关系形成了一个层次结构,其中启动类加载器位于最顶层,应用程序类加载器位于最底层。当一个类加载器需要加载一个类时,它首先会委派给它的父类加载器去尝试加载这个类。如果父类加载器能够成功加载这个类,那么就直接返回这个类的Class对象,如果父类加载器无法加载这个类,那么就会交给子类加载器去尝试加载这个类。这个过程会一直持续到顶层的启动类加载器。

通过这种双亲委派模型,可以保证同一个类在不同的类加载器中只会被加载一次,从而避免了类的重复加载,也保证了类的唯一性。同时,由于每个类加载器只会加载自己所负责的类,因此可以防止恶意代码的注入和类的篡改,提高了Java程序的安全性。

数据流转过程

当类加载器完成字节码数据加载任务之后,JVM划分了专门的内存区域内承载这些字节码数据以及运行时中间数据。其中程序计数器、虚拟机栈以及本地方法栈属于线程私有的,堆以及元数据区属于共享数据区,不同的线程共享这两部分内存数据。我们还是以下面这段代码来说明程序运行的时候,各部分数据在Runtime data area中是如何流转的。

public class Test {public static void main(String[] args) {User user  = new User();Integer result = calculate(user.getAge());System.out.println(result);}private static Integer calculate(Integer age) {Integer data = age + 3;return data;}}
复制代码

以上代码对应的字节码指令如下所示:

如上代码所示,JVM创建线程来承载代码的执行过程,我们可以将线程理解为一个按照一定顺序执行的控制流。当线程创建之后,同时创建该线程独享的程序计数器(Program Counter Register)以及Java虚拟机栈(Java Virtual Machine Stack)。如果当前虚拟机中的线程执行的是Java方法,那么此时程序计数器中起初存储的是方法的第一条指令,当方法开始执行之后,PC寄存器存储的是下一个字节码指令的地址。但是如果当前虚拟机中的线程执行的是naive方法,那么程序计数器中的值为undefined。

那么程序计数器中的值又是怎么被改变的呢?如果是正常进行代码执行,那么当线程执行字节码指令时,程序计数器会进行自动加1指向下一条字节码指令地址。但是如果遇到判断分支、循环以及异常等不同的控制转移语句,程序计数器会被置为目标字节码指令的地址。另外在多线程切换的时候,虚拟机会记录当前线程的程序计数器,当线程切换回来的时候会根据此前记录的值恢复到程序计数器中,来继续执行线程的后续的字节码指令。

除了程序计数器之外,字节码指令的执行流转还需要虚拟机栈的参与。我们先来看下虚拟机栈的大致结构,如下图所示,栈大家肯定都知道,它是一个先入后出的数据结构,非常适合配合方法的执行过程。虚拟机栈操作的基本元素就是栈帧,栈帧的结构主要包含了局部变量、操作数栈、动态连接以及方法返回地址这几个部分。

局部变量

主要存放了栈帧对应方法的参数以及方法中定义的局部变量,实际上它是一个以0为起始索引的数组结构,可以通过索引来访问局部变量表中的元素,还包括了基本类型以及对象引用等。非静态方法中,第0个槽位默认是用于存储this指针,而其他参数和变量则会从第1个槽位开始存储。在静态方法中,第0个槽位可以用来存放方法的参数或者其他的数据。

操作数栈

和虚拟机栈一样操作数栈也是一个栈数据结构,只不过两者存储的对象不一样。操作数栈主要存储了方法内部操作数的值以及计算结果,操作数栈会将运算的参与方以及计算结果都压入操作数栈中,后续的指令操作就可以从操作数栈中使用这些值来进行计算。当方法有返回值的时候,返回值也会被压入操作数栈中,这样方法调用者可以获取到返回值。

动态链接

一个类中的方法可能会被程序中的其他多个类所共享使用,因此在编译期间实际无法确定方法的实际位置到底在哪里,因此需要在运行时动态链接来确定方法对应的地址。动态链接是通过在栈帧中维护一张方法调用的符号表来实现的。这张符号表中保存了当前方法中所有调用的方法的符号引用,包括方法名、参数类型和返回值类型等信息。当方法需要调用另一个方法时,它会在符号表中查找所需方法的符号引用,然后进行动态链接,确定方法的具体内存地址。这样,就能够正确地调用所需的方法。

方法返回地址:

当一个方法执行完毕后,JVM会将记录的方法返回地址数据置入程序计数器中,这样字节码执行引擎可以根据程序计数器中的地址继续向后执行字节码指令。同时JVM会将方法返回值压入调用方的操作栈中以便于后续的指令计算,操作完成之后从虚拟机栈中奖栈帧进行弹出。

知道了虚拟机栈的结构之后,我们来看下方法执行的流转过程是怎样的。

1、JVM启动完成.class文件加载之后,它会创建一个名为"main"的线程,并且该线程会自动调用定义在该类中的名为"main"的静态方法,这也是Java程序的入口点;

2、当JVM在主线程中调用当方法的时候就会创建当前线程独享的程序计数器以及虚拟机栈,在Test.class类中,开始执行mian方法 ,因此JVM会虚拟机栈中压入main方法对应的栈帧;

3、在栈帧的操作数栈中存储了操作的数据,JVM执行字节码指令的时候从操作数栈中获取数据,执行计算操作之后再将结果压入操作数栈;

4、当进行calculate方法调用的时候,虚拟机栈继续压入calculate方法对应的栈帧,被调用方法的参数、局部变量和操作数栈等信息会存储在新创建的栈帧中。其中该栈帧中的方法返回地址中存放了main方法执行的地址信息,方便在调用方法执行完成后继续恢复调用前的代码执行;

5、对于age + 3一条加法指令,在执行该指令之前,JVM会将操作数栈顶部的两个元素弹出,并将它们相加,然后将结果推入操作数栈中。在这个例子中,指令的操作码是“add”,它表示执行加法操作;操作数是0,它表示从操作数栈的顶部获取第一个操作数;操作数是1,它表示从操作数栈的次顶部获取第二个操作数;

6、程序计数器中存储了下一条需要执行操作的字节码指令的地址,因此Java线程执行业务逻辑的时候必须借助于程序计数器才能获得下一步命令的地址;

7、当calculate方法执行完成之后,对应的栈帧将从虚拟机栈中弹出,其中方法执行的结果会被压入main方法对应的栈帧中的操作数栈中,而方法返回地址被重置到main现场对应的程序计数器中,以便于后续字节码执行引擎从程序计数器中获取下一条命令的地址。如果方法没有返回值,JVM仍然会将一个null值推送到调用该方法的栈帧的操作数栈中,作为占位符,以便恢复调用方的操作数栈状态。

8、字节码执行引擎中的解释器会从程序计数器中获取下一个字节码指令的地址,也就是从元空间中获取对应的字节码指令,在获取到指令之后,通过翻译器翻译为对应的汇编语言而再交给硬件解析为机器指令,最终由CPU进行执行,而后再将执行结果进行写回。

CPU执行程序 通过上文我们知道无论什么编程语言最终都需要转化为机器语言才能被CPU执行,但是CPU、内存这些硬件资源并不是直接可以和应用程序打交道,而是通过操作系统来进行统一管理的。对于CPU来说,操作系统通过调度器(Scheduler)来决定哪些进程可以被CPU执行,并为它们分配时间片。它会从就绪队列中选择一个进程并将其分配给CPU执行。当一个进程的时间片用完或者发生了I/O等事件时,CPU会被释放,操作系统的调度器会重新选择一个进程并将其分配给CPU执行。也就是说操作系统通过进程调度算法来管理CPU的分配以及调度,进程调度算法的目的就是为了最大化CPU使用率,避免出现任务分配不均空闲等待的情况。主要的进程调度算法包括了FCFS、SJF、RR、MLFQ等。

CPU如何执行指令? 前文中我们大致搞清楚了类是如何被加载的,各部分类字节码数据在运行时数据区怎么流转以及字节码执行引擎翻译字节码。实际上在运行时数据区数据流转的过程中,CPU已经参与其中了。程序的本质是为了根据输入获得相应的输出,而CPU本质就是根据程序的指令一步步执行获得结果的工具。对于CPU来说,它核心工作主要分为如下三个步骤;

1、获取指令

CPU从PC寄存器中获取对应的指令地址,此处的指令地址是将要执行指令的地址,根据指令地址获取对应的操作指令到指令寄存中,此时如果是顺存执行则PC寄存器地址会自动加1,但是如果程序涉及到条件、循环等分支执行逻辑,那么PC寄存器的地址就会被修改为下一条指令执行的地址。

2、指令译码

将获取到的指令进行翻译,搞清楚哪些是操作码哪些是操作数。CPU首先读取指令中的操作码然后根据操作码来确定该指令的类型以及需要进行的操作,CPU接着根据操作码来确定指令所需的寄存器和内存地址,并将它们提取出来。

3、执行指令

经过指令译码之后,CPU根据获取到的指令进行具体的执行操作,并将指令运算的结果存储回内存或者寄存器中。

因此一旦CPU上电之后,它就像一个勤劳的小蜜蜂一样,一直不断重复着获取指令-》指令译码-》执行指令的循环操作。

CPU如何响应中断?

当操作系统需要执行某些操作时,它会发送一个中断请求给CPU。CPU在接收到中断请求后,会停止当前的任务,并转而执行中断处理程序,这个处理程序是由操作系统提供的。中断处理程序会根据中断类型,执行相应的操作,并返回到原来的任务继续执行。

在执行完中断处理程序后,CPU会将之前保存的程序现场信息恢复,然后继续执行被中断的程序。这个过程叫做中断返回(Interrupt Return,IRET)。在中断返回过程中,CPU会将处理完的结果保存在寄存器中,然后从栈中弹出被中断的程序的现场信息,恢复之前的现场状态,最后再次执行被中断的程序,继续执行之前被中断的指令。 那么CPU又是如何响应中断的呢?主要经历了以下几个步骤:

1、保存当前程序状态

CPU会将当前程序的状态(如程序计数器、寄存器、标志位等)保存到内存或栈中,以便在中断处理程序执行完毕后恢复现场。

2、确定中断类型

CPU会检查中断信号的类型,以确定需要执行哪个中断处理程序。

3、转移控制权

CPU会将程序的控制权转移到中断处理程序的入口地址,开始执行中断处理程序。

4、执行中断处理程序

中断处理程序会根据中断类型执行相应的操作,这些操作可能包括保存现场信息、读取中断事件的相关数据、执行特定的操作,以及返回到原来的程序继续执行等。

5、恢复现场

中断处理程序执行完毕后,CPU会从保存的现场信息中恢复原来程序的状态,然后将控制权返回到原来的程序中,继续执行被中断的指令。

后记

很多时候看似理所当然的问题,当我们深究下去就会发现原来别有一番天地。正如阿里王坚博士说的那样,要想看一个人对某个领域的知识掌握的情况,那就看他能就这个领域的知识能讲多长时间。想想的确如此,如果我们能够对某个知识点高度提炼同时又可以细节满满的进行展开阐述,那我们对于这个领域的理解程度就会鞭辟入里。这种检验自己知识学习深度的方式也推荐给大家。

相关文章:

Java代码是如何被CPU狂飙起来的?

无论是刚刚入门Java的新手还是已经工作了的老司机,恐怕都不容易把Java代码如何一步步被CPU执行起来这个问题完全讲清楚。但是对于一个Java程序员来说写了那么久的代码,我们总要搞清楚自己写的Java代码到底是怎么运行起来的。另外在求职面试的时候这个问题…...

Dynamics365安装失败解决及注册编写

一、修改错误昨天登录报错今天开始返回我之前设置的断点开始重新配置,Reporing Services配置完成后发现dynamics365还是下载失败之后下载了一上午dynamics365就一直卡在最后的界面进度条不动索性我直接把所有环境都卸载了 连同虚拟机卸载重装终于在下午的时候dynami…...

Kafka 集群参数

Kafka 集群参数Broker 端参数存储配置ZooKeeper 配置Broker 连接配置Topic 管理配置数据留存配置Topic 级别参数JVM 参数操作系统参数重要的配置 : Broker 端参数,主题级别的参数、JVM 端参数、操作系统级别的参数 Broker 端参数 存储配置 log.dirs:指…...

等保2.0与1.0 测评要求的变化

No.1标准内容增加了 标准内容上最大的变化就是将安全要求分为了安全通用要求和扩展要求。首先,安全通用要求部分已对1.0标准的内容进行了优化,删除或修订了过时的要求项,新增了对新型网络攻击行为防护和个人信息保护等方面的新要求。其次&am…...

nodejs学习巩固笔记-nodejs基础,Node.js 高级编程(核心模块、模块加载机制)

目录Nodejs 基础大前端开发过程中的必备技能nodejs 的架构为什么是 NodejsNodejs 异步 IONodejs 事件驱动架构全局对象全局变量之 process核心模块核心模块 - path全局变量之 Buffer创建 bufferBuffer 实例方法Buffer 静态方法Buffer-split 实现核心模块之FS模块文件操作 APImd…...

2023年春【移动计算技术】文献精读(二)-3 || 附:创新点、创新思想和技术路线总结

榜样的力量是无穷的! 🎯作者主页:追光者♂ 🌸个人简介:2022年CSDN博客之星人工智能领域TOP4🌟、阿里云社区专家博主🏅 CSDN-人工智能领域新星创作者🏆 【无限进步,一起追光!】 🍎欢迎点赞👍 收藏⭐ 留言📝 🌿本篇,仅接着上两篇,为【移动计算技术】…...

企业新闻稿的格式和要求是什么?如何写好新闻稿?

新闻稿是企业自己撰写给媒体的新闻素材,媒体采纳你的稿件后就可以传播到更多的大众面前。 所以企业新闻稿的撰写一方面要让媒体认可,另外一方面是让用户认可你的品牌或是产品。 企业新闻稿的格式和要求是什么?如何写好新闻稿?今…...

String类的底层原理和版本演变

1 String类的底层演变(1) JDK8以及之前版本 (2)JDK9以及之后版本 javaJDK8的字符串存储在char类型的数组里面,在java中,一个char类型占两个字节。但是很多时候,一个字符只需要一个字节就可存储&…...

软考高级信息系统项目管理师系列之二十三:项目采购管理

软考高级信息系统项目管理师系列之二十三:项目采购管理 一、项目采购管理内容整理二、项目采购管理1.采购的定义2.项目采购管理3.战略合作管理三、规划采购1.供应商管理2.采购需求与计划3.规划采购的输入、输出、工具和技术四、实施采购1.采购合同知识2.实施采购的输入、输出、…...

SpringMVC-0308

五、域对象共享数据0、三个域对象范围request:一次请求 第1~6都是向request共享session:一次会话(浏览器开启到浏览器关闭,与服务器关闭无关,session有钝化和活化操作,可以持久化数据&#xff0…...

[数据结构]:14-选择排序(顺序表指针实现形式)(C语言实现)

目录 前言 已完成内容 选择排序实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-PSeqListFunction.cpp 04-SortCommon.cpp 05-SortFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容,除其中使用到C引用外,全为C语言代…...

基于C/C++综合训练 ----- 贪吃蛇

文章目录一、定义结构体对象二、游戏初始化1. 蛇初始化2. 食物初始化3. 围墙初始化4. 界面初始化三、逻辑编程1. 启动游戏2. 打印成绩3. main函数四、细节处理五、程序源码该篇环境为Visual Studio2022 游戏简述 :在控制终端绘画出一个矩阵表示游戏界面(围墙)&…...

Unity 混合操作(Blending)

渲染图形时,在执行所有着色器并应用所有纹理后,像素将写入到屏幕。这些像素与已有像素的组合方式由 Blend 命令控制。用于生成透明对象。《Unity Shader入门精要》大致解释:片元通过了模板测试和深度测试之后,会进行混合步骤。如果…...

Hive建表高阶语句

CTAS -as select方式建表CREATE TABLE ctas_employee as SELECT * FROM employee;CTE (CTAS with Common Table Expression)CREATE TABLE cte_employee AS WITH r1 AS (SELECT name FROM r2 WHERE name Michael), r2 AS (SELECT name FROM employee WHERE gender Male), r3 …...

面向新时代,海泰方圆战略升级!“1465”隆重发布!

过去四年,海泰方圆“1344”战略一直在引领公司前行,搭建了非常坚实的战略框架基座,并推动全员在实践和行动中达成深度共识。 “1344”战略 1个定位,代表着当前机构用户的一组共性需求,密码安全数据治理信创工程。 3…...

带你感受一次JVM调优实战

本文分成两部分,先了解理论,然后再进行实战。 理论篇 1.1 调优目标 JVM调优的两大目标是: 提高应用程序的性能和吞吐量: 通过优化JVM的垃圾回收机制、调整线程池大小和优化代码,可以提高应用程序的性能和吞吐量。…...

ALG和STUN

目录 ALG 应用层网关讲解 Client1使用FTP主动模式建立FTP Client1使用FTP被动模式建立FTP STUN讲解 ALG 应用层网关讲解 用来替换应用层信息 Client1使用FTP主动模式建立FTP 主动模式:服务器收到客户端发来的请求FTP的地址和端口 服务器使用20端口直接向客户端建…...

原生HTML放大镜

该放大区域用背景图片放大 <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compat…...

C++——模板

文章目录1 泛型编程2 函数模板2.1 函数模板概念2.2 函数模板格式2.3 函数模板的实例化2.3.1 隐式实例化2.3.1.1 定义2.3.1.2 代码演示2.3.1.3 运行结果2.3.1.4 缺点2.3.2 显式实例化2.3.2.1 格式2.3.2.2 代码演示2.3.2.3 运行结果2.4 模板参数的匹配原则2.4.12.4.22.4.33 类模板…...

Chapter2.1:线性表基础

该系列属于计算机基础系列中的《数据结构基础》子系列&#xff0c;参考书《数据结构考研复习指导》(王道论坛 组编)&#xff0c;完整内容请阅读原书。 1.线性表的定义和基本操作 1.1 线性表的定义 线性表是具有相同数据类型的n(n≥0)n(n≥0)n(n≥0)个数据元素的有限序列&…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码&#xff0c;实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

Yolov8 目标检测蒸馏学习记录

yolov8系列模型蒸馏基本流程&#xff0c;代码下载&#xff1a;这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中&#xff0c;**知识蒸馏&#xff08;Knowledge Distillation&#xff09;**被广泛应用&#xff0c;作为提升模型…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用

一、方案背景​ 在现代生产与生活场景中&#xff0c;如工厂高危作业区、医院手术室、公共场景等&#xff0c;人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式&#xff0c;存在效率低、覆盖面不足、判断主观性强等问题&#xff0c;难以满足对人员打手机行为精…...

作为测试我们应该关注redis哪些方面

1、功能测试 数据结构操作&#xff1a;验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化&#xff1a;测试aof和aof持久化机制&#xff0c;确保数据在开启后正确恢复。 事务&#xff1a;检查事务的原子性和回滚机制。 发布订阅&#xff1a;确保消息正确传递。 2、性…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...