被划定为堆栈摄影区的主存区域,将不允许按随机方式访问。这句话哪里错了呢?

  学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆、栈以及静态数据区。那么在Java语言当中,内存又是如何划分的呢?

  由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分。在讨论JVM内存区域划分之前,先来看一下Java程序具体执行的过程:

  如上图所示,首先Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。因此,在Java中我们常常说到的内存管理就是针对这段空间进行管理(如何分配和回收内存空间)。

  在知道了JVM内存是什么东西之后,下面我们就来讨论一下这段空间具体是如何划分区域的,是不是也像C语言中一样也存在栈和堆呢?

一.运行时数据区包括哪几部分?

  如上图所示,JVM中的运行时数据区应该包括这些部分。在JVM规范中虽然规定了程序在执行期间运行时数据区应该包括这几部分,但是至于具体如何实现并没有做出规定,不同的虚拟机厂商可以有不同的实现方式。

二.运行时数据区的每部分到底存储了哪些数据?

  下面我们来了解一下运行时数据区的每部分具体用来存储程序执行过程中的哪些数据。

Register),也有称作为PC寄存器。想必学过汇编语言的朋友对程序计数器这个概念并不陌生,在汇编语言中,程序计数器是指CPU中的寄存器,它保存的是程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令。

  虽然JVM中的程序计数器并不像汇编语言中的程序计数器一样是物理概念上的CPU寄存器,但是JVM中的程序计数器的功能跟汇编语言中的程序计数器的功能在逻辑上是等同的,也就是说是用来指示 执行哪条指令的。

  由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。

  在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。

  由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。

  Java栈也称作虚拟机栈(Java Vitual Machine Stack),也就是我们常常所说的栈,跟C语言的数据段中的栈类似。事实上,Java栈是Java方法执行的内存模型。为什么这么说呢?下面就来解释一下其中的原因。

  Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。讲到这里,大家就应该会明白为什么 在 使用 递归方法的时候容易导致栈内存溢出的现象了以及为什么栈区的空间不用程序员去管理了(当然在Java中,程序员基本不用关系到内存分配和释放的事情,因为Java有自己的垃圾回收机制),这部分空间的分配和释放都是由系统自动实施的。对于所有的程序设计语言来说,栈这部分空间对程序员来说是不透明的。下图表示了一个Java栈的模型:

  局部变量表,顾名思义,想必不用解释大家应该明白它的作用了吧。就是用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的

  操作数栈,想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。

  指向运行时常量池的引用,因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。

  方法返回地址,当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

  由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰。

  本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

  在C语言中,堆这部分空间是唯一一个程序员可以管理的内存区域。程序员可以通过malloc函数和free函数在堆上申请和释放空间。那么在Java中是怎么样的呢?

  Java中的堆是用来存储对象本身的以及数组(当然,数组引用是存放在Java栈中的)。只不过和C语言中的不同,在Java中,程序员基本不用去关心空间释放的问题,Java的垃圾回收机制会自动进行处理。因此这部分空间也是Java垃圾收集器管理的主要区域。另外,堆是被所有线程共享的,在JVM中只有一个堆。

  方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。

  在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。

  在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

  在JVM规范中,没有强制要求方法区必须实现垃圾回收。很多人习惯将方法区称为“永久代”,是因为HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器可以像管理堆区一样管理这部分区域,从而不需要专门为这部分设计垃圾回收机制。不过自从JDK7之后,Hotspot虚拟机便将运行时常量池从永久代移除了。

  以上为个人看法和观点,如有不正之处希望谅解并欢迎指正。

  《深入理解Java虚拟机》

  《Java虚拟机规范 SE7》

版权声明:本文为博主落尘曦的原创文章。如转载请注明链接 【 落尘曦的博客:/qq_ 】感谢配合! /qq_/article/details/

计算机体系结构(Computer Architecture)主要研究硬件和软件功能的划分,确定硬件和软件的界面,哪部分功能由硬件系统来完成,哪部分功能由软件系统来完成。

计算机组成原理(Computer Organization)是依据计算机体系结构,在确定且分配了硬件子系统的概念结构和功能特性的基础上,设计计算机各部件的具体组成,以及它们之间的连接关系,实现机器指令级的各种功能和特性,这点上说计算机组成原理是计算机体系结构的逻辑实现。

Implementation)是计算机组成的物理实现,包括中央处理器、主存储器、输入输出接口和设备的物理结构,所选用的半导体器件的集成度和速度,器件、模块、插件、底板的划分,电源、冷却、装配等技术,生产工艺和系统调试等各种问题。总之,就是将完成逻辑设计的计算机组成方案转换成真实的计算机,也就是将满足设计、运行、价格等各项要求的计算机系统真正地制作并调试出来。

1. 理解单处理器计算机系统中各部件的内部工作原理,组成结构以及相互连接方式,具有完整的计算机系统的整机概念.

2. 理解计算机系统层次化结构概念,熟悉硬件与软件间的界面,掌握指令集体系结构的基本知识和基本实现方法

3. 能够运用计算机组成的基本原理和基本方法,对有关计算机硬件系统中的理论和实际问题进行计算,分析,并能对一些基本部件进行简单设计.

(一) 计算机发展历程

第一台电子计算机ENIAC诞生于1946年美国宾夕法尼亚大学.ENIAC用了18000电子管,1500继电器,重30吨,占地170m2,耗电140kw,每秒计算5000次加法.冯?诺依曼(VanNeumann)首次提出存储程序概念,将数据和程序一起放在存储器,使编程更加方便.50年来,虽然对冯?诺依曼机进行很多改革,但结构变化不大,仍称冯?诺依曼机.

一般把计算机的发展分为五个阶段:

中小规模集成电路计算机时代

大规模集成电路计算机时代

超大规模集成电路计算机时代

组成原理是讲硬件结构的 系统结构是讲结构设计的

摩尔定律    微芯片上的集成管数目每3年翻两番.处理器的处理速度每18个月增长一倍.

  每代芯片的成本大约为前一代芯片成本的两倍

  数学家冯·诺依曼(von Neumann)在研究EDVAC机时提出了“储存程序”的概念.以此为基础的各类计算机通称为冯·诺依曼机.它有如下特点:

①计算机由运算器,控制器,存储器,输入和输出五部分组成

②指令和数据以同等的地位存放于存储器内,并可按地址寻访

③指令和数据均用二进制数表示

④指令由操作码和地址码组成,操作码用来表示操作的性质,地址码用来表示操作数在存储器中的位置

⑤指令在存储器内按顺序存放

⑥机器以运算器为中心,输入输出设备与存储器间的数据传送通过运算器完成

·运算器用来完成算术运算和逻辑运算并将的中间结果暂存在运算器内

·存储器用来存放数据和程序

·控制器用来控制,指挥程序和数据的输入,运行以及处理运行结果

·输入设备用来将人们熟悉的信息转换为机器识别的信息

·输出设备将机器运算结果转为人熟悉的信息形式

(4.a) 主机完成一条指令的过程——以取数指令为例

(4.b) 主机完成一条指令的过程——以存数指令为例

(二) 计算机系统层次结构

1. 计算机硬件的基本组成

计算机硬件主要指计算机的实体部分,通常有运算器,控制器,存储器,输入和输出五部分.

CPU是指将运算器和控制器集成到一个电路芯片中.

2. 计算机软件的分类

计算机软件按照面向对象的不同可分两类:

系统软件:用于管理整个计算机系统,合理分配系统资源,确保计算机正常高效地运行,这类软件面向系统.(包括:标准程序库,语言处理程序,OS,服务程序,数据库管理系统,网络软件)

应用软件:是面向用户根据用户的特殊要求编制的应用程序,这类软件通常实现用户的某类要求.

3. 计算机的工作过程

(1)计算机的工作过程就是执行指令的过程

  操作码指明本指令完成的操作

地址码指明本指令的操作对象

(3)指令的读取    为了纪录程序的执行过程,需要一个记录读取指令地址的寄存器,称为指令地址寄存器,或者程序计数器.指令的读取就可以根据程序计数器所指出的指令地址来决定读取的指令,由于指令通常按照地址增加的顺序存放,故此,每次读取一条指令之后,程序计数器加一就为读取下一条指令做好准备.

(4)执行指令的过程   在控制器的控制下,完成以下三个阶段任务:

(三) 计算机性能指标

1. 吞吐量,响应时间

(1) 吞吐量:单位时间内的数据输出数量.

(2) 响应时间:从事件开始到事件结束的时间,也称执行时间.

(2)主频:CPU工作主时钟的频率,机器主频Rc

(3)CPI:执行一条指令所需要的平均时钟周期

2)不同数制间的数据转换

(1)二,八,十六进制数转换成十进制数

(2)十进制数转换成二进制数

通常要对一个数的整数部分和小数部分分别进行处理,各自得出结果后再合并.

对整数部分,一般采用除2取余数法,其规则如下:

将十进制数除以2,所得余数(0或1)即为对应二进制数最低位的值.然后对上次所得商除以2,所得余数即为二进制数次低位的值,如此进行下去,直到商等于0为止,最后得的余数是所求二进制数最高位的值.

对小数部分,一般用乘2取整数法,其规则如下:

将十进制数乘以2,所得乘积的整数部分即为对应二进制小数最高位的值,然后对所余数的小数部分部分乘以2,所得乘积的整数部分为次高位的值,如此进行下去,直到乘积的小数部分为0,或结果已满足所需精度要求为止.

(3)二进制数,八进制数和十六进制数之间的转换

八进制数和十六进制数是从二进制数演变而来的:

由3位二进制数组成1位八进制数;

由4位二进制数组成1位十六进制数.

对一个兼有整数和小数部分的数以小数点为界,小数点前后的数分别分组进行处理,不足的位数用0补足.

对整数部分将0补在数的左侧,对小数部分将0补在数的右侧.这样数值不会发生差错.

机器数:在计算机中正负号也需要数字化,一般用0表示正号,1表示负号.把符号数字化的数成为机器数.

在计算机中采用4位二进制码对每个十进制数位进行编码.4位二进制码有16种不同的组合,从中选出10种来表示十进制数位的0~9,用,…,1001分别表示0,1,…,9,每个数位内部满足二进制规则,而数位之间满足十进制规则,故称这种编码为“以二进制编码的十进制(binary coded decima1,简称BCD)码”.

在计算机内部实现BCD码算术运算,要对运算结果进行修正,对加法运算的修正规则是:

  如相加之和大于或等于(1010)2,或者产生进位,要进行加6修正,如果有进位,要向高位进位.

在计算机中要对字符进行识别和处理,必须通过编码的方法,按照一定的规则将字符用一组二进制数编码表示.字符的编码方式有多种,常见的编码有ASCII码,EBCDIC码等.

ASCII码用7位二进制表示一个字符,总共128个字符元素,包括10个十进制数字(0-9),52个英文字母(A-Z和a-z),34专用符号和32控制符号.

向量存储法:字符串存储时,字符串中的所有元素在物理上是邻接的.

串表存储法:字符串的每个字符代码后面设置一个链接字,用于指出下一个字符的存储单元的地址.

数据校验码是一种常用的带有发现某些错误或自动改错能力的数据编码方法.其实现原理,是加进一些冗余码,使合法数据编码出现某些错误时,就成为非法编码.

    这样,可以通过检测编码的合法性来达到发现错误的目的.合理地安排非法编码数量和编码规则,可以提高发现错误的能力,或达到自动改正错误的目的.

码距:码距根据任意两个合法码之间至少有几个二进制位不相同而确定的,仅有一位不同,称其码距为1.

    (开销最小,能发现数据代码中一位出错情况的编码,常用于存储器读写检查或ASCII字符或其它类型的信息传输的检查)P216

它的实现原理,是使码距由1增加到2.若编码中有1位二进制数出错了,即由1变成0,或者由0变成1.这样出错的编码就成为非法编码,就可以知道出现了错误.在原有的编码之上再增加一位校验位,原编码n位,形成新的编码为n+1 位.增加的方法有2种:

   奇校验:增加位的0或1要保证整个编码中1的个数为奇数个.

   偶校验:增加位的0或1要保证整个编码中1的个数为偶数个.

实现原理,在数据中加入几个校验位,并把数据的每一个二进制位分配在几个奇偶校验组中.当某一位出错就会引起有关的几个校验组的值发生变化,这不但可以发现出错,还能指出是哪一位出错,为自动纠错提供了依据.

  假设校验位的个数为r,则它能表示2r个信息,用其中的一个信息指出“没有错误”,其余2r-1个信息指出错误发生在哪一位.然而错误也可能发生在校验位,因此只有

k=2r-1-r个信息能用于纠正被传送数据的位数,也就是说要满足关系:

CRC校验码一般是指k位信息之后拼接r位校验码.关键问题是如何从k位信息方便地得到r位校验码,以如何从位k+r信息码判断是否出错.

  将带编码的k位有效信息位组表达为多项式:

若将信息位左移r位,则可表示为多项式M(x).xr.这样就可以空出r位,以便拼接r位校验位.

  CRC码是用多项式M(x).xr除以生成多项式G(x)所得的余数作为校验码的.为了得到r位余数,G(x)必须是r+1位.

设所得的余数表达式为R(x),商为Q(x).将余数拼接在信息位组左移r位空出的r位上,就构成了CRC码,这个码的可用多项式表达为:

因此,所得CRC码可被G(x)表示的数码除尽.

将收到的CRC码用约定的生成多项式G(x)去除,如果无错,余数应为0,有某一位出错,余数不为0. 

(二) 定点数的表示和运算

    无符号数就是指正整数,机器字长的全部位数均用来表示数值的大小,相当于数的绝对值.

    带符号数是指在计算机中将数的符号数码化.在计算机中,一般规定二进制的最高位为符号位,最高位为“0”表示该数为正,为“1”表示该数为负.这种在机器中使用符号位也被数码化的数称为机器数.

    根据符号位和数值位的编码方法不同,机器数分为原码,补码和反码.

机器数的最高位为符号位,0表示正数,1表示负数,数值跟随其后,并以绝对值形式给出.这是与真值最接近的一种表示形式.

机器数的最高位为符号位,0表示正数,1表示负数,其定义如下:

 机器数的最高位为符号,0表示正数,1表示负数.反码的定义:

真值的移码和补码仅差一个符号位.若将补码的符号位由0改为1或从1改为0即可得到真值的移码

乘法运算可用移码和加法来实现,两个n位数相乘,总共要进行n次加法运算和n次移位运算

三种机器数的特点可以归纳为:

·三种机器数的最高位均为符号位.符号位和数值位之间可用“.”(对于小数)或“,”(对于整数)隔开

·当真值为正时,原码,补码和反码的表示形式均相同,即符号位用“0”表示,数值部分与真值部分相同

·当真值为负时,原码,补码和反码的表示形式不同,其它符号位都用“1”表示,而数值部分有这样的关系,即补码是原码的“求反加1”,反码是原码的“每位求反”.

左移,绝对值扩大;右移,绝对值缩小.

算术移位和逻辑移位的区别:

算术移位:带符号数移位;

逻辑移位:无符号数移位;

2)原码定点数的加/减运算;

对原码表示的两个操作数进行加减运算时,计算机的实际操作是加还是减,不仅取决指令中的操作码,还取决于两个操作数的符号.而且运算结果的符号判断也较复杂.

例如,加法指令指示做(+A)+(-B)由于一操作数为负,实际操作是做减法(+A)-(+B),结果符号与绝对值大的符号相同.同理,在减法指令中指示做(+A)-(-B)实际操作做加法(+A)+(+B),结果与被减数符号相同.由于原码加减法比较繁琐,相应地需要由复杂的硬件逻辑才能实现,因此在计算机中很少被采用.

3)补码定点数的加/减运算;

无需符号判定,连同符号位一起相加,符号位产生的进位自然丢掉

4)定点数的乘/除运算

两个原码数相乘,其乘积的符号为相乘两数的异或值,数值两数绝对值之积.

符号∣表示把符号位和数值邻接起来.

原码两位乘和原码一位乘比较

有的机器为方便加减法运算,数据以补码形式存放.乘法直接用补码进行,减少转换次数.具体规则如下:

“布斯公式”: 在乘数Yn后添加Yn+1=0.按照Yn+1 ,Yn相邻两位的三种情况,其运算规则如下:

<1>原码两位乘法,因此实际操作用Yi-1,Yi,C三位来控制,运算规则如下

根据前述的布斯算法,将两步合并成一步,即可推导出补码两位乘的公式.

求部分积的次数和右移操作的控制问题.

    当乘数由1位符号位和以n(奇数)位数据位组成时,求部分积的次数为(1+n)/2,而且最后一次的右移操作只右移一位.

   若数值位本身为偶数n,可采用下述两种方法之一:

①可在乘数的最后一位补一个0,乘数的数据位就成为奇数,而且其值不变,求部分积的次数为1+(n+l)/2,即n/2+1,最后一次右移操作也只右移一位.

②乘数增加一位符号位,使总位数仍为偶数,此时求部分积的次数为n/2+1,而且最后一次不再执行右移操作.

笔算除法和机器除法的比较

余数 不动 低位补“0”

余数 左移一位 低位补“0”

被除数(余数)减去除数,如果为0或者为正值时,上商为1,不恢复余数;如果结果为负,上商为0,再将除数加到余数中,恢复余数.余数左移1位.

当余数为正时,商上1,求下一位商的办法,余数左移一位,再减去除数;当余数为负时,商上0,求下一位商的办法,余数左移一位,再加上除数.

<2>定点补码一位除法(加减交替法)

1〉如果被除数与除数同号,用被除数减去除数;若两数异号,被除数加上除数.如果所得余数与除数同号商上1,否则,商上0,该商为结果的符号位.

2〉求商的数值部分.如果上次商上1,将除数左移一位后减去除数;如果上次商上0,将余数左移一位后加除数.然后判断本次操作后的余数,如果余数与除数同号商上1,如果余数与除数异号商上0.如此重复执行n-1次(设数值部分n位).

3〉商的最后一位一般采用恒置1的办法,并省略了最低+1的操作.此时最大的误差为2-n.

5)溢出概念和判别方法

当运算结果超出机器数所能表示的范围时,称为溢出.显然,两个异号数相加或两个同号数相减,其结果是不会溢出的.仅当两个同号数相加或者两个异号数相减时,才有可能发溢出的情况,一旦溢出,运算结果就不正确了,因此必须将溢出的情况检查出来.判别方法有三种:

1〉当符号相同的两数相加时,如果结果的符号与加数(或被加数)不相同,则为溢出.

2〉当任意符号两数相加时,如果C=Cf,运算结果正确,其中C为数值最高位的进位,Cf为符号位的进位.如果C≠Cf ,则为溢出,所以溢出条件=C⊕Cf .

3〉采用双符号fs2fs1.正数的双符号位为00,负数的双符号位为11.符号位参与运算,当结果的两个符号位甲和乙不相同时,为溢出.所以溢出条件= fs2⊕fs1 ,或者溢出条件= fs2fs1 + fs2fs1

(三) 浮点数的表示和运算

1)浮点数的表示范围;

浮点数是指小数点位置可浮动的数据,通常以下式表示:

其中,N为浮点数,M(Mantissa)为尾数(可正可负),E(Exponent)为阶码(可正可负),R(Radix)称为“阶的基数(底)”,而且R为一常数,一般为2,8或16.在一台计算机中,所有数据的R都是相同的,于是不需要在每个数据中表示出来.因此,浮点数的机内表示一般采用以下形式:

浮点数的机内表示一般采用以下形式:

Ms是尾数的符号位,设置在最高位上.

E为阶码(移码),有n+1位,一般为整数,其中有一位符号位,设置在E的最高位上,用来表正阶或负阶.

M为尾数(原码),有m位,由Ms和M组成一个定点小数.Ms=0,表示正号,Ms=1,表示负.为了保证数据精度属数通常用规格化形式表示:当R=2,且尾数值不为0时,其绝对值大于或等于(0.5)10.对非规格化浮点数,通过将尾数左移或右移,并修改阶码值使之满足规格化要求.

浮点数的表示范围以通式N=M×RE设浮点数阶码的数值位取m位,尾数的数值位取n位

根据IEEE 754国际标准,常用的浮点数有三种格式:

单精度格式32位,阶码为8位,尾数为23位.另有一位符号位S,处在最高位.

由于IEEE754标准约定在小数点左部有一位隐含位,从而实际有效位数为24位.这样使得尾数的有效值变为1.M .

阶码部分采用移码表示,移码值127,1到254经移码为-126到+127.

0 有了精确的表示,无穷大也明确表示.对于绝对值较小的数,可以采用非规格化数表示,减少下溢精度损失.非规格化数的隐含位是0,不是1.

2. 浮点数的加/减运算

加减法执行下述五步完成运算:

1)“对阶”操作    比较两浮点数阶码的大小,求出其差ΔE,保留其大值E,E=max(Ex, Ey).当ΔE≠0时,将阶码小的尾数右移ΔE位,并将其阶码加上ΔE,使两数的阶码值相等.

5)检查阶码是否溢出 阶码溢出表示浮点数溢出.在规格化和舍入时都可能发生溢出,若阶码正常,加/减运算正常结束.若阶码下溢,则设置机器运算结果为机器零,若上溢,则设置溢出标志.

定点数和浮点数可从如下几个方面进行比较

①当浮点机和定点机中的位数相同时,浮点数的表示范围比定点数大得多

②当浮点数位规格化数时,其相对绝对远比定点数高

③浮点数运算要分阶码部分和尾数部分,而且运算结果都要求规格化,故浮点运算步骤比定点运算的步骤多,运算速度比定点运算的低,运算线路比定点运算的复杂

④在溢出的判断方法上,浮点数是对规格化的阶码进行判断,而定点数是对数值本身进行判断

总之,浮点数在数的表示范围,数的精度,溢出处理和程序编程方面(不取比例因子)均优于定点数.但在运算规则即硬件成本方面又不如定点数

(四) 算术逻辑单元ALU

1.  串行加法器和并行加法器

并行加法器可以同时对数据的各位进行相加,一般用n个全加器来实现2个操作数的各位同时向加.其操作数的各位是同时提供的,由于进位是逐位形成,低位运算所产生的进位会影响高位的运算结果.

串行进位(也称波形进位)加法器,逻辑电路比较简单,但是最高位的加法运算,一定要等到所有低位的加法完成之后才能进行,低位的进位要逐步的传递到高位,逐级产生进位,因此运算速度比较慢.

为了提高运算速度,减少延迟时间,可以采用并行进位法,也叫提前进位或先行进位.

并行进位加法器的运算速度很快,形成最高进位输出的延迟时间很短,但是以增加硬件逻辑线路为代价.对于长字长的加法器,往往将加法器分成若干组,在组内采用并行进位,组间则采用串行进位或并行进位,由此形成多种进位结构.

单级先行进位方式将n位字长分为若干组,每组内采用并行进位方式,组与组之间册采用串行进位方式.

多级先行进位在组内和组间都采用先行进位方式.

2. 算术逻辑单元ALU的功能和机构

ALU部件是运算器中的主要组成部分,又称多功能函数发生器,主要用于完成各种算术运算和逻辑运算.

ALU的算术运算部件包含加法器,减法器,乘法器,除法器,增量器(+1),减量器(-1),BCD码运算器等组件.

ALU的主要工作是根据CPU指令要求执行各种指定运算,如加法,减法,乘法,除法,比较,逻辑移位等操作.

通用寄存器组是一组存取速度最快的存储器,用于保存参加运算的操作数和中间结果.访问寄存器无需高速缓存,也不需要运行总线周期,因此指令的执行速度很快.几乎所有的指令都要将寄存器指定为一个操作数,有些指令还要求将操作数存放在专用的寄存器中.

2增加存储字长,使每个周期可读/写更多的二进制数

芯片的容量为2k×k位

20位的地址可以访问1MB的存储空间,32位的地址可以访问4GB的内存空间,64位可以访问1800万TB

静态RAM和动态RAM之间的比较。目前,动态RAM的应用比静态RAM要广泛的多:

②   DRAM行、列按先后顺序输送,减少了芯片引脚,封装尺寸也减少

 (二) 存储器的层次化结构

存储器有3个重要的指标:速度,容量和每位价格,一般来说,速度越快,位价越高;容量越大,位价越低,容量大,速度就越低.上述三者的关系用下图表示:

存储系统层次结构主要体现在缓存-主存-辅存这两个存储层次上,如下图所示:

缓存-主存层次主要解决CPU和主存速度不匹配的问题  

主存-辅存层次主要解决存储系统的容量问题

从CPU角度来看缓存-主存层次的速度接近于缓存,高于主存;其容量和价位却接近于主存,这就从速度和成本的矛盾中获得了理想的解决办法.
主存-辅存层次从整体分析,其速度接近于主存,容量接近于辅存,平均价位也接近于低速的、廉价的存储价位,这又解决了速度、容量、成本这三者之间的矛盾.

现代计算机系统几乎都具有这两个存储层次,构成了缓存、主存、辅存三级存储系统.

 (三) 半导体随机存取存储器

1. SRAM存储器的工作原理

SRAM静态存储单元的每个存储位需要四到六个晶体管组成.比较典型的是六管存储单元,即一个存储单元存储一位信息"0"或"1".静态存储单元保存的信息比较稳定,信息为非破坏性读出,故不需要重写或者刷新操作;另一方面,其结构简单,可靠性高,速度较快,但其占用元件较多,占硅片面积大,且功耗大,所以集成度不高.

2. DRAM存储器的工作原理

常见的DRAM存储单元有三管式和单管式两种,它们的共特点是靠电容存储电荷的原理来寄存信息.若电容上存有足够的电荷表示“”,电容上无电荷表示"0".电容上的电荷一般只能维持1-2ms,因此即使电源不掉电,电容上的电荷会自动消失.因此,为保证信息的不丢失,必须在2ms之内就要对存储单元进行一次恢复操作,这个过程称为再生或者刷新.与SRAM相比,DRAM具有集成度更高,功耗低等特点,目前被各类计算机广泛使用.

前面介绍的DRAM和SRAM均为可任意读/写的随机存储器,当掉电时,所存储的内容消失,所以是易失性存储器.只读存储器,即使停电,存储内容也不丢失.根据半导体制造工艺不同,分为ROM,PROM,EPROM,E2ROM和Flash Memory

    掩模式ROM由芯片制造商在制造时写入内容,以后只能读而不能再写入.其基本存储原理是以元件的“有/无”来表示该存储单元的信息(“1”或“0”),可以用二极管或晶体管作为元件,显而易见,其存储内容是不会改变的.

2. 可编程序的只读存储器(PROM)

    PROM可由用户根据自己的需要来确定ROM中的内容,常见的熔丝式PROM是以熔丝的通和断开来表示所存的信息为“1”或“0”.刚出厂的产品,其熔丝是全部接通的.根据需要断开某些单元的熔丝(写入).显而易见,断开后的熔丝是不能再接通了,因而一次性写入的存储器.掉电后不会影响其所存储的内容.

3. 可擦可编程序的只读存储器(EPROM)

为了能修改ROM中的内容,出现了EPROM.利用浮动栅MOS电路保存信息,信息改写用紫外线照射即可擦除.

4. 可电擦可编程序只读存储器(E2PROM)

    其读写操作可按每个位或每个字节进行,类似SRAM,但每字节的写入周期要几毫秒,比SRAM长得多.E2PROM每个存储单元采则2个晶体管.其栅极氧化层比EPROM薄,因此具有电擦除功能.

(五) 主存储器与CPU的连接

1个存储器的芯片的容量是有限的,它在字数或字长方面与实际存储器的要求都有很大差距,所以需要在字向和位向进行扩充才能满足需要.根据存储器所需的存储容量和所提供的芯片的实际容量,可以计算出总的芯片数.一个存储器的容量为M×N位,若使用L×K位存储器芯片,那么,这个存储器共需要M/L×N/K存储器芯片.

    位扩展指的是用多个存储器器件对字长进行扩充.位扩展的连接方式是将多片存储器的地址,片选己,读写控制端R/W可相应并联,数据端分别引出.

    静态存储器进行字扩展时,将各芯片的地址线,数据线,读写控制线相应并联,而由片选信号来区分各芯片的地址范围.

(六) 双口RAM和多模块存储器

双端口存储器是一种具有两个单独的读/写端口及控制电路的存储器,通过增加一个读/写端口,双端口存储器扩展了存储器的的信息交换能力.

为了解决CPU与主存储器之间的速度匹配问题,在高速存储器中,普遍采用并行主存系统.即利用类似存储器扩展(位扩展,字扩展,字位扩展)的方法,将n个字长为W位的存储器并行连接,构建一个更大的存储器.并行主存有单体多字方式,多体并行方式和多体交叉方式.

(七) 高速缓冲存储器(Cache实际上,这是来自法文的一个单词,意思是隐蔽之所或藏东西的地方)

避免 CPU “空等” 现象

1. 程序访问的局部性

从大量的统计中得到的一个规律是,程序中对于存储空间90%的访问局限于存储空间的10%的区域中,而另外10%的访问则分布在存储空间的其余90%的区域中.这就是通常说的局部性原理.访存的局部性规律包括两个方面:

时间局部性:如果一个存储项被访问,则可能该项会很快被再次访问.

空间局部性:如果一个存储项被访问,则该项及其邻近的项也可能很快被访问.

Cache通常由两部分组成,块表和快速存储器.其工作原理是:处理机按主存地址访问存储器,存储器地址的高段通过主存-Cache地址映象机构借助查表判定该地址的存储单元是否在Cache中,如果在,则Cache命中,按Cache地址访问Cache.否则,Cache不命中,则需要访问主存,并从主存中调入相应数据块到Cache中,若Cache中已写满,则要按某种算法将Cache中的某一块替换出去,并修改有关的地址映象关系.

从这个工作原理我们可以看出,它已经涉及到了两个问题.首先是定位,然后是替换的问题.

Cache的存在对程序员是透明的.其地址变换和数据块的替换算法均由硬件实现.通常Cache被集成到CPU内以提高访问速度.

3. Cache和主存之间的映射方式

因为处理机访问都是按主存地址访问的,而Cache的空间远小于主存,如何知道这一次的访问内容是不是在Cache中,在Cache中的哪一个位置呢? 这就需要地址映象,即把主存中的地址映射成Cache中的地址.让Cache中一个存储块(空间)与主存中若干块相对应,如此,访问一个主存地址时,就可以对应地知道在cache中哪一个地址了.地址映象的方法有三种:直接映象,全相联映象和组相联映象.

直接映象就是将主存地址映象到Cache中的一个指定地址.任何时候,主存中存储单元的数据只能调入到Cache中的一个位置,这是固定的,若这个位置已有数据,则产生冲突,原来的块将无条件地被替换出去.

全相联映象就是任何主存地址可映象到任何Cache地址的方式.在这种方式下,主存中存储单元的数据可调入到Cache中的任意位置.只有在Cache中的块全部装满后才会出现块冲突.

组相联映象指的是将存储空间的页面分成若干组,各组之间的直接映象,而组内各块之间则是全相联映象.

4. Cache中主存块的替换算法

在直接映象方式下,不存在块替换的算法,因为每一块的位置映象是固定的,需要哪一块数据就可直接确定地将该块数据调入上层确定位置.而其他两种映象就存在替换策略的问题,就是要选择替换到哪一个Cache块.即替换算法.

用软的或硬的随机数产生器产生上层中要被替换的页号

没有利用上层存储器使用的"历史信息",没有反映等程序局部性,命中率低.

选择最早装入上层的页作为被替换的页

实现方便,利用了主存历史的信息

不能正确反映程序局部性原理,命中率不高,可能出现一种异常现象.

选择近期最少访问的页作为被替换的页

比较正确反映程序局部性,利用访存的历史信息,命中率较高

将未来近期不用的页换出去

命中率最高,可作为衡量其他替换算法的标准

不现实,只是一种理想算法

对Cache的写操作,情况比读操作要复杂一些.由于写入Cache时,并没有写入主存,因此就出现Cache和主存数据不一致的情况.如何处理Cache和主存不一致的方法就称为更新策略.

是指在CPU执行写操作时,信息只写入Cache中,仅当需要替换时,才将改写过的Cache块先送回主存(写回),然后再调块(设置dirty位)

有利于省去许多将中间结果写入主存的无谓开销.

需设修改位增加Cache的复杂性

在写操作时,将数据同时写入Cache和主存

为了写中间结果浪费了不少时间

另外,当写不命中时(也就是写Cache块时,这块早被人替换出去而在Cache中找不到时)是不是要把这块再取回Cache中,有两个解决方法:

u 不按写分配法,就是直接写到主存里,不再把该地址对应的块调回Cache中.

u 按写分配法,就是写到主存,而且把这一块从主存中调入到Cache.

一般写回法用按写分配法,全写法则采用不按写分配.

1. 虚拟存储器的基本概念

虚拟存储器是主存的扩展,虚拟存储器的空间大小取决于计算机的访存能力而不是实际外存的大小,实际存储空间可以小于虚拟地址空间.从程序员的角度看,外存被看作逻辑存储空间,访问的地址是一个逻辑地址(虚地址),虚拟存储器使存储系统既具有相当于外存的容量又有接近于主存的访问速度.

虚拟存储器的访问也涉及到虚地址与实地址的映象,替换算法等,这与Cache中的类似,前面我们讲的地址映象以块为单位,而在虚拟存储器中,地址映象以页为单位.设计虚拟存储系统需考虑的指标是主存空间利用率和主存的命中率.

虚拟存储器与Cache存储器的管理方法有许多相同之处,它们都需要地址映象表和地址变换机构.但是二者也是不同的.

虚拟存储器的三种不同管理方式:按存储映象算法,分为段式,页式和段页式等,这些管理方式的基本原理是类似的.

页式管理:是把虚拟存储空间和实际空间等分成固定大小的页,各虚拟页可装入主存中的不同实际页面位置.页式存储中,处理机逻辑地址由虚页号和页内地址两部分组成,实际地址也分为页号和页内地址两部分,由地址映象机构将虚页号转换成主存的实际页号.

页式管理用一个页表,包括页号,每页在主存中起始位置,装入位等.页表是虚拟页号与物理页号的映射表.页式管理由操作系统进行,对应用程序员的透明的.

段式管理: 把主存按段分配的存储管理方式.它是一种模块化的存储管理方式,每个用户程序模块可分到一个段,该程序模块只能访问分配给该模块的段所对应的主存空间.段长可以任意设定,并可放大和缩小.

系统中通过一个段表指明各段在主存中的位置.段表中包括段名(段号),段起点,装入位和段长等.段表本身也是一个段.段一般是按程序模块分的.

4. 段页式虚拟存储器

段页式管理:是上述两种方法的结合,它将存储空间按逻辑模块分成段,每段又分成若干个页,访存通过一个段表和若干个页表进行.段的长度必须是页长的整数倍,段的起点必须是某一页的起点.

在虚拟存储器中进行地址变换时,需要虚页号变换成主存中实页号的内部地址变换,这一般通过查内页表实现.当表中该页对应的装入位为真时,表示该页在主存中,可按主存地址问主存;如果装入位为假时,表示该页不在存储器中,就产生页失效中断,需从外存调入页.

中断处理时先通过外部地址变换,一般通过查外页表,将虚地址变换为外存中的实际地址,到外存中去选页,然后通过I/0通道调入内存.当外存页面调入主存中时还存在一个页面替换略的问题.

提高页表的访问速度是提高地址变换速度的关键.因为,每次访存都要读页表,如果页存放在主存中,就意味着访存时间至少是两次访问主存的时间,这样查表的代价大大.只有内部地址变换速度提高到使访问主存的速度接近于不采用虚拟存储器时的访主存速度时,虚拟存储器才能实用.

根据访存的局部性,表内各项的使用的概率不是均匀分布的.在一段时间内,可能只用表中的很少几项,因此应重点提高使用概率高的这部分页表的访问速度,可用快速硬件构成全表小得多的部分表格,而将整个表格放在主存中,这就引出了快表和慢表的概念和技术.这样,虚地址到实地址的变换方法如后图所示.

查表时,根据虚页表同时查找快表和慢表,当在快表中查到该虚页号时,就能很快找到对应的实页号,将其送入主存实地址寄存器,同时使慢表的查找作废,这时主存的访问速度没降低多少.

如果在快表中查不到,则经过一个访主存的时间延迟后,将从慢表中查到的实页送入实地址寄存器,同时将此虚页号和对应的实页号送入快表,这里也涉及到用一个替换算法从快表中替换出一行.

快表的存在对所有的程序员都是透明的.

固定盘、盘组大部分不可换

人们习惯把每一条机器语言的语句称为机器指令,而又将全部机器指令的集合称为机器的指令系统

形成下一条指令的地址并送到PC中

计算机是通过执行指令来处理各种数据的.为了指出数据的来源,操作结果的去向及所执行的操作,一条指令必须包含下列信息:

从上述分析可知,一条指令实际上包括两种信息即操作码和地址码.

操作码(operation code)用来表示该指令所要完成的操作(如加,减,乘,除,数据传送等),其长度取决于指令系统中的指令条数.如操作码占7位,则该机器最多包含27=128条指令.

地址码用来描述该指令的操作对象,或直接给出操作数或指出操作数的存储器地址或寄存器地址(即寄存器名).

操作码的长度不固定会增加指令译码和分析难度,使控制器的设计复杂.

形式地址  指令字中的地址

有效地址  操作数的真实地址

约定  指令字长=存储字长=机器字长

2. 定长操作码指令格式

指令中只有操作码,而没有操作数或没有操作数地址.这种指令有两种可能:

(1)无需任何操作数,如空操作指令,停机指令等.

(2)所需的操作数是默认的.如堆栈结构计算机的运算指令,所需的操作数默认在堆栈中,由堆栈指针SP隐含指出,操作结果仍然放回堆栈中.又如Intel 8086的字符串处理指令,源,目的操作数分别默认在源变址寄存器SI和目的变址寄存器DI所指定的存储器单元中.

A——操作数的存储器地址或寄存器名

指令中只给出一个地址,该地址既是操作数的地址,又是操作结果的存储地址.如加1,减1和移位等单操作数指令均采用这种格式,对这一地址所指定的操作数执行相应的操作后,产生的结果又存回该地址中.

在某些字长较短的微型机中(如早期的Z80,Intel8080,MC6800等),大多数算术逻辑指令也采用这种格式,第一个源操作数由地址码A给出,第二个源操作数在一个默认的寄存器中,运算结果仍送回到这个寄存器中,替换了原寄存器内容,通常把这个寄存器称累加器.

  A1——第一个源操作数的存储器地址或寄存器地址.

  A2——第二个源操作数和存放操作结果的存储器地址或寄存器地址.

这是最常见的指令格式,两个地址指出两个源操作数地址,其中一个还是存放结果的目的地址.对两个源操作数进行操作码所规定的操作后,将结果存入目的地址,在本例中即为A2指定的地址

其操作是对A1,A2指出的两个源操作数进行操作码(OP)所指定的操作,结果存入A3中.

在某些性能较好的大,中型机甚至高档小型机中,往往设置一些功能很强的,用于处理成批数据的指令,如字符串处理指令,向量,矩阵运算指令等.

为了描述一批数据,指令中需要多个地址来指出数据存放的首地址,长度和下标等信息

3. 扩展操作码指令格式

设某机器的指令长度为16位,包括4位基本操作码字段和三个4位地址字段,其格式下:

4位基本操作码有16个码点(即有16种组合),若全部用于表示三地址指令,则只有16条.但,若三地址指令仅需15条,两地址指令需15条,一地址指令需15条,零地址指令需16条,共61条指令,应如何安排操作码?

显然,只有4位基本操作码是不够的,必须将操作码的长度向地址码字段扩展才行.

一种可供扩展的方法和步骤如下:

(1)15条三地址指令的操作码由4位基本操作码从0000~1110给出,剩下一个码点1111用于把操作码扩展到A1,即4位扩展到8位;

(2)15条二地址指令的操作码由8位操作码从~给出,剩下一个码点用于把操作码扩展到A2,即从8位扩展到12位;

(3)15条一地址指令的操作码由12位操作码从~给出,剩下的一个码点用于把操作码扩展到A3,即从12位扩展到16位;

(4)16条零地址指令的操作码由16位操作码从0000~1111给出.

指令字长取决于操作码的长度操作数地址的长度操作数地址的个数.为了提高指令的运行速度和节省存储空间,通常尽可能的吧常用的指令(如数据传输指令、算逻运算指令等)设计成单字长或短字长格式的指令.

地址实际也可以看做是一种数据,在许多情况下要计算操作数的地址.这时地址可看作无符号的整数

计算机中常见的数字有定点数、浮点数和十进制数字

在应用计算机时,文本或者字符串也是一种常见的数据类型

计算机除了做算术运算外,有时还做逻辑运算,此时n个0和1的组合不是被看做算术数字而被看做逻辑数

奔腾Pentium处理器的数据类型有逻辑数、有符号数(补码)、无符号数、压缩和未压缩的BCD码、地址指针、位串以及浮点数(符合IEEE754标准)等

数据传送包括寄存器与寄存器,寄存器与存储单元,存储单元与存储单元之间的传送

这操作可实现算术运算(加,减,乘,除,增1,减1,取负即求补)逻辑运算(与,或,非,异或)

移位可分为算术移位,逻辑移位和循环移位三种

不受任何约束条件直接把程序转移到下一条需执行指令的地址

根据当前指令的执行结果决定是否需要转移

l   子程序调用可出现在子程序中,即允许子程序嵌套

CPU必须记住返回地址,使子程序能准确返回,返回地址存放在以下3处

l   寄存器内.机器内设有专用寄存器,专用于存放返回地址

l   栈顶内.现代计算机都设有堆栈,执行RETURN指令后,便可自动从堆栈内取出应返回的地址

陷阱(Trap)与陷阱指令

其实是一种意外事故的中断,一般不提供给用户使用,作为隐指令,再出现故障时,由CPU自动产生并执行

对于I/O单独编址的计算机而言,通常设有输入输出指令,他完成从外设中的寄存器读入一个数据到CPU寄存器内,或将数据从CPU的寄存器输出至某外设的寄存器中

包括等待指令、停机指令、空操作指令、开中断指令、关中断指令、置条件码指令等

有些大型或巨型机还设有向量指令,可对整个向量或矩阵进行求和求积运算

(二) 指令的寻址方式

操作数的真实地址称为有效地址,记做EA,它是寻址方式和形式地址共同来决定的.

2. 数据寻址和指令寻址

寻址方式是指确定本条指令的数据地址以及下一条将要执行的指令的地址,与硬件结构密切相关,寻址方式分为指令寻址和数据寻址两大类

指令寻址分为顺序寻址和跳跃寻址两种.

顺序寻址可以通过程序计数器PC加1自动形成下一条指令的地址,跳跃寻址则通过转移类指令实现,是通过对PC的运算得到新的下一条指令的地址.

所需的操作数由指令的地址码部分直接给出,就称为立即数(或直接数)寻址方式.这种方式的特点是取指时,操作码和一个操作数同时被取出,不必再次访问存储器,提高了指令的执行速度.但是由于这一操作数是指令的一部分,不能修改,而一般情况下,指令所处理的数据都是在不断变化的(如上条指令的执行结果作为下条指令的操作数),故这种方式只能适用于操作数固定的情况.通常用于给某一寄存器或存储器单元赋初值或提供一个常数等.(图中“#”表示立即寻址的标记,A的位数限制了这类指令所能表述的立即数的范围)

指令的地址码部分给出操作数在存储器中的地址.

操作数的地址隐含在操作码或者某个寄存器中.

  在寻址时,有时根据指令的地址码所取出的内容既不是操作数,也不是下一条要执行的指令,而是操作数的地址或指令的地址,这种方式称为间接寻址或间址.

计算机的中央处理器一般设置有一定数量的通用寄存器,用以存放操作数,操作数的地址或中间结果.假如指令地址码部分给出某一通用寄存器地址,而且所需的操作数就在这一寄存器中,则称为寄存器寻址.通用寄存器的数量一般在几个至几十个之间,比存储单元少很多,因此地址码短,而且从寄存器中存取数据比从存储器中存取快得多,所以这种方式可以缩短指令长度,节省存储空间,提高指令的执行速度,在计算机中得到广泛应用.

寄存器中给出的是操作数的地址,因此还需要访问一次存储器才能得到操作数.

在计算机中设置一个专用的基址寄存器,或由指令指定一个通用寄存器为基址寄存器.操作数的地址由基址寄存器的内容和指令的地址码A相加得到

指令地址码部分给出的地址A和指定的变址寄存器X的内容通过加法器相加,所得的和作为地址从存储器中读出所需的操作数.这是几乎所有计算机都采用的一种寻址方式.

把程序计数器PC的内容(即当前执行指令的地址)与指令的地址码部分给出的位移量(disp)之和作为操作数的地址或转移地址,称为相对寻址.

 主要用于转移指令,执行本条指令后,将转移到(PC)+disp,(PC)为程序计数器的内容.相对寻址有两个特点:

  1〉转移地址不是固定的,它随着PC值的变化而变化,并且总是与PC相差一个固定值disp,因此无论程序装人存储器的任何地方,均能正确运行,对浮动程序很适用.

  2〉位移量可正,可负,通常用补码表示.如果位移量为n位,则这种方式的寻址范围在

  计算机的程序和数据一般是分开存放的,程序区在程序执行过程中不允许修改.在程序与数据分区存放的情况下,不用相对寻址方式来确定操作数地址.

在一般计算机中,堆栈主要用来暂存中断和子程序调用时现场数据及返回地址,用于访问堆栈的指令只有压入(即进栈)和弹出(即退栈)两种,它们实际上是一种特殊的数据传送指令:

压入指令(PUSH)是把指定的操作数送入堆栈的栈顶;

弹出指令(POP)的操作刚好相反,是把栈顶的数据取出,送到指令所指定的目的地.

一般的计算机中,堆栈从高地址向低地址扩展,即栈底的地址总是大于或等于栈顶的地址(也有少数计算机刚好相反)当执行压入操作时,首先把堆栈指针(SP)减量(减量的多少取决于压入数据的字节数,若压入一个字节,则减1;若压入两个字节,则减2,以此类推),然后把数据送人SP所指定的单元;当执行弹出操作时,首先把sp所指定的单元(即栈顶)的数据取出,然后根据数据的大小(即所占的字节数)对SP增量.

设计指令格式应考虑的各种因素

指令系统集中反映了机器的性能,又是程序员编程的依据,高档机必须能兼容低档机的程序运行,称之为“向上兼容”.

指令格式集中体现了指令系统的功能.为此,在确定指令系统时,必须从以下几个方面综合考虑.

①   操作类型:包括指令数及操作的难易程度

②   数据类型:确定哪些数据类型可以参加操作

③   指令格式:包括指令字长、操作码位数、地址码位数、地址个数、寻址方式类型、以及指令字长和操作码位数是否可变等.

④   寻址方式:包括指令和操作数具体有哪些寻址方式.

⑤  寄存器个数:寄存器的多少直接影响指令的执行时间.

顺序寻址可通过程序计数器PC加1自动形成下一条指令的地址

跳跃寻址则通过转移类指令实现

操作数本身设在指令字内,即形式地址A不是操作数地址而是操作数本身

l   A的位数限制了这类指令所能表述的立即数的范围

指令中的形式地址A就是操作数的真实地址EA,即EA=A

l   缺点在于A的位数限制了操作数的寻址范围而且必须修改A的值才能修改操作数的地址

指令字中不明显给出操作数的地址,其操作数的地址隐含在操作码或某个寄存器中

l   由于隐含寻址在指令字中少了一个地址,因此,这种寻址方式的指令有利于缩短指令字长

倘若指令字中的形式地址不直接指出操作数的地址,而是指出操作数有效地址所在的存储单元的地址,也就是说,有效地址是由形式地址间接提供的,即为间接地址,即EA=(A)

1.   与直接寻址相比,扩大了操作数的寻址范围,因为A的位数通常小于指令字长,而存储字长可与指令字长相等

l   指令的执行阶段需要访存两次(一次间接寻址)或多次(多次间接寻址),致使指令执行时间延长

在寄存器寻址的指令字中,地址码字段直接指出了寄存器的编号,即EA=R

l   由于地址字段只需指明寄存器编号(计算机中寄存器数有限)故指令字

l   较短,节省了存储空间,因此寄存器寻址在计算机中得到广泛应用

l   执行阶段不访存,只访问寄存器,执行速度快

l   有效地址在寄存器中, 操作数在存储器中,执行阶段访存

基址寻址需设有基址寄存器BR,其操作数的有效地址EA等于指令字中的形式地址与基址寄存器中的内容(称为基地址)相加,即EA=A+(BR)

采用专用寄存器作基址寄存器

采用通用寄存器作基址寄存器

l   由用户指定哪个通用寄存器作为基址寄存器

l   基址寄存器的内容由操作系统确定

变址寻址与基址寻址极为相似.其有效地址EA等于指令字中的形式地址A与变址寄存器IX的内容相加之和,即EA=A+(IX)

~的有效地址是将PC的内容(即当前指令地址)与指令字中的形式地址A(A 是相对于当前指令的位移量(可正可负,补码)相加而成,即EA=(PC)+A

要求计算机中设有堆栈.堆栈既可用寄存器组(称为硬堆栈)来实现,也可利用主存的一部分空间作堆栈(称为软堆栈)

1.CISC(复杂指令集计算机)

随着VLSI技术的发展,计算机的硬件成本不断下降,软件成本不断提高,使得人们热衷于在指令系统中增加更多的指令和复杂的指令,来提高操作系统的效率,并尽量缩短指令系统与高级语言的语义差别,以便于高级语言的编译和降低软件成本.

    另外,为了做到程序兼容,同一系列计算机的新机器和高档机的指令系统只能扩充而不能减去任意一条,因此,促使指令系统越来越复杂,某些计算机的指令多达几百条.例如,DEC公司的VAX 11/780计算机有303条指令,18种寻址方式,我们称这些计算机为复杂指令系统计算机(complex instruction  set 

2.RISC(简单指令集计算机)

最长使用的是一些简单指令,占指令总数的20%,但在程序中出现的频率却占80%.     而占20%的复杂指令,为实现其功能而设计的微程序代码却占总代码的80%.CISC研制时间长,成本高,难于实现流水线;因此出现了RIC技术.

计算机执行程序所需的时间P可用下式表述:

其中,I是高级语言程序编译后在机器上运行的机器指令数;C为执行每条机器指令所需的平均机器周期;T是每个机器周期的执行时间.

1)优先选取使用频率最高的一些简单指令;

选用使用频度较高的一些 简单指令,复杂指令的功能由简单指令来组合

指令 长度固定、指令格式种类少、寻址方式少

4)CPU中的寄存器数量很多;

CPU 中有多个 通用 寄存器

5)大部分指令在一个或小于一个机器周期完成;

采用 流水技术  一个时钟周期 内完成一条指令

6)硬布线控制逻辑为主,不用或少用微码控制;

采用 组合逻辑 实现控制器

7)一般用高级语言编程,特别重视编译优化,以减少程序执行时间.

采用 优化 的 编译 程序

绝大多数在一个机器周期完成

RISC机的主要优点可归纳如下

①充分利用VLSI芯片的面积

②提高了计算机运行速度

③便于设计,降低成本,提高可靠性

④有效支持高级语言程序

(一) CPU的功能和基本结构

CPU主要是由运算器和控制器组成,由于运算器(实现算术运算和逻辑运算)部分在第二部分介绍过,所以本节主要介绍控制器的组成和工作原理.

计算机对信息进行处理(或计算)是通过程序的执行而实现的,程序是完成某个确定算法的指令序列,要预先存放在存储器中.控制器的作用是控制程序的执行,它必须具有以下基本功能:

计算机不断重复顺序执行上述三种基本操作:取指,分析,执行;再取指,再分析,再执行,如此循环,直到遇到停机指令或外来的干预为止.

4).控制程序和数据的输入与结果输出

根据程序的安排或人的干预,在适当的时候向输入输出设备发出一些相应的命令来完成I/O功能,这实际上也是通过执行程序来完成的.

5).对异常情况和某些请求的处理

当机器出现某些异常情况,诸如算术运算的溢出和数据传送的奇偶错等;或者某些外来请求,诸如磁盘上的成批数据需送存储器或程序员从键盘送入命令等,此时由这些部件或设备发出: 

(1)“中断请求”信号.

根据对控制器功能分析,得出控制器的基本组成如下:

即指令地址寄存器.在某些计算机中用来存放当前正在执行的指令地址;而在另一些计算机中则用来存放即将要执行的下一条指令地址;而在有指令预取功能的计算机中,一般还需要增加一个程序计数器用来存放下一条要取出的指令地址.

有两种途径来形成指令地址,其一是顺序执行的情况,通过程序计数器加“1”形成下一条指令地址(如存储器按字节编址,而指令长度为4个字节,则加“4”).其二是遇到需要改变顺序执行程序的情况,一般由转移类指令形成转移地址送往程序计数器,作为下一条指令的地址.

用以存放当前正在执行的指令,以便在指令执行过程中,控制完成一条指令的全部功能.

3). 指令译码器或操作码译码器

对指令寄存器中的操作码进行分析解释,产生相应的控制信号.

在执行指令过程中,需要形成有一定时序关系的操作控制信号序列,为此还需要下述组成部分.

4).脉冲源及启停线路

脉冲源产生一定频率的脉冲信号作为整个机器的时钟脉冲,是机器周期和工作脉冲的基准信号,在机器刚加电时,还应产生一个总清信号(reset).启停线路保证可靠地送出或封锁时钟脉冲,控制时序信号的发生或停止,从而启动机器工作或使之停机.

5).时序控制信号形成部件

当机器启动后,在CLK时钟作用下,根据当前正在执行的指令的需要,产生相应的时序控制信号,并根据被控功能部件的反馈信号调整时序控制信号.例如,当执行加法指令时,若产生运算溢出的异常情况,一般不再执行将结果送入目的寄存器(或存储单元)的操作,而发出中断请求信号,转入中断处理;又如执行条件转移指令时,根据不同的条件产生不同的控制信号,从而进入适当的程序分支.

    计算机工作的过程是取指令,分析指令,执行指令三个基本动作的重复.考虑到所有的器件中(寄存器,存储器)存储器的速度最慢,因此,取最慢的器件工作时间(周期)作为整个工作的最长同步标准.

    计算机的工作时序是按照存储器的工作周期划分的.每个存储器工作周期又称为机器周期.因此,每个机器周期至少完成一个基本操作.一般最长的操作是访问存储器(读/写),这个时间也用于访问外设接口(寄存器).如果,某个操作,比如利用运算器执行一次运算,如果不访问存储器,即使占用的时间很短,但是,也必须为其划分一个机器周期.因此,机器周期是计算时序划分的最大单位.

现在我们为计算机的执行时间进行最基本的划分:由于计算机不断地重复执行每个指令,所以,我们将执行的时间划分为一条一条指令执行所占用的时间,如下:

    我们将每指令占用的时间称为指令周期.由于每条指令的功能不一样,因此执行的时间也不同,指令周期长短不一样.

    而每条指令的执行,又可以是取指令,分析指令,执行指令.由于取指令必须访问存储器,所以占用一个机器周期.分析指令是由指令译码电路完成的,所占用的时间极短,无需分配一个完整的机器周期.一般是在取指周期后期(结束之前的很短时间内)就可以完成.指令的执行较为复杂:可能不访问存储器;访问一次存储器;访问两次存储器等.因此,可能是一个机器周期到几个机器周期.

因此,每条指令的执行过程如下:

    第一个机器周期总是取指周期,而指令的地址总是从PC中获得,当发出读取存储器命令后,指令总是从数据总线DB送回,CPU接受到指令之后,将指令放在指令寄存器IR之中.指令在IR中一直保留到取下一条指令为止.

    所以,根据指令执行的不同情况,将会得到不同指令执行所占用的机器周期.

    根据每个机器周期完成的任务不同,我们将每个机器周期按照任务命名.如同用取指周期命名第一个机器周期一样.

加法指令完成以下操作:

从存储器取指令,送入指令寄存器,并进行操作码译码(分析指令).

计算数据地址,将计算得到的有效地址送地址寄存器AR.

    控制器发出的控制信号:AR→AB,W/R=0,M/IO=1;DB→DR(将地址寄存器内容送地址总线,同时发访存读命令,存储器读出数据送数据总线后,打入数据寄存器).

进行加法运算,结果送寄存器,并根据运算结果置状态位N,Z,V,C.

(三) 数据通路的功能和基本结构

CPU的数据通路是连接CPU内部各个部件以及和CPU外部个部件之间的数据和控制信号的连接关系图.

(四) 控制器的功能和工作原理

控制器控制信号的产生是采用逻辑电路,也称组合逻辑电路控制方式. “时序控制信号形成部件”是由硬逻辑布线完成的.实际设计中,需要几十~几百条指令,确定每条指令所需的机器周期,将情况相同的指令归并在一起,列出表达式,画出逻辑图.

每一步由一个机器周期来完成,假设采用4个机器周期,总之,需要4个不同的信号输出,代表4个不同的周期.

  指令的操作码部分指出本指令将执行什么指令,如加法,减法等.对于不同的指令,采用不同的代码表示.

(3)操作控制信号的产生

  以加法指令为例,加法指令的完成是由4个机器周期cy1,cy2,cy3,cy4组成,分别是取指,计算地址,取数,计算4个机器周期.

将所有的机器周期的操作控制信号的逻辑表达式全部写出来,就会得到各个操作控制信号的所有表达式,再将这些表达式安每个操作控制信号组合起来,就得到某个操作控制信号的表达式.

取指周期需要产生的操作控制信号如下:

计算地址周期cy2需要完成有效地址((rs1)+Disp)的计算.产生的操作控制信号如下:

例如,“+”操作控制信号在加法指令的cy2(计算有效地址)和cy4(操作数相加)时需要;减法指令的cy2(计算有效地址)时需要;转移指令的cy2(计算有效地址)时需要;….

  所以,“+”操作控制信号的逻辑表达式如下:

  设机器有7位操作码(OP0~OP6),假设加法指令的操作码为0001100,形成的加法指令信号的逻辑表达式为:

如,某机器128条指令,用7位操作码(OP0~OP6),如果其中有16条算术逻辑运算指令,可以将这些指令的3位操作码都设计相同的编码,如OP0OP1OP2= 001,而其他位OP3~OP6编码表示16个不同的指令.

  设命令A是所有算术逻辑运算在cy2周期需要产生的,逻辑表达式:

  只需要一个与门,就可实现命令A.

(1)微程序,微指令和微命令

在计算机中,一条指令的功能是通过按一定次序执行一系列基本操作完成的,这些基本操作称为微操作.例如,前面讲到的加法指令,分成四步(取指令,计算地址,取数,加法运算)完成,每一步实现若干个微操作.实现这些微操作的控制命令就是微命令.

微操作是指最基本的,不可再分的操作,如前面提到的:

PC→AB等就是微命令.

微指令:在微程序控制的计算机中,将由同时发出的控制信号所执行的一组微操作称为微指令,所以微指令就是把同时发出的控制信号的有关信息汇集起来而形成的.将一条指令分成若干条微指令,按次序执行这些微指令,就可以实现指令的功能.组成微指令的微操作又称微命令.

微程序:计算机的程序由指令序列构成,而计算机每条指令的功能均由微指令序列解释完成,这些微指令序列的集合就叫做微程序.

(2)微指令的编码方式;

在微指令的控制字段中,每一位代表一个微命令,在设计微指令时,是否发出某个微命令,只要将控制字段中相应位置成“1”或“0”,这样就可打开或关闭某个控制门,这就是直接控制法.

在计算机中的各个控制门,在任一微周期内,不可能同时被打开,而且大部分是关闭的(相应的控制位为“0”).所谓微周期,指的是一条微指令所需的执行时间.如果有若干个(一组)微命令,在每次选择使用它们的微周期内,只有一个微命令起作用,那么这若干个微命令是互斥的.

选出互斥的微命令,并将这些微命令编成一组,成为微指令字的一个字段,用二进制编码来表示, 就是字段直接编译法.

字段间接编译法是在字段直接编译法的基础上,进一步缩短微指令字长的一种编译法.      如果在字段直接编译法中,还规定一个字段的某些微命令,要兼由另一字段中的某些微命令来解释,称为字段间接编译法. 

(3)微地址的形式方式.

1)微程序入口地址的形成

  当操作码的位数与位置固定时,可直接使操作码与入口地址的部分位对应.

    先按照指令类型标志转移到某条微指令,以区分出是哪一大类,然后可以进一步按指令操作码转移,区分出是该指令中的哪一类具体操作.

2)微程序后继地址的形成

<1>以增量方式产生后继微地址.

    在顺序执行微指令时,后继微地址由现行微地址加上一个增量(通常为1)形成的;而在非顺序执行时则要产生一个转移微地址.

<2>增量与下址字段结合产生后继微地址

    将微指令的下址字段分成两部分:转移控制字段BCF和转移地址字段BAF,当微程序实现转移时,将BAF送?PC,否则顺序执行下一条微指令(?PC+1).

1. 指令流水线的基本概念

流水线技术是一种显著提高指令执行速度与效率的技术.方法是:指令取指完成后,不等该指令执行完毕即可取下一条指令.

如果把一条指令的解释过程进一步细分,例如,把分析,执行两个过程分成取指,译码,执行,访存和写回寄存器五个子过程,并用五个子部件分别处理这五个子过程.

  这样只需在上一指令的第一子过程处理完毕进入第二子过程处理时,在第一子部件中就开始对第二条指令的第一子过程进行处理.随着时间推移,这种重叠操作最后可达到五个子部件同时对五条指令的子过程进行操作.

(2)影响流水线性能的因素

在流水线中会出现三种相关,影响流水线的畅通流动,这三种相关是结构相关,数据相关和控制相关.

结构相关是当多条指令进人流水线后,硬件资源满足不了指令重叠执行的要求时产生的.

数据相关是指令在流水线中重叠执行时,当后继指令需要用到前面指令的执行结果时发生的.

控制相关是当流水线遇到分支指令和其他改变PC值的指令时引起的.

流水线的性能通常用吞吐率,加速比和效率3项指标来衡量.

   在指令流水线中,吞吐率是指单位时间内流水线所完成的指令或输出结果的数量.

   流水线的加速比是指m段流水线的速度与等功能的非流水线的速度之比.

效率是指流水线中个功能段的利用率.

2. 超标量和动态流水线的基本概念

在超标量的处理器结构中,整数和浮点数运算,装入,存储以及条件转移等普通操作指令可以同时启动并独立执行.

超标量流水CPU是指集成了多条流水线结构的CPU,当流水线满载时,每个时钟周期可以完成一条以上的指令.

流水线按功能可分成单功能流水线和多

 同样,根据摩尔定律,我们知道单核

的主频不可能无限制的增长,要想很多的提升新能,需要多个处理器协同工作,

总裁的贝瑞特单膝下跪事件标志着多核时代的到来。

 基于高速缓存的存储交互很好的解决了处理器与内存之间的矛盾,也引入了新的问题:缓存一致性问题。在多处理器系统中,每个处理器有自己的高速缓存,而他们又共享同一块内存(下文成主存,

主要内存),当多个处理器运算都涉及到同一块内存区域的时候,就有可能发生缓存不一致的现象。为了解决这一问题,需要各个处理器运行时都遵循一些协议,在运行时需要将这些协议保证数据的一致性。这类协议包括

虚拟机内存模型中定义的访问操作与物理计算机处理的基本一致!

中通过多线程机制使得多个任务同时执行处理,所有的线程共享

,而每个线程又单独的有自己的工作内存,当线程与内存区域进行交互时,数据从主存拷贝到工作内存,进而交由线程处理(操作码

操作数)。更多信息我们会在后面的《深入

类执行机制中详细解说》。

在之前,我们也已经提到,JVM的逻辑内存模型如下:

 我们现在来逐个的看下每个到底是做什么的!

程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看

做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,

各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变

这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、

线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现

的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行

一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要

有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内

存区域为线程私有的内存。

如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节

码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。此

内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。

它的生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执

行的时候都会同时创建一个栈帧(Stack Frame ①)用于存储局部变量表、操作栈、动态

链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在

虚拟机栈中从入栈到出栈的过程。

经常有人把Java 内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗

糙,Java 内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序

员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的在后

面会专门讲述,而所指的就是现在讲的虚拟机栈,或者说是虚拟机栈中的局部变

局部变量表存放了编译期可知的各种基本数据类型(booleanbytecharshortint

floatlongdouble)、对象引用(reference 类型,它不等同于对象本身,根据不同的虚拟

机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或

者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。

其中64 位长度的longdouble 类型的数据会占用2 个局部变量空间(Slot),其余

的数据类型只占用1 个。局部变量表所需的内存空间在编译期间完成分配,当进入一个

方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间

不会改变局部变量表的大小。

Java 虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大

于虚拟机所允许的深度,将抛出StackOverflowError 异常;如果虚拟机栈可以动态扩展

(当前大部分的Java 虚拟机都可动态扩展,只不过Java 虚拟机规范中也允许固定长度的

虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError 异常。

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其

区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则

是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语

言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至

有的虚拟机(譬如Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。

对于大多数应用来说,Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的

一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的

唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java

拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配①,但是随着JIT 编译器

的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换②优化技术将会导致一些微妙

的变化发生,所有的对象都分配在堆上也渐渐变得不是那么绝对了。

Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GCGarbage

Collected Heap,幸好国内没翻译成垃圾堆)。如果从内存回收的角度看,由于现在

收集器基本都是采用的分代收集算法,所以Java 堆中还可以细分为:新生代和老年代;

的角度看,线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local

Allocation BufferTLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,

存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配

内存。在本章中,我们仅仅针对内存区域的作用进行讨论,Java 堆中的上述各个区域的

分配和回收等细节将会是下一章的主题。

根据Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要

逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小

的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx

-Xms 控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出

方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存

储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽

Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-

Heap(非堆),目的应该是与Java 堆区分开来。

对于习惯在HotSpot 虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区

拟机的设计团队选择把GC 分代收集扩展至方法区,或者说使用永久代来实现方法区而

已。对于其他虚拟机(如BEA JRockitIBM J9 等)来说是不存在永久代的概念的。即

使是HotSpot 虚拟机本身,根据官方发布的路线图信息,现在也有放弃永久代并搬家

Java 虚拟机规范对这个区域的限制非常宽松,除了和Java 堆一样不需要连续的内

存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾

收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一

永久存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸

载,一般来说这个区域的回收成绩比较难以令人满意,尤其是类型的卸载,条件

相当苛刻,但是这部分区域的回收确实是有必要的。在Sun 公司的BUG 列表中,曾出

现过的若干个严重的BUG 就是由于低版本的HotSpot 虚拟机对此区域未完全回收而导

根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出

类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool

Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放

到方法区的运行时常量池中。

Java 虚拟机对Class 文件的每一部分(自然也包括常量池)的格式都有严格的规

定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、

装载和执行。但对于运行时常量池,Java 虚拟机规范没有做任何细节的要求,不同的

提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除

了保存Class 文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常

运行时常量池相对于Class 文件常量池的另外一个重要特征是具备动态性,Java

言并不要求常量一定只能在编译期产生,也就是并非预置入Class 文件中常量池的内容

才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发

既然运行时常量池是方法区的一部分,自然会受到方法区内存的限制,当常量池无

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java

虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致

OutOfMemoryError 异常出现,所以我们放到这里一起讲解。

与缓冲区(Buffer)的I/O 方式,它可以使用Native 函数库直接分配堆外内存,然

后通过一个存储在Java 堆里面的DirectByteBuffer 对象作为这块内存的引用进行

操作。这样能在一些场景中显著提高性能,因为避免了在Java 堆和Native 堆中来

显然,本机直接内存的分配不会受到Java 堆大小的限制,但是,既然是内存,则

肯定还是会受到本机总内存(包括RAMSWAP 区或者分页文件)的大小及处理器

寻址空间的限制。服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx

等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制

(包括物理上的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError

逻辑内存模型我们已经看到了,那当我们建立一个对象的时候是怎么进行访问的呢?

Java 语言中,对象访问是如何进行的?对象访问在Java 语言中无处不在,是最普通的程序行为,但即使是最简单的访问,也会却涉及Java 栈、Java 堆、方法区这三个最重要内存区

域之间的关联关系,如下面的这句代码:

假设这句代码出现在方法体中,那“Object obj”这部分的语义将会反映到Java

的本地变量表中,作为一个reference 类型数据出现。而“new Object()”这部分的语义

将会反映到Java 堆中,形成一块存储了Object 类型所有实例数据值(Instance Data,对

象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布

局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java 堆中

还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地

址信息,这些类型数据则存储在方法区中。

由于reference 类型在Java 虚拟机规范里面只规定了一个指向对象的引用,并没有

定义这个引用应该通过哪种方式去定位,以及访问到Java 堆中的对象的具体位置,因此

不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄和直接

如果使用句柄访问方式,Java 堆中将会划分出一块内存来作为句柄池,reference

中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的

具体地址信息,如下图所示。

 如果使用直接指针访问方式,

堆对象的布局中就必须考虑如何放置访问类型

数据的相关信息,reference 中直接存储的就是对象地址,如下图所示

 这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是

储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只

会改变句柄中的实例数据指针,而reference 本身不需要被修改。

使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开

销,由于对象的访问在Java 中非常频繁,因此这类开销积少成多后也是一项非常可观的

执行成本。就本书讨论的主要虚拟机Sun HotSpot 而言,它是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各种语言和框架使用句柄来访问的情况也十分常见。

下面的程中我们限制Java 堆的大小为20MB,不可扩展(将堆的最小值-Xms

数与最大值-Xmx 参数设置为一样即可避免堆自动扩展),通过参数-XX:+HeapDump

OnOutOfMemoryError 可以让虚拟机在出现内存溢出异常时Dump 出当前的内存堆转储

快照以便事后进行分析。

异常是实际应用中最常见的内存溢出异常情况。出现

要解决这个区域的异常,一般的手段是首先通过内存映像分析工具(如Eclipse

Memory Analyzer)对dump 出来的堆转储快照进行分析,重点是确认内存中的对象是

否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢

如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots 的引用链。于是就

能找到泄漏对象是通过怎样的路径与GC Roots 相关联并导致垃圾收集器无法自动回收

它们的。掌握了泄漏对象的类型信息,以及GC Roots 引用链的信息,就可以比较准确

地定位出泄漏代码的位置。

如果不存在泄漏,换句话说就是内存中的对象确实都还必须存活着,那就应当检查

虚拟机的堆参数(-Xmx-Xms),与机器物理内存对比看是否还可以调大,从代码上

检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期

以上是处理Java 堆内存问题的简略思路,处理这些问题所需要的知识、工具与经验

在后面的几次分享中我会做一些额外的分析。

3、常量池溢出(常量池都有哪些信息,我们在后续的JVM类文件结构中详细描述)

我要回帖

更多关于 堆栈 的文章

 

随机推荐