精华内容
下载资源
问答
  • 但是这样的弊端也很明显:不能实时的和内存发生信息交换,在不同CPU执行的不同线程对同一个变量的缓存值不同。用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,...

    每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取。但是这样的弊端也很明显:不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并不是一样,java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。内存屏障是什么

    硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。内存屏障有两个作用:阻止屏障两侧的指令重排序;强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。java内存屏障

    java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。 它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能volatile语义中的内存屏障

    volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障; 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;

    由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性。final语义中的内存屏障

    对于final域,编译器和CPU会遵循两个排序规则:新建对象过程中,构造体中对final域的初始化写入和这个对象赋值给其他引用变量,这两个操作不能重排序;(废话嘛)初次读包含final域的对象引用和读取这个final域,这两个操作不能重排序;(晦涩,意思就是先赋值引用,再调用final值)总之上面规则的意思可以这样理解,必需保证一个对象的所有final域被写入完毕后才能引用和读取。这也是内存屏障的起的作用:写final域:在编译器写final域完毕,构造体结束之前,会插入一个StoreStore屏障,保证前面的对final写入对其他线程/CPU可见,并阻止重排序。读final域:在上述规则2中,两步操作不能重排序的机理就是在读final域前插入了LoadLoad屏障。X86处理器中,由于CPU不会对写-写操作进行重排序,所以StoreStore屏障会被省略;而X86也不会对逻辑上有先后依赖关系的操作进行重排序,所以LoadLoad也会变省略。

    展开全文
  • 其实我们都知道,计算机内存本来就是一块内存,没有堆栈之。 在学编程的时候,我们应该都听过一句话 “如果程序结束之后仍然想要访问那一段数据就要用堆(不释放的话,程序修改后的数据仍存在)”,我想这个其实...

    其实我们都知道,计算机内存本来就是一块内存,没有堆栈之分

    在学编程的时候,我们应该都听过一句话 “如果程序结束之后仍然想要访问那一段数据就要用堆(不释放的话,程序修改后的数据仍存在)”,我想这个其实就是本题目的关键了,堆和栈都有其自己的独特性,可能你了解这两个东西,但是我还是解释下,以免别的小伙伴在看答案的时候,不知道。

    linux下一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(私有的)栈(stack)。堆(heap)的分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程共享。(一个进程下的所有线程有自己独立的栈,但是堆是共享进程的堆)

    :就像我第一句话说的,本没有什么堆栈之分,但是编程语言的出现,就有了一个概念“函数”,这个函数之间是可以相互调用的(就像我们传递东西,比如:胡小然 将东西传递 胡小然2 将东西传递 胡小然3,之后需要从后面向前面反馈传递结果,这个传递的过程我们就可以理解为调用),那就出现了前后之分,这就是调用队列了,那这个队列有个什么特点呢,那就是先被调用进入队列的要最后出去,就是我们常说的先进后出(FILO),那么这时栈就出现了,而且它还有一个特点那就是线程独有(所以可以存放其运行状态和局部自动变量、临时变量),生命周期是随线程的,线程结束时对应的栈结束(栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。。当然我所说的是内存栈的意思,其实“栈”就是个数据结构,是一种限定仅在表尾进行插入和删除操作的线性表,这个特性不正好是符合我刚才说的FILO嘛。所以你可以这么理解c++或者java(jvm)中的内存栈的概念,就是编程语言的作者为了管理内存使用了“栈”这种数据结构(说的再细点就是现代CPU体系结构决定了栈是管理函数调用和局部变量的最佳数据结构。因为CPU已经提供了现成的指令)。

    :可算是一种特殊的数据结构,好像我们经常使用的二叉树。内存堆这个解释起来就更简单了,就是一块能自由分配的内存,是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。它允许程序在运行时动态地申请某个大小的内存空间,比如:程序员向操作系统申请一块内存,当系统收到程序的申请时,会遍历一个记录空闲内存地址的链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。其特点就是分配的速度较慢,地址不连续,容易碎片化并且是由程序员申请,同时也必须由程序员负责销毁,否则导致内存泄露。像在java这种高级语言中,我们不比担心内存回收的问题,那是因为jvm已经在帮我们处理了。

    不同的线程有不同的stack栈空间,以及共享的heap堆空间。每个线程确实可以new一片新的内存空间在堆里面,这种方法被称为Thread Local Storage (TLS),但是某一个线程建立的heap空间别的线程也是可以访问的。比如某一个线程使用 Object* o = new Object() 新建一个对象,这个对象是在heap中,而指针o是在stack中,只要这个线程把指针o的值发给别的线程,然后别的线程用另一个指针p来接收,那么别的线程依然能够访问这个线程new的对象,这就是共享heap空间的解释。

    一般不建议在线程中用new开辟新内存空间,因为heap是共享的,所以一个线程在用new的时候别的所有线程都得停下来等,这样就有很大的同步代价. 如果m个线程每个线程分配n大小的内存到heap,那么就需要m次内存分配的操作,所有的线程需要等m次。记住每一个new操作都是很耗时的。我们完全可以只分配一次m*n大小的内存到heap,然后每个线程访问自己需要访问的部分。这样只需要一次内存分配,而且之后的操作没有同步代价。

    上面说了这么多,就是想说明一下内存栈和内存堆出现的意义和作用,所以答案就出来了,那就是不能“只用堆或者全部只用栈”那样我们程序的调用和数据的存储都会出现问题。

    最后说一下两者的特点。

    (1)栈具有先进后出,后进先出特性,连续存储,操作简单,使用方便,无需管理,大部分芯片都对栈提供芯片级别的硬件支持,只需要移动指针就可以快速实现内存的分配和回收。比如局部变量使用栈内存,减少不必要的内存分配管理。栈创建和删除的时间复杂度是O(1),速度快。但是不利于管理大内存,栈中的数据大小和生存周期都是确定的,缺乏灵活性。

    (2)堆内存的管理机制相对复杂,有一套相应的分配策略,防止大量小碎片出现,同时加快查找。堆用于动态创建分配内存,创建和删除节点的时间复杂度是O(logn)。堆的回收机制也复杂很多,根据内存大小不同,数据生命周期不同,采用相应的回收机制,涉及操作系统的堆管理。因为堆内存的管理和申请相对复杂,更消耗系统资源,通常生命周期更长使用范围更广的全局变量使用堆内存。


    为什么每个线程都有单独的栈呢?

    有四个函数A、B、C、D,地址分别为100、200、300、400;有两个线程同时执行;

    1)假如只有一个栈

    • 函数A在线程1中执行的时候,调用了函数B,将函数A中下一条指令的地址入栈(104),然后执行函数B;
    • 在执行函数B过程中,发现要调用Yield()函数(蓝色,Yield()的作用可以理解为切换线程),于是先将B中下一条要执行的指令地址入栈(204),然后执行Yield()切换到地址300处的线程,也就是线程2;
    • 接下来执行函数C,同样道理调用方法D,304入栈;
    • 最后执行函数D,404入栈,而D中的Yield() 会跳到地址204继续执行(切换到线程1的下一条待执行语句对应的地址204);
    • 紧接着,函数B执行完,会返回,返回地址是栈顶的值(404),这里的返回地址本应该是104;

    因此,多个线程共用一个栈就会出现问题!

    2)每个线程一个栈

    再切换线程时,同时也要切换栈,这里就需要一个数据结构TCB(Thread control block)来存储栈的指针;每个线程都有一个TCB。

    线程2中的Yield()函数应该改写成如下格式:

    void Yield(){
    
      TCB2.esp=esp;
      esp=TCB1.esp;
      jmp 204;
    }

    执行过程:

    • 在A函数中,调用B,将地址104入栈(esp=1000);
    • 在函数B中执行Yield(),保存当前栈指针TCB1.esp =esp,同时切换栈指针esp=TCB2.esp,将地址204入栈,跳转到函数C(esp=1000);
    • 在函数C中调用函数D,将地址304入栈(esp=2000);
    • 函数D执行Yield(),保存栈指针,切换栈指针,将地址404入栈,跳转到函数B,继续执行地址204处的代码;
    • 执行完毕,执行 '}' ,弹出线程1栈的栈顶地址204,发现此处重复执行地址204处的指令;

    3)最终

    线程2的Yield():

    void Yield(){
      TCB2.esp=esp;
      esp=TCB1.esp;
    }

    这样在2)中第四步执行时,不再使用jmp 204跳转,而是执行 '}' ,将线程1中的栈顶地址出栈。

    示例

    一个由c/C++编译的程序占用的内存分为以下几个部分:
    1、栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
    2、堆区(heap) — 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
    3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放。
    4、文字常量区   —常量字符串就是放在这里的。程序结束后由系统释放。
    5、程序代码区—存放函数体的二进制代码。
     

    //main.cpp
    int a = 0; 全局初始化区
    char *p1; 全局未初始化区
    main()
    {
      int b; 栈
      char s[] = "abc"; 栈
      char *p2; 栈
      char *p3 = "123456"; 123456\0在常量区,p3在栈上。
      static int c =0;全局(静态)初始化区
      p1 = (char *)malloc(10);
      p2 = (char *)malloc(20);
      //分配得来得10和20字节的区域就在堆区。
      strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
    }

    stack由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
    heap需要程序员自己申请,并指明大小,在c中malloc函数,如p1 = (char *)malloc(10); 在C++中用new运算符,如p2 = (char *)malloc(10);但是注意p1、p2本身是在栈中的。

    参考:

    https://www.zhihu.com/question/335304770/answer/751916155

    https://www.wukong.com/question/6813911448666800392/

    https://blog.csdn.net/qq_38038480/article/details/80437350

    展开全文
  • 在hotspot虚拟机中,对象在堆内存中的存储布局可以划分三部分:对象头,实例数据,对齐填充。 HotSpot虚拟机对象的对象头部包含类信息 用于存储对象自身的运行时数据,如HashCode,GC的代年龄,锁状态标志,...

    在hotspot虚拟机中,对象在堆内存中的存储布局可以划分为三部分:对象头,实例数据,对齐填充
    HotSpot虚拟机对象的对象头部包含两类信息

    • 用于存储对象自身的运行时数据,如HashCode,GC的分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。这部数据的长度在32位和64位的虚拟机中分别为32比特和64比特,官方称为“Mark word”。
    • 另一种是类型指针,即对象指向它的类型元数据的指针,Java通过这个指针确定该对象是哪个类的实例。但是并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息不一定要经过对象本身。

    在32位的HotSpot虚拟机中,如对象未被同步锁锁定的状态下,Mark Word的32个比特存储空间中的25个比特用于存储对象的HashCode,4个比特存储对象分代年龄,2个比特存储锁标志位,一个比特固定为0.

    在这里插入图片描述
    在这里插入图片描述
    根据对32位和64位的Hot Spot的对象头分析,分代年龄都占4bit。
    所以 在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15。
    这也是 -XX:MaxTenuringThreshold 指令设置最大值为15的原因。


    参考文章 :《深入理解Java虚拟机》

    展开全文
  • Survivor区存在的意义 如果新生代只有Eden区,则对象经历一次垃圾回收后就会被送往老年代。这样使得老年代内存更容易占满,从而触发Full GC,最终造成影响程序的...此时,可分两种情况: Survivor垃圾回收,剩余未回收

    Survivor区存在的意义

    如果新生代只有Eden区,则对象经历一次垃圾回收后就会被送往老年代。这样使得老年代内存更容易占满,从而触发Full GC,最终造成影响程序的执行与响应速度,导致连接超时的不良后果。
    因此设计survivor区使得对象只有到达一定岁数才能进入到老年代。

    为什么需要两个survivor区

    假设只有一个Survivor区,当对新生代进行垃圾回收时,Eden区和Survivor区会同时进行垃圾回收,再存入该Survivor区。此时,可分两种情况:

    1. Survivor垃圾回收,剩余未回收对象仍在原地址存储。再将Eden区进行垃圾回收,存活对象存入Survivor区可以存放的空间。由于Survivor区回收后会造成很多内存碎片,因此向其中存储时需要考虑内存是否已经被占用,并且如果想存放大对象时会有可能没有足够空间从而触发Full GC。
    2. Survivor垃圾回收,剩余未回收对象依次从头开始存到本Survivor内存中连续的空间。但是这个操作会需要一直考虑是否想放入的内存没有被占用,如果被占用还需要挪动该对象,算法比较复杂。

    只有一个Survivor---------------------------------------------------------图1.只有一个Survivor---------------------------------------------------------------------

    因此,如果有两个Survivor区,则可以将Eden区和Survivor区未回收的对象连续的放在一块空白内存区域。从而解决上述问题。
    在新生代中, 每次垃圾收集时都发现有大批对象死去, 只有少量存活, 那就选用复制算法, 只需要付出少量存活对象的复制成本就可以完成收集。 而老年代中因为对象存活率高、 没有额外空间对它进行分配担保, 就必须使用 “ 标记 - 清理 ” 或 “ 标记 - 整理 ” 算法来进行回收。

    展开全文
  • 很多人都会搞混不清这两种内存,下面给大家详细介绍一下,让大家对运行内存和机身内存的区别做一个大致的了解!名词简介运行内存是指手机运行程序时的内存,也叫RAM(简称运存)。机身内存相当于电脑的硬盘,厂家常...
  • 为什么Linux下必须有 根 swap分区一流行的、以讹传讹的说法是,安装Linux系统时,交换分区swap的大小应该是内存倍。也就是说,如果内存是2G,那么就应该出4G的硬盘空间作为交换空间。其实这是严重的浪费。...
  • JVM包括下列几个运行时数据区域:GC什么是GC为什么需要学习GC从三个角度切入来学习GC哪些内存要回收?堆的回收区域判断对象是否存活算法1.引用计数算法2、可达性分析算法垃圾收集算法三大垃圾收集算法最基础的收集...
  • jvm内存模型按照线程可分为线程独占线程共享两种. 线程独占 本地方法栈,虚拟机方法栈,程序计数器. 线程共享 堆,方法区 首先本地方法栈: 本地方法栈放的就是本地方法的栈针,这种方法一般是由c语言底层写的.通过...
  • == 分两种情况: 若比较的是基本数据类型,则比较的是值,只要值相等,就可以 若比较的是引用数据类型,则比较的是内存地址。 equals 也是分两种情况: 没有重写equals()。通过equals比较俩对象的时候,==...
  • 我将上下节来带大家了解JVM的内存管理机制只要是Java程序员估计都体验过程序中的内存泄漏OOM异常吧。为了剖析这类问题,理解JVM内存及其管理的内容方式是极其重要的。JVM的一大优点是内存的自动管理。什么是...
  • 为什么必须管理内存 内存管理是计算机编程最为基本的领域之一。在很多脚本语言中,您不必担心内存是如何管理的,这并不能使得内存管理的重要性有一点点降低。对实际编程来说,理解您的内存管理器的能力与局限性至...
  • 文章一: 问题描述 为什么进行分表? 库? 一般多少数据量开始分表?... 什么是数据库垂直拆分水平拆分 ...为什么要进行库 ...业务发展,当单个数据库中的表越来越多,数据量...此时有两种解决方案: 横向扩
  • 两种广泛使用的内存分配技术:自动分配动态分配。通常,他们分别对应内存区域:栈堆。 栈 栈始终以顺序方式分配内存。 它可以这样做,因为它要求你以相反的顺序释放内存(先进先出,FILO)。许多编程语言都...
  • java内存模型

    2017-05-19 18:21:11
    为什么需要 Java 内存模型 为了让程序员忽略掉各种硬件操作...在并发编程中,需要处理两个关键问题:线程之间如何通信(线程之间以何种机制来交换信息, 有两种方式:共享内存和消息传递)及线程之间如何同步。
  • 文章目录说明Redis 简介由来为什么Redis效率那么高Nosql数据库对比1.性能2.便利性3.存储空间4.可用性5.可靠性6.一致性各个Nosql数据库的优缺点1.Redis2.Memcache3.MongoDB安装Redis 3.01.安装所需依赖2.创建目录,...
  • 如何评价索引的好坏:数据库服务器有两种存储介质,硬盘和内存,为了数据安全,索引需要存放在硬盘上,这样在硬盘上进行查询时,就会产生硬盘的I/O操作,索引的查找次数也就是硬盘I/O的操作次数,所以索引需要减少...
  • 我们都知道,JavaScript数据类型分两大类,基本类型(或者称原始类型)引用类型。 基本类型的值是保存在栈内存中的简单数据段,它们是按值访问的。JS中有五基本类型:Undefined、Null、Boolean、NumberString...
  • 关于变量、作用域和内存问题,以前零零散散的看别人博文研究过,不过今天自己看到原书,有醍醐灌顶的感觉,特此记录。 一、变量 前面也提及过变量存在大分类,基本数据类型引用类型(关于具体是那些,我就不在...
  • JVM内存模型及常见问题JVM内存模型堆内存模型展示对象创建过程常见问题及解答如何理解各种GC为什么需要survivor区?只有Eden不行吗?为什么需要个S区?为什么Eden:s1:s2是8:1:1?堆内存中都是线程共享区域吗?...
  • 关于js内存泄露

    2020-08-02 17:11:15
    线代浏览器都是自动回收垃圾,原理分两种: 引用计数垃圾回收 这一点 objective-c 很像。当一个对象能访问另一个对象时,则意味着被访问的对象引用计数+1,当移除这种访问时,引用计数-1。当引用计数 0 时,...
  • 我们都知道,JavaScript数据类型分两大类,基本类型(或者称原始类型)引用类型。 基本类型的值是保存在栈内存中的简单数据段,它们是按值访问的。JS中有五基本类型:Undefined、Null、Boolean、Number...
  • 零. 为什么需要 Java 内存模型 ...为了让程序员忽略掉各种硬件操作系统的内存访问差异, 也...在并发编程中,需要处理两个关键问题:线程之间如何通信(线程之间以何种机制来交换信息, 有两种方式:共享内存和
  • 我们已经知道类体中的方法分为实例方法类方法两种,用static修饰的是类方法。二者有什么区别呢?当一个类创建了一个对象后,这个对象就可以调用该类的方法。  当类的字节码文件被加载到内存时,类的实例...
  • cyc的Java内存模型

    2020-03-07 23:05:59
    文章目录1、对象的访问定位的两种方式?...5、为什么要采用代收集算法?6、对象如何进入老年代7、谈谈对Java引用的理解(4种)?8、什么是浮动垃圾?9、常用的垃圾收集器有哪些?10、CMS(低停顿)11、G1(...
  • 虚拟内存 (合转)

    2021-03-13 21:11:38
    为什么不直接使用物理内存 虚拟内存是计算机系统内存管理的一技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部...
  • 单片机SFR是什么意思?

    千次阅读 多人点赞 2020-04-13 13:35:27
    SFR全称:specialfunctionregister(翻译:特殊功能寄存器) 要想明白什么是sfr,需要先了解什么是寄存器 寄存器是RAMROM的统称 就像猫科动物是老虎与狮子的统称...那这两种寄存器的区别是什么呢? 1 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 542
精华内容 216
关键字:

内存分为什么和什么两种