内存概述
谈内存,就要谈其寻址。而要理解内存就要先理解其的逻辑地址、线性地址、物理地址。
物理地址:最容易理解的,它就是实实在在物理内存上的地址,你PC上有1G内存,那最大地址就是0x40000000.
线性地址:这是APP用的地址,也就是我们程序员写代码用的地址,它是一个虚拟地址,最终会被转化到物理地址。
逻辑地址:这是最麻烦的一个地址了,CPU发出的地址。
早期的CPU内部有20根地址线,能寻址2^20个地址(这些就是全部的线性地址),也就是128KB,但是其中的寄存器只有16位,只能寻址 2^16个地址,也就是64KB,怎么寻址512M呢?
把寻址分为两部分,基地址(CS寄存器)和 偏移地址(IP),CS:IP就是我们的逻辑地址。
用CS+IP就是2^32=4G. 但又一个问题又出来了 4G >512M 很多,怎么解决?
第一,浪费CPU的4跟地址线,规定系统支持最大内存就是512M;
第二,扩大内存到4G;
第三,引进虚拟内存(文件系统)充当内存(最优)。
CPU不能直接访问文件系统速度太慢?
那只能到CPU的要访问虚拟内存的时候去文件系统把数据加载到内存来了再给CPU。
出现地址冲突了?
在物理内存和逻辑内存中抽出一个地址叫线性地址,然后再能过快表(TLB)映射到真实的内存上去。
例:514M的内存,20根地址线。 现在CPU 发出 cs 0x1000 ip 0x0008 的逻辑地址,转化为 2^3*2^16+8=512M+8b线性地址。再通过查找快表TLB,得到 ox10000008对应着 0x00000008的物理地址
随着CPU的发展,现在CPU的地址线远大于内存的需求,现在机子中 逻辑地址=线性地址。
内存页式管理与快表
页式管理:把内存以4KB的大小切成页。
1.为什么要切成页:便于回收及换片,以免造成过多的空间碎片。
2.为什么要4K:你的系统是大存储可以改大点,如果此值过大会你又存储小文件,会严重浪费空间。比如你设置成100K,如果你这块只存了1K的数据就会造成99K的浪费。
上图TLB快表的实现方式:32位的线性地址会被分成3个部分,10位页目录,10位页表,12位页偏移。
问:如何让每个进程都感觉自己独享着内存?
答:让每个线程都拥有一张快表
问:上图快表占用的空间是1KB+1M=1205KB. 如果开10条进程就会占用 10M多内存
答:只有页目录会优先加入,页表用到再加入(一个程序不可能全用到)
问:TBL每次寻址都要使用,用不会影响速度
答:CPU中往往设计了TLB和Cache,用硬件实现的TBL加快了查找速度,Cache则缓存数据和指令,加快了数据的写入。
问:如果减少进程切换引起的快表切换的时间
答:映射内核地址空间及两进程共用的地方可以不用切换(线程切换可以全部不用切换)
引进ASID,区别每个进程访问快表的区域
虚拟内存![](https://i-blog.csdnimg.cn/blog_migrate/f41634b0f69b1e4dab84fc19c5a51247.png)
虚拟内存很好的利用了程序的局部性,因为程序在运行的时候不可能会使用满申请的内存数(系统把没使用的内存直接分配在文件中,这样子就感觉每个程序都有很大的内存,但感觉是感觉,如果很多线程同时一起使用,就会靠成崩溃了,就像大家一起去银行取钱)
Linux系统会不时的进行页面交换操作,以保持尽可能多的空闲物理内存,即使并没有什么事情需要内存,Linux也会交换出暂时不用的内存页面。这可以避免等待交换所需的时间。如果此时没有足够的物理内存来容纳这些页面,它们又会被马上交换出去,如此以来,虚拟内存中可能没有足够空间来存储这些交换页面,最终会导致linux出现假死机、服务异常等问题,也称为“内存抖动”。
进程空间
Mem表示内存总量为 1020092k 其中 824180k被用
swap中cached表示被用的内存有172404k被用作虚拟内存和真实内存交换的缓冲区
VIRT — 进程使用的虚拟内存总量,VIRT=SWAP+RES
RES — 进程使用的.未被换出的物理内存大小.RES=CODE+DATA
这二个数说明,进程实际申请 2594M,但才用了522m
系统角度除了引导系统的BIN区,整个内存空间主要被分成两个部分:
内核内存是Linux自身使用的内存空间:主要提供给程序调度、内存分配、连接硬件资源等程序逻辑使用。
用户内存是提供给各个进程主要空间:Linux给 各个进程提供相同的虚拟内存空间;这使得进程之间相互独立,互不干扰。实现的方法是采用虚拟内存技术:给每一个进程一定虚拟内存空间,而只有当虚拟内存实 际被使用时,才分配物理内存。
从进程的角度来看,进程能直接访问的用户内存(虚拟内存空间)被划分为5个部分:
代码区:代码区中存放应用程序的机 器代码,运行过程中代码不能被修改,具有只读和固定大小的特点。
数据区:存放了应用程序中的全局数据,静态数据和一些常量字符串等,其大小也是固定的。
堆区:是运行时程序动态申请的空间,属于程序运行时直接申请、释放的内存资源。
栈区:用来存放函数的传入参数、临时变量,以及返回地址等数据。
栈区未使用区:是分配新内 存空间的预备区域。(堆区和栈区要扩大)
JAVA进程空间
上图是JDK 7时合进程进程空间对应图。每个进程都要符和进程空间的标准。而我们JAVA程序也不例外,谁叫他运行在操作系统上呢?
JVM进程模型的代码区和数据区指的是JVM自身的,而非Java程序的。
永久代本质上是Java程序的代码区和数据区。
新生代和老年代才是Java程序真正使用的堆空间,主要用于内存对象的存储;比如C++执行new操作时,会触发一次分配内存空间的系统调用,由操作系统的线程根据对象的大小分配好空间后返回;同时,程序释放对象时,比如C++执行delete操作时,也会触发一次系统调用,通知操作系统对象所占用的空间已经可以回收。 JVM对内存的使用和一般进程不同。JVM向操作系统申请一整段内存区域(具体大小可以在JVM参数调节)作为Java程序的堆(分为新生代和老年代); 当Java程序申请内存空间,比如执行new操作,JVM将在这段空间中按所需大小分配给Java程序,并且Java程序不负责通知JVM何时可以释放这 个对象的空间,垃圾对象内存空间的回收由JVM进行。
最后是未使用区,未使用区是分配新内存空间的预备区域。对于普通进程来说,这个区域被可用于堆和栈空间的申请及释放,每次堆内存分配都会使用这个区 域,因此大小变动频繁;对于JVM进程来说,调整堆大小及线程栈时会使用该区域,而堆大小一般较少调整,因此大小相对稳定。操作系统会动态调整这个区域的 大小,并且这个区域通常并没有被分配实际的物理内存,只是允许进程在这个区域申请堆或栈空间。
而JDK8时候,把永久代中字符串常量池放到了JAVA堆中,其它的放到末使用,这下JAVA堆=系统堆。而末使用区域是申请及释放的,是可动态调整的,不像永久带,启动时多大就多大。