精华内容
下载资源
问答
  • 虚存,内存,主存,辅存

    千次阅读 2016-01-21 16:23:35
    1、内存  cache和主存构成了系统的内存。 2、虚拟存储器  主存和辅存依靠辅助软... 进程在运行时,其页表中一部分在主存中,另一部分在辅存中保存。  由于主存属于内存,辅存属于外存,所以粗糙点讲,页表

    1、内存

       cache和主存构成了系统的内存。

    2、虚拟存储器

       主存和辅存依靠辅助软硬件的支持构成了虚拟存储器。软硬件是指地址转换部件。

      每一个程序拥有一个虚拟存储器,这个虚拟存储器可以是页表,段表等。

    3、页表有两处保存地方

      与上面两句话对应,

      当一个进程在运行时,其页表中一部分在主存中,另一部分在辅存中保存

       这主要是因为由于虚地址空间可以很大,要节省页表本身占用的主存空间。

      由于主存属于内存,辅存属于外存,所以粗糙点讲,页表中一部分在内存中,另一部分在外存中保存。

    4、调页操作

       在主存部分的页表称为内页表,在辅存部分的页表称为外页表。

      当虚页不在内存时,有存储管理部件向cpu发出"缺页中断",进行调页操作。

     

    5、为什么要使用虚拟存储器

      同样是因为不知道

      在多用户多任务系统中,多个用户或多个任务共享全部内存,要求同时执行多道程序。这些同时运行的程序到底占用实际内存的哪一个部分,在编制程序时是无法确定,必须等到程序运行时才能动态分配。

     

     

     

     

      

     


    展开全文
  • 地址映象:是将每个虚存单元按某种规则装入实存,即建立多用户虚地址与实存地址之间的对应关系。地址变换:是程序按照这种映象关系装入实存后,执行时,多用户虚地址如何变换成对应的实存地址。 页面争用(实页...
    地址映象:是将每个虚存单元按某种规则装入实存,即建立多用户虚地址与实存地址之间的对应关系。
    地址变换:是程序按照这种映象关系装入实存后,在执行时,多用户虚地址如何变换成对应的实存地址。

    页面争用(实页冲突):发生两个以上的虚页想要进入主存中同一个页面位置的现象。

    由于虚存空间远远大于实存空间,因此页式虚拟存储器常采用全相联映像


    替换算法

    当发生页面失效时,要从磁盘中调入一页到主存。如果主存所有页面都已经被占用,必须从主存储器中淘汰掉一个不常使用的页面,以便腾出主存空间来存放新调入的页面。

    • 随机算法(RAND):用软的或硬的随机数产生器来形成主存重要被替换页的页号。
      简单,易于实现
      没有利用历史信息
      命中率低,很少使用
    • 先进先出(FIFO):选择最早装入主存的页作为被替换的页。
      配置计数器字段
      虽然利用历史信息,但不一定反映出程序的局部性
    • 近期最少使用(LRU):选择近期最少访问的页作为被替换的页。
      配有计数器字段
      比较正确反映程序的局部性
    • 优化替换算法(OPT):在时刻t找出主存中每个页将要用到时刻ti,然后选择其中ti-t最大的那一页作为替换页。
      理想化算法

    例子:设有一道程序,有1至5共5页,执行时的地址流为:
    2,3,2,1,5,2,4,5,3,2,5,2

    主存页为3。分别采用FIFO、LRU、OPT算法。


    其中LRU是以历史信息为判断依据,而OPT则建立在对未来使用页的预知(故称理想算法)。

    展开全文
  • MESI协议MESI协议是一个...然而,多核CPU,低级别的缓存是单个CPU独占的:如上图所示,每个CPU核心分别拥有独立的一、二级缓存,共享了三级缓存。这就带来了缓存一致性的问题:同一份数据同时存在于多个CPU的独...

    MESI协议

    MESI协议是一个被广泛使用的CPU缓存一致性协议。我们都知道在CPU中存在着多级缓存,缓存级别越低,容量就越小,速度也越快。有了缓存,CPU就不需要每次都向主存读写数据,这提高了CPU的运行速度。然而,在多核CPU中,低级别的缓存是单个CPU独占的:

    1e58ffc5acc20d8ca502841933f993ac.png

    如上图所示,每个CPU核心分别拥有独立的一、二级缓存,共享了三级缓存。这就带来了缓存一致性的问题:当同一份数据同时存在于多个CPU的独立缓存中时,如何保证缓存数据的一致性?

    MESI协议提供了一种方式,成功的解决了缓存一致性问题。对于缓存中的每一行,都设置一个状态位,一共有四种状态:

    • M(modified):表示缓存行仅存在于当前的缓存中,并且已经被更改。在该缓存行写回到主存之前,任何其他CPU都不能读取该缓存行的内容。
    • E(exclusive):表示缓存行仅存在于当前的缓存中,并且未被修改。如果有其他CPU读取该行,则转移到Shared状态;如果修改该行,则转移到Modified状态。
    • S(shared):表示有多个CPU共享该缓存行,且内容未被修改。
    • I(invalid):表示缓存行已失效(未被使用)。

    从上述状态定义可以看出,MESI协议实际上定义了一个状态机,其中状态转移规则保证了CPU在多级缓存环境下的缓存一致性。MESI定义的状态转移规则如下所述:

    除Invalid状态以外,所有状态的缓存行都可以进行读操作

    从状态定义就可以看出,只有Invalid状态的缓存行内容是无效的,必须从主存读取。

    只有在状态为M或E时才能进行写操作,如果当前状态为S,则其它CPU中的同一行必须转移到状态I,这是通过发送RFO(request for ownership)广播实现的

    M或E状态下,缓存行都只存在于单个CPU中,S状态下多个CPU共享一行,因此必须将其他CPU的状态置为I,才能进行写操作。

    除M以外的任意状态都可转移到I,M状态必须先写回到主存再丢弃

    只要内容未被修改,CPU可以再任意时刻丢弃一个缓存行,否则则必须先把修改的内容写回到主存

    处于M状态的缓存必须拦截其他CPU对同一行的读操作,并返回自身缓存中的数据

    这可以保证所有CPU读到的都是最新的内容,这种拦截操作称为snoop,数据不需要写回到主存,直接由M状态的缓存返回,状态由M转移到S

    处于S状态的缓存必须监听RFO广播,并转移到I状态

    当一个CPU修改S状态的缓存时,其余的缓存必须先转移到I状态,防止并发的写操作

    处于E状态的缓存必须拦截其他CPU的读操作,并转移到S状态

    当有其他CPU读E状态的缓存时,状态必须由独占转移到共享

    上述过程可以用下面这幅图来描述:

    11ecc75bebfad453098f29a62c1f850d.gif

    那么问题来了

    MESI很好的解决了缓存一致性的问题,但是也不可避免的带来了额外的开销。考虑一个简单的赋值操作a=1,假设变量所在的缓存行不在当前CPU中,则CPU需要发送"read invalidate"消息,获取缓存行,并且告知其他CPU丢弃该缓存行,然后该CPU必须等待,直到收到来自其他CPU的确认响应为止:

    6c58ef6fd011c84dc106a09867c6a655.png

    而实际上,无论a之前的值为何,在该条指令执行后都会被覆盖,因此这段等待的开销是完全没有必要的。为此,CPU的设计者加入了store buffer,用于缓存store指令对缓存行的修改:

    e68f5be279bcfb1e97b16120c3e31e4a.png

    如图所示,对缓存行的修改操作不会立刻执行到缓存行上,而是先进入store buffer,这样CPU的写操作就不需要等待到从其他CPU得到缓存行才能执行。CPU可以立即执行写操作,等到得到缓存行时,才将变更从store buffer写入缓存行。然而,这又立刻带来了另外一个问题,由于store buffer的存在,在CPU中同一个变量可能存在两份拷贝(当缓存行到达CPU时,缓存和store buffer中存在同一个变量的两份拷贝),这无疑破坏了缓存的一致性,若CPU在store buffer写入缓存之前load数据,就会拿到旧的数据。为了解决这个问题,CPU设计者又加入了store forwarding机制,简单的讲就是CPU会优先从store buffer中取变量,保证同一时刻一个变量在单个CPU中的一致性:

    5d57a432437212a368005dc5de0bc4a1.png

    然而,这样做并不能解决另外一个问题,那就是隐式的数据依赖,考虑下面两个

    代码清单1:foo(){ a = 1; b = 1;}bar(){ while(b == 0) continue; assert(a == 1);}复制代码

    假设CPU0执行foo,CPU1执行bar,并且a处于CPU1的缓存中。由于store buffer的存在,对a的写操作会立刻执行,而不会等待其他CPU的invalidate响应。CPU0接着执行b=1,CPU1获取到最新的b以后,执行assert语句,此时,CPU1有可能尚未收到来自CPU0的invalidate消息,因而a有可能仍在CPU1的缓存中,并且值未被改变,从而导致assert失败。

    内存屏障

    引入store buffer带来了性能的提升,却导致MESI协议无法保障缓存的一致性。从上一节中的例子可以看出,一致性问题的出现来源于数据之间的隐式依赖,也就是说必须保证某个操作在另外一个操作之前完成。比如a=1这个操作必须写入到cache line(只有在cpu收到invalidate响应时,才会把数据从store buffer写入cache line),才能执行b=1。但是CPU是无法探测到这种隐式相关性的,必须由程序员自己来进行控制。因此CPU提供了内存屏障指令,该指令使得屏障之前的写操作都在屏障之后的写操作之前完成:

    代码清单2:foo(){ a = 1; smp_mb(); // 加入内存屏障 b = 1;}bar(){ while(b == 0) continue; assert(a == 1);}复制代码

    smp_mb的实际功能是对store buffer中的变量标记,这样当CPU0执行b=1时,发现store buffer中存在标记过的变量,就不能立刻将b=1写入缓存行,而是将其写入store buffer(但不进行标记)。等到CPU0收到invalidate响应,将store buffer中的标记变量写入缓存行,b=1才会写入到缓存行。在此期间,由于标记变量的存在,所有对b的读操作都只能读到b的原始值,也就是0,导致CPU1无法执行到assert语句。

    除了写操作等待,invalidate操作的开销也很大,因为它的存在,CPU不得不频繁丢弃缓存行,导致缓存命中率低下。为了进一步提升性能,CPU中又加入了invalidate队列(invalidate queue),CPU收到invalidate消息以后会立刻发送响应,但并不立刻处理,而是将该消息放入队列,等到适当的时候再处理。与store buffer类似,这么做的副作用也是破坏了MESI协议,延迟响应的代价就是缓存中可能存在过期的数据。这个问题同样可以用内存屏障来解决:

    代码清单3:foo(){ a = 1; smp_mb(); // 加入内存屏障 b = 1;}bar(){ while(b == 0) continue; smp_mb(); // 加入内存屏障 assert(a == 1);}复制代码

    bar()函数的内存屏障保证了屏障之前的invalidate消息都会执行,然后才执行后面的指令。这样CPU1执行assert时,发现a的缓存行已经失效,只能尝试读取,此时CPU0会返回最新的数据a=1,assert执行成功。为了进一步提升效率,CPU还支持对store buffer和invalidate队列单独进行操作,这就是写屏障和读屏障。写屏障保证屏障之前的写操作对其他CPU都是可见的;读屏障保证屏障之后的读操作读到的都是最新的数据。

    Java中的内存屏障

    Java中的 volatile 关键字可以用来修饰变量,它可以保证:

    可见性 一个线程对volatile变量的写操作可以立刻被其他线程看到

    原子性 对volatile变量的单个读/写操作具有原子性

    在某些jvm中,对 long 和 double 的读写操作是不具有原子性的,而是会拆成两部分:对高32位和低32位分别赋值。因此,假设线程a在读 long 变量l时,线程b也在写入,那么线程a可能读到的数据可能一半是新的,一半是旧的。如果将 long 和 double 变量声明为volatile,则可能保证变量的读写具有原子性。但是要注意,这个原子性只是对读写的单个操作而言的,对于复合操作则不能保证:

    代码清单4:volatile int a = 0;incrementAndGet(){ a++; return a;}复制代码

    如果指望代码清单4中的a变量能够正确的增长,恐怕要失望了。因为a++这个操作实际上是由读取-修改-写入三个操作组成的,在并发环境中,这样的操作不具有原子性,数据更新很有可能会丢失。

    可见性 又是怎么一回事呢?这就涉及到了内存屏障,为了使得对volatile变量的修改对其他线程总是可见的,jvm会执行如下操作:

    在volatile变量的写操作之后插入写屏障

    插入写屏障之后,屏障之前的写操作对于其他CPU都是可见的,需要注意的是此处的可见性并不只针对标记为volatile的变量,而在所有在屏障之前执行了写操作的共享变量(写屏障是对store buffer中存在的所有变量进行标记)。

    在对volatile变量的读操作之前插入读屏障

    插入读屏障之后,本地缓存中所有被更改过的共享变量会立刻失效(通过执行invalidate队列中的消息实现)。这样,在屏障之后读取共享变量时,由于缓存失效,只能向主存或其他CPU发送读取请求,从而保证了读到的一定是最新的值。

    代码清单5是一个简单的实例

    代码清单5:class MBExample{ int a = 0; volatile boolean flag = false; void foo(){ a = 2; flag = true; } void bar(){ if(flag) a ++; else a --; }}复制代码

    假设线程t1和t2共享MBExample的一个实例,t1执行foo, t2执行bar。假设foo先于bar执行,此时我们一定期望a最终的值为3。但是,如果flag变量未被标记为 volatile ,根据之前的讨论,由于store buffer和invalidate queue的存在,t2未必能获得最新的a和flag的值(例如假设一开始a以S状态存在于t1,t2的cache line中,而flag以E状态存在与t1的cache line,最终的结果有可能为a=1)。如果flag被标记为volatile,代码清单5实际上变成了如代码清单6的情形:

    代码清单6:...void foo(){ a = 2; // ---------------------------1 flag = false; // --------------------2 smp_wmb(); // 写屏障}void bar(){ smp_rmb(); // 读屏障 if(flag) // -------------------------3 a ++; // -----------------------4 else a --;}...复制代码

    事实上,volatile的作用不止于此,对于JIT编译器而言,volatile还是"指令屏障",如果编译器出于性能优化的考虑对指令进行重排序,有可能破坏程序的原本意图,volatile对这一行为进行了限制。Java内存模型针对volatile的指令重排序做了如下规定:

    • 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。
    • 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。
    • 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。

    因此,当foo在bar之前执行时,实际上产生了一种偏序关系,如代码清单6所示1 >> 2 >> 3 >> 4,最终使得4中读取到的a的值一定为2。这种指令重排的约束仅对JIT生效,因为java字节码解释器的解释执行是line by line的,指令的先后顺序天然的得到保留。

    参考资料

    Memory Barriers: a Hardware View for Software Hackers

    MESI protocol

    深入理解Java内存模型(四)——volatile

    Memory Barriers/Fences

    Non-atomic Treatment of double and long

    我自己收集了一些Java资料,里面就包涵了一些BAT面试资料,以及一些 Java 高并发、分布式、微服务、高性能、源码分析、JVM等技术资料

    资料获取方式:关注我并私信“666”即可免费获取

    8075006cb46bb2a80afbd7c5a4738f7a.png
    89a96dcc41dee38bd863cfd34b16c2b4.png
    展开全文
  • Java的volatile关键字

    千次阅读 2019-10-24 19:05:10
    并发编程的三基本概念: 原子性 可见性 有序性 编写多线程程序时,经常会看到 volatile 关键字。 Java,volatile 被用来修饰 变量。 使用 volatile 修饰的...线程需要对 主存 的变量进行读写时,会先...

    并发编程的三个基本概念:

    • 原子性
    • 可见性
    • 有序性

    编写多线程程序时,经常会看到 volatile 关键字。

    在Java中,volatile 被用来修饰 变量。

    使用 volatile 修饰的变量有什么特点呢?

    JMM内存划分

    在Java内存模型中,内存区域的划分大致如下图所示:

    在这里插入图片描述

    除了共享的 主内存 外,每个Java线程都有一块自己私有的工作内存。

    当线程需要对 主存 中的变量进行读写时,会先从 主存 加载到自己的工作内存中,然后进行读写,再刷新到主存中去。

    对于多次读取,如果工作内存中已经有缓存,就不会再去 主存 中加载了。
    这就会导致:多线程下数据读取不一致的问题。

    使用 volatile 修饰的变量,会让该变量在线程的工作内存中的缓存失效,每次读取时都去 主存 中加载。

    volatile 可以保证可见性,但是不能保证原子性。
    对于非原子操作,仅仅使用 volatile 仍然会有数据安全问题。

    指令重排序

    Java代码机器无法识别,要想运行,最终会被编译成一条条指令。

    编译器在编译代码时,为了优化程序性能会自动将指令进行:重排序。

    重排序的目的是为了 优化性能,但是不管指令如何重排,单线程下程序的运行结果都是一致的。

    使用 volatile 修饰的变量,编译器会对其 禁止使用重排序。

    使用场景

    volatile 是Java提供的一种比 synchronized 更轻量级的一种同步机制。
    它的安全性没有 synchronized 高,但是开销低。

    变量的操作如果都是原子的,那么可以放心使用。
    如果是非原子操作,例如:i++,就会存在数据安全问题。

    适合的场景:

    • 使用于 读多写少。
    • 写操作不依赖当前值。
    • 用作状态标记。
    展开全文
  • 如果你的Java程序是多线程的,你的Java程序中每个CPU上一个线程可能同时(并发)执行。一个CPU需要读取主存时,它会将主存的部分读到CPU缓存。它甚至可能将缓存的部分内容读到它的内部寄存器,然后寄存器...
  • Java之:JVM内存模型

    千次阅读 2016-06-17 15:49:35
    二、JVM内存模型总图Java通过多线程机制使得多个任务同时执行处理,所有的线程共享JVM内存区域main memory,而每个线程又单独的有自己的工作内存,线程与内存区域进行交互时,数据从主存拷贝到工作内存,进而交...
  • 如果你的Java程序是多线程的,你的Java程序中每个CPU上一个线程可能同时(并发)执行。 一个CPU需要读取主存时,它会将主存的部分读到CPU缓存。它甚至可能将缓存的部分内容读到它的内部寄存器,然后...
  • 学习MySQL优化原理

    2021-05-07 10:33:15
    页是计算机管理存储器的逻辑块,...当程序要读取的数据不在主存中时,会触发一缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后一起返回,程序继续运行。
  • 可以很明确地说,java中的...cpu的指令执行是很快的,但是发生在主存中的数据读取和写入却没有这么快,所以就出现了高速缓存的概念。 当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那
  • 运行程序时,JVM都会为执行任务的线程分配一独立的缓存。  支线程对主存中的共享资源进行改变时,先会将共享资源读取到支线程的缓存中来,然后对flag值进行改变,再将flag值写到主存中。但写之前...
  • 计算机在执行程序时,条指令都是在CPU中执行,而在执行指令过程中,就会涉及到数据的读取和写入的问题,由于程序运行过程中的临时数据是存放在主存中的,这时就存在一问题,由于CPU执行速度很快,而从内存中读取...
  • 进程概述 一个可执行程序在现代系统上运行时,操作系统会提供一种假象——好像系统上只有这个程序在运行,看上去只有这个程序在使用处理器,主存和IO设备。  处理器看上去就像在不间断的一条接一条的执行程序...
  • 磁盘上数组的一些内容被缓存在主存中。 虚拟内存的思想:每个进程拥有独立的地址空间,这些空间被分为大小相等块,这些块被称为页,每个页都是一段连续的地址空间。这些页被映射在物理内存中,这些页不必在程序运行...
  • 由于程序运行过程中数据是放在主存中(物理内存),这里面存在一问题,CPU执行速度远快于从内存中读取速度,因此如果对数据的读取操作都要通过和内存交换指令来进行的话,会大大降低指令的执行速度,因此CPU便引入...
  • 线程同步的几种方法

    2018-04-06 21:03:00
    当程序在运行时,会将运算需要的数据从主存复制一份到CPU的高速缓存,CPU进行计算时就可以直接从他的高速缓存读取数据和向其中写入数据,运算结束后,在将高速缓存的数据刷新到主存当中。  每个线程会有自己的...
  • volatile详解

    2021-03-07 21:23:05
    计算机为什么会出现线程不安全的问题 计算机在执行程序时,条指令都是在CPU执行的,而执行指令过程会涉及到数据的读取和写入。由于程序运行过程的...当程序在运行过程,会将运算需要的数据从主存复制一份
  • 由于程序运行过程的临时数据是存放在主存(物理内存)当中的,这时就存在一问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的...
  • 聊聊volatile 

    2020-06-04 17:27:09
    由于程序运行过程的临时数据是存放在主存(物理内存)当中的,这时就存在一问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的...
  • 由于程序运行过程的临时数据是存放在主存(物理内存)当中的,这时就存在一问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的...
  • 深入理解volatile

    2020-12-29 09:52:27
    由于程序运行过程的临时数据是存放在主存(物理内存)当中的,这时就存在一问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的...
  • volatile 先从基础的知识说起... 一个多线程程序执行一个多核心的机器上时,就会出现真正的并行情况,每个线程都独立的运行一个CPU上,每个CPU都有属于自己独立的周边缓存。 那么此时,一个变量被两个线程操作...
  • volatile关键字解析

    2020-08-11 13:04:49
    由于程序运行过程的临时数据是存放在主存(物理内存)当中的,这时就存在一问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的...
  • 由于程序运行过程的临时数据是存放在主存(物理内存)当中的,这时就存在一问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的...
  • 当程序在运行过程,会将运算需要的数据从主存复制一份到 CPU 的高速缓存当中,那么 CPU 进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,运算结束之后,再将高速缓存的数据刷新到主存当中。...
  • 整理自炼数成金 源码连接: 1.线程安全是什么: 指某个函数、函数库多线程环境... –工作内存存放主存中变量的值的拷贝 数据从主内存复制到工作存储时,必须出现两动作:第一,由主内存执行的读(r...
  • volatile 先从基础的知识说起吧,...一个多线程程序执行一个多核心的机器上时,就会出现真正的并行情况,每个线程都独立的运行一个CPU上,每个CPU都有属于自己独立的周边缓存。 那么此时,一个变量被两个线程...
  • 虚拟内存学习总结

    2020-08-17 23:14:48
    考虑一台电脑同时运行了多个应用程序,如果将每个程序的数据都拷贝到物理内存,必然会沾满大量的内存,而且进程切换的时候会涉及到大量数据的拷贝,效率非常低。 而操作系统引入了虚拟内存的概念,应用程序的...

空空如也

空空如也

1 2 3 4
收藏数 80
精华内容 32
关键字:

当每个程序在主存中