现代操作系统基本都采用虚拟内存管理的方式来管理每个进程对内存的访问,对进程采用虚拟内存的方式进行管理可以避免不同的进程访问相同的物理内存而造成程序的紊乱。每个进程都维护独立的一份虚拟地址,例如在32位操作系统中进程A有4GB的虚拟地址可以使用,进程B也有4GB的虚拟地址可以使用,**因此不同进程彼此之间访问是相互隔离的。**
**注意,进程中的地址称为“逻辑地址”,也称“虚拟地址”,需要转化为实际的内存设备的“物理地址”才能够使用。**
:-: ![](https://img.kancloud.cn/26/bf/26bfab47c01eb77fbe9b343d4100cef2_193x424.png)
对于内存的管理一般有分段和分页两种方式。
## 内存分段
分段式管理将进程中的内存分为不同的段,例如可以分为代码段、数据段、栈段、堆段等,这也是最早的内存管理方式。这种方式通过**段表**结构来将虚拟内存映射成物理内存。段表中的段表项由(段号,物理段号)组成。虚拟内存的地址由(段号,偏移地址)组成。
:-: ![](https://img.kancloud.cn/1e/90/1e90c4c51b98becd72ff1a4619cbfb83_945x665.png)
【参考:小林图解系统】
采用内存分段的方式有如下的两个问题:
1. 容易产生内存碎片:内存分段的方式是完整的将应用程序加载进内存中,而且内存的分配还是一大段连续的,这样就很容易造成一小块内存一小块内存存在的问题,这就是内存碎片。
2. 内存交换效率低:Linux系统中有一块swap的磁盘区域就是用于当内存不够用的时候将内存换到磁盘中,由于分段式连续且一般都是比较大的一块空间,所以交换的效率就比较低。
## 内存分页
这是现在操作系统用得比较多的内存管理方式,将内存分为以4KB大小为单位的内存页,这样在内存不够用的时候进行交换时也比较快;同时由于每页都是比较小的连续4KB的内存空间,也不容易造成内存碎片。
内存分页使用页表结构来保存虚拟内存到物理内存的映射关系,通过MMU(内存管理单元)来处理这种映射关系,内存分页的映射过程如下:
:-: ![](https://img.kancloud.cn/de/1e/de1e3cab140342dfb0e4dcd9543351e6_946x684.png)
虚拟地址组成:(页号、页内偏移量)
页表组成:(虚拟页号、物理页号)
**每个进程都有单独的一份页表结构来保存进程内虚拟内存到物理内存的映射关系。**
现在操作系统多采用多级页表的结构,其结构如下:
:-: ![](https://img.kancloud.cn/a6/02/a60298a93632924d79e4211a392747d1_918x630.png)
由于“程序局部性原理”,可以使得每次不用生成太多的二级页表,只需要生成加载进内存的那部分内存空间的二级页表就行了,**注意一级页表的范围是要覆盖所有物理内存的大小的。**
为了避免地址映射的时候多次读取内存,因此在CPU内存封装了TLB(快表)缓存结构,用来缓存最近访问过的页表项。
## Linux的内存管理方式
由于CPU现代CPU采用分段+分页的方式来管理内存的,Linux系统也只能跟随的CPU来实现。但是Linux将整个物理内存都当成一个段,这样的话就屏蔽了分段,只用到分页来管理内存。
Linux一般将一个进程的虚拟内存分为**用户空间与内核空间。**例如32位操作系统将虚拟内存分为1GB的内核空间和3GB的用户空间
:-: ![](https://img.kancloud.cn/54/c2/54c2e030b7c3fcdb8c7957d19bae798b_918x372.png)
对于每个进程其3GB的用户空间是自定义的,但是1GB的内核空间是映射到同一块物理内存的,即内核空间是共享的。
更具体的Linux系统中进程的内存布局如下:
:-: ![](https://img.kancloud.cn/2a/2d/2a2d0e8e69ef89465fe0a6057967b2bf_512x689.png)
以Java进程为例,JVM的堆结构其实是在这里的“堆”分配的一块空间,“堆外内存”也是在这里分配的,只是堆含义的主体对象不同罢了。
【参考】
《小林图解系统》
- 第一章 Java基础
- ThreadLocal
- Java异常体系
- Java集合框架
- List接口及其实现类
- Queue接口及其实现类
- Set接口及其实现类
- Map接口及其实现类
- JDK1.8新特性
- Lambda表达式
- 常用函数式接口
- stream流
- 面试
- 第二章 Java虚拟机
- 第一节、运行时数据区
- 第二节、垃圾回收
- 第三节、类加载机制
- 第四节、类文件与字节码指令
- 第五节、语法糖
- 第六节、运行期优化
- 面试常见问题
- 第三章 并发编程
- 第一节、Java中的线程
- 第二节、Java中的锁
- 第三节、线程池
- 第四节、并发工具类
- AQS
- 第四章 网络编程
- WebSocket协议
- Netty
- Netty入门
- Netty-自定义协议
- 面试题
- IO
- 网络IO模型
- 第五章 操作系统
- IO
- 文件系统的相关概念
- Java几种文件读写方式性能对比
- Socket
- 内存管理
- 进程、线程、协程
- IO模型的演化过程
- 第六章 计算机网络
- 第七章 消息队列
- RabbitMQ
- 第八章 开发框架
- Spring
- Spring事务
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 数据库
- Mysql
- Mysql中的索引
- Mysql中的锁
- 面试常见问题
- Mysql中的日志
- InnoDB存储引擎
- 事务
- Redis
- redis的数据类型
- redis数据结构
- Redis主从复制
- 哨兵模式
- 面试题
- Spring Boot整合Lettuce+Redisson实现布隆过滤器
- 集群
- Redis网络IO模型
- 第十章 设计模式
- 设计模式-七大原则
- 设计模式-单例模式
- 设计模式-备忘录模式
- 设计模式-原型模式
- 设计模式-责任链模式
- 设计模式-过滤模式
- 设计模式-观察者模式
- 设计模式-工厂方法模式
- 设计模式-抽象工厂模式
- 设计模式-代理模式
- 第十一章 后端开发常用工具、库
- Docker
- Docker安装Mysql
- 第十二章 中间件
- ZooKeeper