后端技术面试38讲
李智慧
同程艺龙交通首席架构师,前Intel&阿里架构师,《大型网站技术架构》作者
立即订阅
3551 人已学习
课程目录
已更新 12 讲 / 共 38 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 掌握软件开发技术的第一性原理
免费
软件的基础原理 (8讲)
01丨程序运行原理:程序是如何运行又是如何崩溃的?
02丨数据结构原理:Hash表的时间复杂度为什么是O(1)?
03丨Java虚拟机原理:JVM为什么被称为机器(machine)?
04丨网络编程原理:一个字符的互联网之旅
05丨文件系统原理:如何用1分钟遍历一个100TB的文件?
06丨数据库原理:为什么PrepareStatement性能更好更安全?
07丨编程语言原理:面向对象编程是编程的终极形态吗?
答疑丨Java Web程序的运行时环境到底是怎样的?
软件的设计原理 (2讲)
08丨软件设计的方法论:软件为什么要建模?
09丨软件设计实践:如何使用UML完成一个设计文档?
不定期加餐 (1讲)
加餐 | 软件设计文档示例模板
后端技术面试38讲
登录|注册

01丨程序运行原理:程序是如何运行又是如何崩溃的?

李智慧 2019-11-18
软件的核心载体是程序代码,软件开发的主要工作产出也是代码,但是代码被存储在磁盘上本身没有任何价值,软件要想实现价值,代码就必须运行起来。那么代码是如何运行的?在运行中可能会出现什么问题呢?

程序是如何运行起来的

软件被开发出来,是文本格式的代码,这些代码通常不能直接运行,需要使用编译器编译成操作系统或者虚拟机可以运行的代码,即可执行代码,它们都被存储在文件系统中。不管是文本格式的代码还是可执行的代码,都被称为程序,程序是静态的,安静地呆在磁盘上,什么也干不了。要想让程序处理数据,完成计算任务,必须把程序从外部设备加载到内存中,并在操作系统的管理调度下交给 CPU 去执行,去运行起来,才能真正发挥软件的作用,程序运行起来以后,被称作进程
进程除了包含可执行的程序代码,还包括进程在运行期使用的内存堆空间、栈空间、供操作系统管理用的数据结构。如下图所示:
操作系统把可执行代码加载到内存中,生成相应的数据结构和内存空间后,就从可执行代码的起始位置读取指令交给 CPU 顺序执行。指令执行过程中,可能会遇到一条跳转指令,即 CPU 要执行的下一条指令不是内存中可执行代码顺序的下一条指令。编程中使用的循环 for…,while…和 if…else…最后都被编译成跳转指令。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《后端技术面试38讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(30)

  • 斐波那契
    在java里 锁是通过cas把当前线程id刷新到对象的头信息里 在获取锁时会去头信息里拿这个信息 如果没有 则会cas刷新进去 刷新成功就获取到锁 刷新失败就表明有别的线程也在尝试刷新这个信息 在操作系统层面 有pv操作保证原子性 而pv操作也是利用cpu中原语指令 在获取锁时保证不会被别的指令打断(或被重排序)

    作者回复: 👍

    2019-11-18
    2
    34
  • Allen_
    有些地方可能不是很到位,欢迎补充
    小结:
    1. 我们平时开发出来的程序是文本格式代码,但只是在硬盘中还只是一个程序,只有加载到内存里面通过cpu执行成为进程才是发挥了程序作用。
    2.进程里面有堆,栈,可执行代码和进程数据结构。
    3.cpu分时共享技术进行并发操作,进程切换效率不高,所以有了线程切换
    4.因为线程安全问题引入锁,不过也引入了更多造成阻塞的可能
    5.线程阻塞可能是I/O,锁,网络请求,数据库链接获取
    6利用分布式系统架构来减缓高并发的性能不佳

    作者回复: 清晰👍

    2019-11-18
    2
    12
  • Luciano李鑫
    “不管你是否有意识,你开发的 web 程序都是被多线程执行的,web 开发天然就是多线程开发。”这个会不会绝对了一些,比如go或者c++开发的没有额外创建线程的web程序呢?

    作者回复: 这样开发出来的也就不会是一个可以满足高并发的web应用😁 也就不会有我们标题提出的问题~

    这个专栏的大部分文章都试图在一篇较小的篇幅内讲完整一个较大的问题场景,这样就会有一些绝对化和理想化,后面的文章也会有类似的情况😢

    这也真是我在开篇词想要表达的意图,我们在构建自己的知识体系时,不追求细节的完整和绝对的严谨,而是在大的方向上建起支撑性的知识支柱。细节的缺失和瑕疵在未来的学习和实践中完善。

    当然也欢迎大家在评论中指出各种问题,有兴趣的同学参与讨论,我们在评论区就完成部分知识的深入和完善。

    感谢🌹

    2019-11-18
    5
    12
  • 雷咏
    我们用文本格式书写的程序有三种执行方式:
    1.解释执行。例子是脚本语言书写的程序或类似于BASIC语言书写的程序。著名的PYTHON也属于这种情况。
    2.编译执行。通常C/C++程序属于这种情况。文本格式书写的程序称之为源程序,需要编译器编译成机器语言代码,称之为可执行程序一或目标程序。
    3.虚拟机执行。将文本格式的程序先编译成一种中间代码,然后由驻留在计算机中的虚拟机解释执行。例子是通常的JAVA程序。

    作者回复: 👍

    2019-11-19
    1
    6
  • 探索无止境
    希望老师在第二节课可以谈谈上一节课留下的思考题,您是怎么理解分析的

    作者回复: 也许可以将来以彩蛋的方式专门写一篇文章讲讲~

    2019-11-19
    4
  • 小美
    李老师好:我想请教下,一个JVM 是一个进程。JVM 上跑 tomcat,tomcat 可以部署多个应用?那每个跑在tomcat 上的应用是一个线程吗?那一个应用crash了,其他应用也会crash.这块感觉有点问题。不知道老师方便解释下吗?

    作者回复: JVM是一个进程;tomcat是一个框架;tomcat会启动线程处理应用请求,执行应用的代码;应用自己不能跑起来的,只能被tomcat的线程执行,应用在tomcat线程中执行时,也可以自己启动线程,并发、异步执行自己的某些计算。

    tomcat会对不同应用做一些隔离,但是如果某个应用导致JVM crash,所有应用都crash。

    可参考后续专栏《JVM虚拟机原理》

    2019-11-18
    1
    4
  • y欧尼酱
    # 01 程序运行原理

    ## 程序是如何运行起来的?

    - 1 、程序员被开发出来,文本格式代码,不能直接运行,需要编译器或者虚拟机先编译成机器码(也叫可执行代码),存储在文件系统中(即磁盘)。
    - 2 、操作系统将编译好的代码加载到内存中。
    - 3、CPU去执行,运行,程序运行起来被称作进程。

    运行起来的程序,在运行期使用内存堆空间,栈空间,等数据结构。

    ![如图](https://static001.geekbang.org/resource/image/89/98/89c6e3bbc44cdc042e7a8bcddb3b4398.png)



    程序运行时需要创建数组等数据结构,这些由操作系统在进程中分配。

    进程的堆,栈中分配相应的空间,比如堆,堆的地址又存在栈中。



    栈:一种数据结构,遵循先进后出,后进先出的顺序。

    比如函数

    ```
    void f(){
    int x = g(1); x++; //g函数返回,当前堆栈顶部为f函数栈帧,在当前栈帧继续执行f函数的代码。
    }
    int g(int x){
    return x + 1;
    }
    ```





    每次函数调用,操作系统都会在栈中创建一个栈帧,方法程序(函数)通常被压栈进栈,函数中的变量,内存都在当前栈帧中,如下图。



    ![如图](https://static001.geekbang.org/resource/image/f0/f7/f08d6fca893da5cac926a23f1f1aa7f7.png)





    ## 计算机如何同时处理数以百计的任务



    通常情况下我们个人的PC 可能会是一核心或两核,现在基本都4核或者更高核cpu了

    我们个人通常是用电脑会同时打开很多程序软件,听歌的,看电影的,下载视频等等几十个任务。

    而服务器可以同时处理成千上百个任务以及并发请求而且一点儿都不卡顿(这里也存在服务器配置高的因素)

    ### 为什么电脑可以同时处理这么多的任务呢?

    `这里主要依靠的是操作系统的 CPU 分时共享技术`

    很多个进程在执行,操作系统会将 CPU 的执行时间分成很多份,进程按照某种策略轮流在 CPU 上运行

    > 每个进程都只被执行了很短一个时间,但是在外部看来却好像是所有的进程都在同时执行,每个进程似乎都独占一个 CPU 执行



    为什么每个程序不单独使用进程呢?

    原因是因为CPU每个进程在CPU上轮训使用,消耗资源以及时间很大!

    通常我们在打开自己电脑时候开了很多软件(或者进程)后明显的感觉自己电脑变慢了,卡顿了。

    一方面是因为自己电脑内存小,另一方面是因为进程数太多了,cpu切换需要花费时间。



    那么如何解决进程多cpu切换代价大的情况呢?

    计算机的先辈们引入了线程的概念,比如我们开发中容器tomcat 每次用户请求时候,tomcat分配给用户一个线程,在进程里可以启动很多的线程,线程可以理解为轻量级的进程,在进程内创建,拥有自己的线程栈,在 CPU 上进行线程切换的代价也更小。



    线程在运行时,和进程一样,也有三种主要状态

    运行:当一个进程在 CPU 上运行时,则称该进程处于运行状态。处于运行状态的进程的数目小于等于 CPU 的数目。

    就绪:当一个进程获得了除 CPU 以外的一切所需资源,只要得到 CPU 即可运行,则称此进程处于就绪状态,就绪状态有时候也被称为等待运行状态。

    阻塞:也称为等待或睡眠状态,当一个进程正在等待某一事件发生(例如等待 I/O 完成,等待锁……)而暂时停止运行,这时即使把 CPU 分配给进程也无法运行,故称该进程处于阻塞状态。



    ## 系统为什么会变慢,为什么会崩溃



    主要大致可以分为一下几个步骤原因

    - 1、线程锁,引起线程阻塞,多线程情况下可能会存在线程排队等锁,县城无法并行执行导致速度变慢。
    - 2、此外I/O阻塞同理,比如数据库连接,并行数量超过数据库连接数量,线程就会出现阻塞
    - 3、等待其他县城释放连接才能访问数据库,并发越大等待连接越多,响应时间越长,系统越慢。
    - 4、被阻塞的线程越多,占据的系统资源也越多,这些被阻塞的线程既不能继续执行,也不能释放当前已经占据的资源,在系统中一边等待一边消耗资源,如果阻塞的线程数超过了某个系统资源的极限,就会导致系统宕机,应用崩溃。





    如何解决

    - 1、采用请求限流
    - 2 、集群架构
    - 3、分布式架构

    2019-11-19
    2
    3
  • 我爱布丁
    老师,看完文章,联想到两个关于协程的问题:

    1. 使用协程在出现IO等待时,程序会自己调度去执行其他的(CPU)任务。理论上这样可以避免额外的IO等待导致的线程间切换。我的问题是从系统的角度上看,使用协程可以抢占到更多的CPU时间片吗?

    2. 感觉系统崩溃(除人为Bug外) 主要是系统资源不足导致的。那么即使用轻量级的协程也不会变得更好。因为当协程数量过多,导致event loop过大,变慢,系统还是要崩溃的对吗?

    作者回复: 1 多个协程通过自我调度复用同一个线程,所以某个协程IO等待的时候,会导致整个线程阻塞,并不能避免线程切换。更好的做法是使用异步IO,不要IO等待。

    能否抢到时间片是操作系统调度的,协程自己控制不了,但是协程利用自己更轻量级,配合异步IO等方法,可以提高运行效率,整体性能得到优化。

    2 是的。但是用好协程,减少额外的线程调度切换,可以提高整体的系统吞吐能力。

    2019-11-21
    2
  • 夜里饿煮面
    操作系统中PV原语是最小的原子,就是不知道硬件上是怎么实现的
    2019-11-18
    2
  • Paul Shan
    请问老师,内存中的堆区域和数据结构中的最大堆最小堆,两者都叫做堆,两者有没有关系?
    2019-12-04
    1
  • Paul Shan
    思考题
    锁要全局共享但只有一处可得,应该有特殊的CPU指令来处理吧。
    2019-12-03
    1
  • Paul Shan
    磁盘上的代码就是一堆静态的二进制数据,运行之间和一张图片没有本质区别。当被执行的时候,代码就被加载到内存变成进程,原先的代码被转化成CPU可以操作的指令。一般的指令是从前往后的,也有些指令是根据条件跳转的,循环条件语句都是跳转指令。程序除了消耗CPU外,还要消耗内存。内存又分为栈和堆,栈处理函数运行的局部变量,新的函数调用,会有新的栈帧。堆处理局部变量以外的变量,例如Java new的对象,这些对象又会被栈中的变量引用,堆栈一起协同工作,堆容量大,负责存储。栈记录活动变量,引用堆中存储,后进先出。进程就是把指令集合,堆栈打包并建立边界的一个容器。CPU是全局宝贵资源,可以被多个进程共享,如果有多核的话,也可以同时运行多个进程,Python中的并发就是这么实现的。但是,每个进程都有自己的堆栈和其他活动资源,切换代价高昂。所以,多数编程语言都有线程的概念,线程是CPU调度的单位,同一进程下的线程之间共享堆和其他资源,切换成本大降,但是多个线程共享堆也会引发一系列并发的问题,通常需要锁来解决。另外一种平凡调用IO的情况,线程也不算太合适,因为IO和CPU本身并不竞争,通常IO比CPU慢10倍以上,所以很多语言引入了协程,在单线程的情况下实现并发,让IO和CPU资源得到充分利用,协程切换的成本比线程更低,也没有并行的问题,但也要注意协程只是在单线程下调度IO和CPU资源,对于并行计算并无帮助。
    单个进程或者线程消耗太多资源会让系统变慢甚至崩溃,更多的情况是,等待中的进程线程数目太多,无法释放资源而让系统变慢甚至崩溃。这就好比交通系统,一辆坦克车横冲直撞造成问题毕竟少,多数问题还是车多到一定数目,前面的车过不去,后面的车进不来,虽然,每辆车都遵守交通法则,但是还是造成了拥堵。这个时候,正常的交通规则已经不管用了,需要其他治理拥堵的特殊措施来处理。
    2019-12-03
    1
  • Citizen Z
    粗略思考:
    1. 锁在内存中只有 1 份数据(JVM 用堆区中对象的 header 来实现,所以是每个线程都可见的),有开/关两个状态
    2. 不同线程访问锁时一定是有先后顺序的(JMM 的 happens-before 原则有规定,这个可能涉及较多手段来保证,问题难点主要出现在多核 CPU 和 CPU cache 的情况下)
    3. 如果线程遇到打开状态的锁,就“获取并关闭“锁(这里的原子性由 CPU 指令保证,就是 cas 机制)

    更复杂的锁优化策略都在这个基础上实现
    2019-11-20
    1
  • Tobe24
    您好,老师,这节课太适合我这种新手了,这里有三个不太明白的地方,希望老师能够解惑。
    问题1:CPU 分时共享技术同时执行进程的数量,取决于什么?
    问题2:为什么线程切换的代价更小?
    问题3:进程切换是不是必须要等到线程切换完毕后进行?如果不是,优先级是由什么决定的?
    一点小建议:
    有一些表达程度的词,如果能用数据举例简单说明一下,对于我们理解会更有帮助。比如2问题中,代价更小,小到什么程度,是进程切换速度的几倍?
    思考题:
    作为小白,我的思路是这样,锁是在线程的临界区,线程是在进程的线程栈,而 一个 cpu 同时只能运行一个进程,所以本质上都是轮流执行的……于是,只要保证在获取锁的时候,锁不在正在获取或已经被获取的状态即可,进而推断线程中会有一片内存区域用来存这些状态信息。
    😂不知道这个思路对不对。
    最后谢谢老师。

    作者回复: 1 取决于CPU的核数
    2 线程比进程占据的资源少,切换代价小,而且进程内线程可能执行相同的代码,切换线程后也许还可以复用CPU cache数据
    3 线程切换和进程切换只依赖CPU是否空闲,和线程进程彼此没关系

    PS,上面回复内容其实我也没有确切的资料证实,我是根据第一性原理推导出来的,我觉得仅根据专栏文章内容就可以推导出来,我希望你也可以自己分析推导,这样获得的知识会更加稳固

    2019-11-20
    1
  • vega
    为何我总觉得这里的说法都比较针对Java,如果放在计算机操作系统这门课来说是不是会显得不严谨?
    2019-12-01
  • 樂文💤
    操作系统层面应该是通过CPU 同步原语实现的 有原子性
    2019-11-29
  • 静水流深
    老师,您好,闭包函数的执行栈帧如何描述?

    作者回复: 堆栈是属于线程的堆栈,所以要看函数被哪个线程执行,具体要看编程语言和执行环境。

    2019-11-29
  • peter
    一直以为tomcat是一个独立的进程。根据本文所述,tomcat只是一个线程,是虚拟机进程中的一个线程。是这样吗?

    作者回复: tomcat是一个程序,被JVM加载到JVM进程中。

    Tomcat的代码在JVM被执行后,可以启动很多线程。

    2019-11-24
  • 小烽
    Java 中,锁的状态存在对象头中,包括线程id 等,因为是在堆中,是共享变量,所以所有的线程都能获取到,并通过cas判断是否能拿到锁。换成其他需要应该也是如此,在共享变量中存储锁相关的信息即可。但要具体实现还是有些困难

    李老师,您好。我是非计算机专业的,在从事java 开发,听了课程,感觉收获很大,但是很多概念有些模糊,像操作系统基本不了解,在性能调优时也发现了必须懂底层原理,才能走的更远和更有竞争力,请问题老师,这底层原理,像操作系统需要怎么去学呢。
    2019-11-21
  • Heidi
    你好,想提个问题。文章中大部分知识点都掌握,但是遇到问题的时候没有从这些角度出发,只是跟着一些关联去分析问题,对遇到的问题反应比较慢。这种情况是不是知识没有成体系?那么怎样建立比较完整的知识体系呢?

    作者回复: 思考问题从问题的根源出发思考,如果在解决问题的时候现场比较乱,无法做到。事后做复盘,复盘的时候重新从根源思考。

    2019-11-20
    1
收起评论
30
返回
顶部