查看原文
其他

程序员应如何理解内存:下篇

码农的荒岛求生 码农的荒岛求生 2020-12-18

本节是操作系统系列教程的第三篇文章,属于操作系统第一章即基础篇,在真正开始操作系统相关章节前在这一部分回顾一些重要的主题,以下是目录,由于本文篇幅较多因此按上篇中篇、下篇三次发布,目录中黑体为本篇内容,本文为该主题最后一篇。




什么是内存

C/C++内存模型

堆区与栈区的本质

Java、Python等内存模型

Java内存模型
Jave中的堆区与栈区是如何实现的
Python内存模型

指针与引用

进程的内存模型

幻想大师-操作系统

总结




指针与引用

在各种编程语言中我们应该经常听到两个词,那就是引用或者指针。这两个词都是和内存相关的,指针和引用的作用都是“如何找到存放在内存上的数据”。

C/C++中有“指针”这样一个概念,而其它语言比如Java、Python有的只是“引用”这样一个概念。这两者有什么区别呢?我们打个比方你就能理解了。

“引用”就好比一个人的外号一样,就好有个程序员叫令狐冲,但是令狐冲同学在A公司的英文名可能是“Tom”,在B公司中可能又叫“Jerry”,那么在A公司中你只需要喊一声“Tom”就能找到令狐冲同学。

而“指针”强调的是位置,比如令狐冲在A公司的工位是“10排第二个”,在B公司中的工位是“8排第六个”,下班后回的位置在“中关村”。

这个例子当中的令狐冲同学就好比程序语言中的对象,令狐冲的各种外号就好比对象的引用,令狐冲当前所在的位置就好比对象的指针。

虽然通过“引用”和“指针”都能找到令狐冲同学,但是寻找的方式是不一样的。

只有C/C++这样的编译型语言才会有“指针”这样一个概念,指的是当前的对象放在了内存中的哪个位置上了。在比如Java、Python等语言中只有“引用”这样一个概念。在C/C++语言中,我们可以通过指针直接找到一个对象,因为你知道这个对象就在内存中指针所指向的位置,但在Java、Python等语言中,当你利用引用找到对象时基本上是冲着解释器喊一句“Hey,解释器,帮我找到令狐冲这个对象”,解释器通过记录查找到这个对象,注意解释器是知道对象在内存中的真正位置的,由于直接管理内存是一项非常繁琐容易出错的事情(C/C++程序员一定对此有深刻体会),因此解释器就接手了对内存直接管理,Java、Python等程序员是没有必要知道对象在内存中的真正位置的,没有指针也可以开心的写程序而且程序更加健壮,何乐不为呢,因此这些语言中是没有指针这样一个概念。

Sun的一篇论文中提到了为什么Java里没有指针。

Most studies agree that pointers are one of the primary features that enable programmers to inject bugs into their code. Given that structures are gone, and arrays and strings are objects, the need for pointers to these constructs goes away. Thus, Java has no pointer data types. ...

You no longer have dangling pointers and trashing of memory because of incorrect pointers, because there are no pointers in Java.

大意是Java设计者认为指针太有技巧性以至于很容易出错,因此Java中没有指针。其实不只是Java,流行的语言当中除了C/C++之外几乎都没有指针。

在这一节中,你只需要理解以下两点就可以啦。

  • 指针:直接在内存中找到变量所在位置。所以指针是实实在在的内存地址。

  • 引用:告诉解释器你想使用的变量,然后解释器再去内存中找到变量的位置。所以引用只是解释器的一个承诺,只要这个变量存在,解释器就承诺能找到这个变量,程序员就可以使用这个变量,至于这个变量在内存中的什么地方是不需要程序员关心的。


进程的内存模型

我们已经在前面几个小节中研究了C/C++以及Java、Python程序的内存模型,接下来让我们回到操作系统。

我们已经知道了,不管什么语言,最后操作系统看到的都是C程序,C程序在内存运行起来就是进程。而在前面的小节当中我们已经知道进程在内存中的样子,但那里的描述其实是不完整的,也是不准确的。接下里我们就来看一下,操作系统中的进程在内存中到底是什么样子的,如下图所示(注意这幅图描述的是32位操作系统下进程在内存中是什么样子的),我们需要注意以下几点:

  1. 在上图中多出了一块内存,注意,这块内存就是操作系统在运行的时候所占用的内存。

  1. 每个进程独占一个连续的4G大小的内存,从内存地址0开始,一直到0xffffffff,其中最上方的1G留给了操作系统使用,下方的3G是留给进程自己使用的,其中程序员可以操作的区域就是图中的堆区和栈区。

  1. 你会发现代码段下方也有一点空隙没有使用,其实这是有特殊目的的,具体用途会在后面的章节中讲解。

现在你已经知道了进程在内存中的样子,你一定会有疑问吧,

为什么每个进程认为自己占用的是4G内存呢?如果我的PC上只有2G内存,进程还是认为自己拥有4G内存吗

操作系统上不是可以同时运行很多进程吗,内存是有限的,假如只有2G,每个进程都认为自己拥有4G内存,这不会有问题吗

我们首先来回答第一个问题:是的,每个进程都认为计算机上的真实内存就是4G,而且是进程自己独占的,即使真正的物理内存只有256MB。

第二个问题:很显然,不管你现在看这篇文章用的电脑,iPad,安卓手机还是iPhone,这些计算设备中的进程都是这么认为的,你能看到这篇文章说明进程认为自己拥有4G内存是不会出现问题的。

在这里需要再次强调的是:

每个进程都认为真实的内存就是4G,其中1G被操作系统使用,剩余部分被进程使用,也就是可以被程序员使用。注意这是不受真实物理内存限制的,也就是说,即使真实的物理只有256MB,进程同样认为在内存是4G,其中1G是操作系统的,剩余3G是进程自己独占的,程序员依然可以按照内存大小是3G来写程序。所以在大小256MB的真实物理内存上,程序员依然可以一次性申请超过256MB的内存而且可以申请成功,后续内存的使用也不受影响。

就像我第一次知道这种魔法时一样,你肯定也会惊呼这怎么可能呢?我们怎么能在256MB大小的内存上申请超过256MB的内存呢?但事实就是如此,你可以在物理内存大小为256MB的内存上面申请超过256MB的内存,而且无论物理内存大小,每个进程都认为自己拥有4G内存,而且是独占内存。

这真的是太神奇了,这就是本课程的主角-操作系统带来的神奇魔法。


幻象大师——操作系统

这种魔法确实是真实的,这个魔法就来自我们的幻象大师-操作系统,其实进程看到的内存是操作系统制造的幻觉。操作系统让每个进程都认为内存就只有两部分,一部分是操作系统的一部分是自己的,这种魔法就称之为虚拟内存。后面的章节中会重点介绍操作系统是如何实现这种魔法的。

在虚拟内存上程序员分配内存不受真实物理内存大小的限制

但这仅仅是进程自己这么认为,这是操作系统给进程制造的幻觉,所以被称之为虚拟内存。虚拟内存是操作系统中极为重要的概念,和进程一样,对虚拟内存的深刻理解也是编程高手的标志之一。我会在后续文章中来为大家透彻讲解操作系统是如何做到的。


总结

哈哈,这真是比较长的一节,希望你能坚持学到这里,没办法,内存真的是非常重要的,要想学好操作系统,对内存的透彻理解是必不可少的

在这一节中我们认识到了其实内存仅仅就是一堆装0或1的小盒子组成,是没有什么神秘的。我们也了解了C/C++、Java、Python程序的内存模型,也知道了操作系统中的进程在内存中是什么样子的。同时操作系统中被被称为虚拟内存的神奇魔法也着实让人惊叹,想学习这么魔法请继续关注操作系统系列文章。

完整阅读《程序员应如何理解内存》一文请参见下方的操作系统系列目录。



操作系统系列

基础篇
1,什么是程序

2,程序?进程?傻傻分不清

3,程序员应如何理解内存:上篇
4,程序员应如何理解内存:中篇


PS:我才知道原来从去年三月份以后申请的公众号已经没有留言功能了,还在想为什么一直没有留言,这个问题目前还没有一个很好的解决方法,如果你有问题欢迎暂时直接在我的公众号里发送消息,发送消息时注明针对哪篇文章,后面如果大家疑问较多我会建一个技术群供大家学习交流,对此如果你有其它好的想法也欢迎直接在公众号里发送消息,目的就是希望这个公众号能更好的帮到大家!如果你喜欢这类文章也请多多转发,不胜感激,今天周五,预祝大家周末愉快 :)


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存