ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Java程序员应该掌握的底层知识】 07 内存管理

2021-07-31 23:32:38  阅读:113  来源: 互联网

标签:load Java 07 映射 程序员 地址 内存 进程 虚拟内存


文章目录

DOS时代

在DOS时代 ,内存很小,同一时间只能有一个进程在运行(也有一些特殊算法可以支持多进程,通过栈来手动实现多进程之间的切换,但这个比较特殊很少见,不做讨论)

Winwods9X时代

在这里插入图片描述

内存变大,可以让多个进程装入内存。但依然存在问题:
1、内存不够用,多个进程同时装入,但内存时有限的。
2、互相打扰,因为多个进程同时存在于内存中,那意味着某一个进程可以随意去访问另外一个进程的物理内存地址,这也是非常危险的一件事情,恶意攻击将变得非常容易。

为了解决这两个问题,诞生了现在的内存管理系统:虚拟地址 分页装入 软硬件结合寻址

现代内存管理系统

针对于内存不够的问题,采用了内存分页的解决方法。

内存分页

在这里插入图片描述
为了解决之前内存不够用的问题以及涉及到内存对齐的问题(每个进程的所需内存大小不同,可能会因为某个进程的内存比较大,即使内存有剩余,但也放不下改进程的所需大小),现代内存管理系统采用的解决方法是: 首先在内存中,不再是一整块作为单位进行存储管理,而是分为若干个不同的固定大小的内存小块进行存储管理,这其实也就是所谓的内存页,内存页以4K作为一个标准的页大小进行划分。现今的操作系统都实现了以4K为最小单位页的存储粒度,当然有些操作系统还支持了4K的倍数大小(16k,64k)的单位页的存储。 因此最初即使没有任何进程的时候,内存中其实也已经划分成了一个个页框,等待着进程的载入
而与此同时,程序(硬盘)也分为一块块4K大小的块。 并且程序读入内存时,不会一次性将块全部放入内存中,而是实现了一种按需放入:程序运行时需要哪些块,此时才放入内存中

也就是说,一个进程在启动的时候,操作系统只是分配并记录了这个进程对应的所有的磁盘页块映射表,并且发现例如启动程序所依赖的数据代码处于3号磁盘块,此时就把进程的3号磁盘块load到内存中,然后发现load进来的3号磁盘块的代码还依赖了4号磁盘块,就同时把4号也load到内存中。。。。 这样的一个过程 ,而并非全部load进来

但即使是这样,如果load块的时候,内存还是已经满了,已经放不下一个新的内存页了怎么办?操作系统会将内存中最长时间没有使用的内存块从内存取出放入swap分区中(当然swap分区是硬盘实现的,速度肯定非常慢),然后再将刚刚这个新的要存入的程序块进行load到对应刚刚被移走的那个内存位置上。 而这个操作的策略,实际上就是LRU算法。

关于LRU算法的具体实现思路,可以参考我的另外一篇笔记:
leetcode146-LRU算法.note

而针对于进程之间相互打扰的问题,采用了虚拟内存的解决方法。

虚拟内存

在这里插入图片描述
为了保证进程之间互不影响,也为了保证物理内存的安全性,操作系统虚拟出来了一块内存空间,每个进程都工作在自身独有的虚拟内存空间中,自此,每个进程都不再能直接去访问直接内存,从而A进程永远不可能再可以访问到B进程的内存空间,也就解决了内存互相访问的安全性的问题
那虚拟内存空间多大呢?寻址空间 , 64位系统来说,是 2 ^ 64 byte,比物理内存空间大很多 。
站在虚拟的角度,每个进程都认为自己是独享整个系统 + CPU

虚拟内存的结构

虚拟内存的结构是固定的,而它的结构主要是由若干段组成的:
1、数据段,又分为只读的数据段和可读写的代码段
2、代码段,只读
3、运行时堆
4、共享库,用来存放例如一些调用c的类库
5、用户栈
6、内核。物理真实的内核只有一个,但站在每个进程的虚拟角度来看,自身都拥有一个完整的内核。

基于每一段中,又分为了若干的内存页,这个内存页的概念和真实内存的概念相同,因此也是按需load程序块进虚拟内存页。

每个进程都可以在自己的虚拟内存中看到这样的一个结构,对进程来说操作的都是虚拟内存中的内容,其实最终对虚拟空间的操作还是要作用在真实内存地址中,
因此就需要将对应的虚拟地址与真实的内存地址进行一个映射

因此这里我们可以做一个小结:
在这里插入图片描述

内存映射

在这里插入图片描述

逻辑地址

在虚拟内存中,一个数据肯定是被装在了某个内存数据段中的某个内存页中,逻辑地址就是说这个数据相对于所在数据段的偏移位置

线性地址

找到数据的所在内存页偏移地址,先需要找到数据所在的内存段的地址,因为内存段也是一整块虚拟内存中的某个偏移位置上,因此所谓线性地址说的就是:
数据所在内存段的偏移位置(基地址)+数据相对于所在数据段的偏移位置(也就是逻辑地址,也叫偏移量)。
假设数据的逻辑地址是20,所在数据段地址是1000,那么它的线性地址就是1000+20=1020

映射到真实内存地址

当然,上面说的地址都是虚拟空间上的地址,那如何映射到真实内存地址上呢?实际上这个过程是比较复杂的。
整个过程其实是需要数据从逻辑地址->线性地址->物理地址的一个映射过程。这涉及到需要把虚拟空间的页放入到真实内存中去,如果真实内存不够用,还需要把真实内存置换出去等等的问题。

但总结来讲,这个过程的完成,是操作系统+硬件(CPU中的MMU:Memory Management Unit)来完成的。

在这里插入图片描述
每个进程中的虚拟地址都需要操作系统+MMU来完成映射到物理空间内存中,如果物理内存装不下,会放置到硬盘级别的swap分区中。

缺页中断

因为一个进程的内存不会全量分配,因此会存在例如load磁盘3号块到了内存中,结果发现3号要用到磁盘4号块的内容,但此时内存页中还没有,就会产生缺页异常(中断),由内核处理并加载4号块到内存页中。
在这里插入图片描述

swap内存置换

有时候我们发现电脑运行的时候,忽然变卡,然后响了起来。 这个时候可能就是因为内存满了,对内存中的数据进行置换出去,然后将需要的内容从磁盘读入内存中,所以会比较慢,但却是必需的。

内存映射与ZGC

在这里插入图片描述
ZGC使用的垃圾回收算法叫做:Colored Pointer
GC信息记录在对象指针上,而不是记录在对象头部, immediate memory use(内存立即重用),区别于之前的记录在对象头部,每当对象的状态进行了修改之后,都需要去更新markword的状态,从而才能判断对象是否存活,是否可以从内存中回收。而ZGC将GC信息直接记录在对象指针上,通过发现对象指针的改变,可以不再需要去看markword,直接就可以决定是否在内存中进行释放这个对象的空间。

ZGC现在只支持64位的系统,因此对象指针是64位的,Colored Pointer使用了42位作为对象指针的寻址空间,也就是4T,除此之外,还有4位存放对象的此时的状态,18位是空余的。因此我们不难想到,后续可以慢慢扩增对象指针的存储位数,因此, 在JDK13,更是提高到了44位进行存储,也就是16T。 但实际上,也最高只能使用44位进行存储寻址地址,这是为什么呢?

这其实牵扯到一个问题:

CPU如何区分一个立即数 和 一条指令

一条数据数字和一条指令,本质上都是01进行组合的数字,那CPU如何区分是指令还是数据?
实际上总线内部分为:数据总线 地址总线 控制总线,通过不同的总线穿过来区分不同类型。
而我们的寻址自然是用到的是地址总线,而地址总线目前设计上只设计了48位。

ZGC地址映射的体现?

颜色指针本质上包含了地址映射的概念,ZGC运用了虚拟内存,通过在不同的内存区域存放不同状态的对象,最终映射到同一块物理地址上面去。

标签:load,Java,07,映射,程序员,地址,内存,进程,虚拟内存
来源: https://blog.csdn.net/xyz9353/article/details/119282919

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有