现在所说的计算机基本上都是冯诺依曼体系的计算机。其核心原理:
冯·诺依曼计算的核心思想是将程序指令和数据以二进制形式存储存储在同一存储器中,并使用相同的数据格式和处理方式来处理它们。这种存储程序的设计理念使得计算机能够以可编程的方式执行不同的任务,而不需要对硬件进行物理修改。
在冯·诺依曼计算中,计算机由以下几个主要组件组成:控制器、运算器、存储器、输入设备、输出设备。
程序是如何加载的存储器(Memory):用于存储程序指令和数据。存储器被划分为地址单元,每个单元存储一个字节的数据。程序和数据按照地址的顺序存储在存储器中,并通过地址来访问。
算术逻辑单元(Arithmetic Logic Unit,ALU)用于执行算术和逻辑操作
控制器(Controller):协调各个组件的操作,控制指令的执行和数据的传输。
输入/输出设备(Input/Output Devices):用于与外部世界进行交互,例如键盘、鼠标、显示器、磁盘驱动器等。
由 冯诺依曼体系,我们知道程序想要执行,必需先加载到计算机的内存中。首先来看一下程序加载过程。这里以 java 程序为例。
- 在IDE或者文本编辑器中写 java 程序,如:
/**
* @author shengjk1
* @date 10/21/23
*/
public class HelloWorld {
public static void main(String[] args) {
System.out.println("HelloWorld!");
}
}
- 编译,编译成 Java 二进制的字节码
javac HelloWorld.java
编译后的文件
- 加载 JVM 中并执行
java HelloWorld
借助 JVM 的类加载器,将 HelloWorld.Class 加载到 JVM 中
- JVM 的内存区域会分为两种:数据区和指令区
数据区包括:
- 堆(Heap):堆是 Java 程序运行时动态分配对象的区域。所有通过 new 关键字创建的对象都存储在堆中。堆是 JVM 中最大的一块内存区域,被所有线程共享。
- 方法区(Method Area):方法区用于存储类的元数据信息,包括类的结构、常量池、静态变量、编译器优化后的代码等。方法区也被称为永久代(Permanent Generation)或元空间(Metaspace)。
- 栈(Stack):栈用于存储方法执行时的局部变量、方法参数、方法调用和返回的信息。每个线程在运行时都有自己的栈,称为线程栈。栈的大小是固定的,并且栈中的数据是按照后进先出(LIFO)的顺序进行操作。
- 本地方法栈(Native Method Stack):本地方法栈类似于栈,但用于执行本地方法(Native Method)的数据和调用信息。
- PC 寄存器(Program Counter Register):PC 寄存器存储着当前线程执行的字节码指令地址。当线程切换时,PC 寄存器的值会保存和恢复。
指令区包括:
指令区用于存储 Java 程序的字节码指令。字节码是 Java 程序的中间表示形式,由编译器生成。JVM 在指令区中解释和执行字节码指令。
如下:
程序在计算机中如何执行- 程序计数器(Program Counter,PC)的初始化:JVM通过将程序计数器(PC)的值设置为程序的入口点( 通常就是main方法 )来初始化。程序计数器是一个特殊的寄存器,它存储下一条要执行的指令的地址。
- 取指:CPU根据程序计数器中存储的地址,从存储器中获取下一条要执行的指令。
- 译码:CPU对获取的指令进行解码,确定其操作类型和所需的操作数。
- 操作数获取:如果指令需要操作数,CPU从存储器或寄存器中获取操作数的值。指令中的地址或寄存器标识符确定操作数的位置。
- 执行:CPU根据指令的操作类型和操作数执行相应的操作。这可能涉及算术运算、逻辑运算、内存访问等。
- 结果存储:执行操作后,CPU将结果存储在存储器或寄存器中,以供后续指令使用或输出到外部设备。
- 更新程序计数器:CPU根据指令的执行情况更新程序计数器的值,以指示下一条要执行的指令的地址。
- 重复执行:重复执行第2步到第7步,直到程序的所有指令都被执行完毕。这样,程序中的指令按照顺序逐条执行,实现了整个程序的功能。