美文网首页
程序运行原理:程序是如何运行又是如何崩溃的?

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

作者: 随手点灯 | 来源:发表于2019-11-26 11:09 被阅读0次

    注: 本文是极客时间后端技术基础详解的读书笔记.

    01 程序是如何运行起来的

    程序: 分为可执行的程序和静态的文本文件.

    程序需要从外部设备加载到内存,然后在操作系统的管理调度下,交给CPU去执行和运行,才能真正发挥软件的作用.

    程序运行起来就是进程.进程在内存当中包含可执行代码,堆,栈内存以及数据结构.

    01.png

    程序运行时如果需要创建数组等数据结构,操作系统就会在进程的堆空间申请一块相应的内存空间,并把这块内存的首地址信息记录在进程的栈中。堆是一块无序的内存空间,任何时候进程需要申请内存,都会从堆空间中分配,分配到的内存地址则记录在栈中。

    思考: 在java中,数组等对象都是在堆内存中创建,然后地址的引用是在栈内存当中.如果栈内存中没有该对象的引用,这个对象其实就是已经死亡.另外,每个线程都有独立的栈内存.如果多个栈引用堆中的同一个对象,就需要考虑线程安全问题.

    每次函数调用,操作系统都会在栈中创建一个栈帧(stack frame)。正在执行的函数参数、局部变量、申请的内存地址等都在当前栈帧中,也就是堆栈的顶部栈帧中。如下图所示:

    02.png

    思考: 每当创建一个线程都会在内存当中分配一个独立的栈,当这个线程执行一个方法的时候,就会往这个栈里面压入栈帧.栈的特点就是先进的后出,所以我们看到错误信息的时候,打印出来的栈信息就是如此.每次真正执行的方法就是栈最顶部的方法,并且每个栈帧之间是相互隔离的,所以定义相同的变量不会出现混乱.

    02. 一台计算机如何同时处理数以百计的任务

    那么为什么一台计算机服务器可以同时处理数以百计,以千计的计算任务呢?这里主要依靠的是操作系统的 CPU 分时共享技术。如果同时有很多个进程在执行,操作系统会将 CPU 的执行时间分成很多份,进程按照某种策略轮流在 CPU 上运行

    思考: CPU的运行速度太快了,以至于我们根本无法感知到CPU是在轮流处理我们的进程.一个核通常同一时间只能处理一个进程.

    所以虽然从外部看起来,多个进程在同时运行,但是在实际物理上,进程并不总是在 CPU 上运行的,一方面进程共享 CPU,所以需要等待 CPU 运行,另一方面,进程在执行 I/O 操作的时候,也不需要 CPU 运行。进程在生命周期中,主要有三种状态,运行、就绪、阻塞。

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

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

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

    不同进程轮流在 CPU 上执行,每次都要进行进程间 CPU 切换,代价是非常大的,实际上,每个用户请求对应的不是一个进程,而是一个线程。线程可以理解为轻量级的进程,在进程内创建,拥有自己的线程栈,在 CPU 上进行线程切换的代价也更小。线程在运行时,和进程一样,也有三种主要状态,从逻辑上看,进程的主要概念都可以套用到线程上。我们在进行服务器应用开发的时候,通常都是多线程开发,理解线程对我们设计、开发软件更有价值。

    思考: 进程的三种状态,就绪,运行和阻塞.但是进程太过重量级了,线程是轻量级的进程.比如JVM就是一个进程,但是里面可以创建很多的线程来处理用户的各种操作,比如360软件中,你可以同时清理垃圾和卸载文件.

    03. 系统为什么会变慢和崩溃

    Tomcat容器是如何为每个用户去分配一个线程的?

    03.png

    Tomcat 启动多个线程,为每个用户请求分配一个线程,调用和请求 URL 路径相对应的 Servlet(或者 Controller)代码,完成用户请求处理。而 Tomcat 则在 JVM 虚拟机进程中,JVM 虚拟机则被操作系统当做一个独立进程管理。真正完成最终计算的,是 CPU、内存等服务器硬件,操作系统将这些硬件进行分时(CPU)、分片(内存)管理,虚拟化成一个独享资源让 JVM 进程在其上运行。

    思考: 我认为应该是每一个请求有一个线程,一个页面可能出现多个请求.软件天然就是分层的,大多数的架构图都是分层的.在这里,Tomcat为用户分配线程,JVM负责找操作系统要资源,操作系统分配硬件资源CPU执行代码并执行代码.

    以上就是一个 Java web 应用运行时的主要架构,有时也被称作架构过程视图。需要注意的是,这里有个很多 web 开发者容易忽略的事情,那就是不管你是否有意识,你开发的 web 程序都是被多线程执行的,web 开发天然就是多线程开发

    思考: 很多开发者意识不到自己的在进行多线程的开发,因为对于用户请求的线程管理,是Tomcat容器在进行管理多线程.所以几乎没有意识到.

    多个线程去访问堆中的共享变量会出现线程安全问题.

    多个线程访问共享资源的这段代码被称为临界区,解决线程安全问题的主要方法是使用锁,将临界区的代码加锁,只有获得锁的线程才能执行临界区代码,如下:

    lock.lock();  //线程获得锁
    i++;  //临界区代码,i位于堆中
    lock.unlock();  //线程释放锁
    

    如果当前线程执行到第一行,获得锁的代码的时候,锁已经被其他线程获取并没有释放,那么这个线程就会进入阻塞状态,等待前面释放锁的线程将自己唤醒重新获得锁。

    锁会引起线程阻塞,如果有很多线程同时在运行,那么就会出现线程排队等待锁的情况,线程无法并行执行,系统响应速度就会变慢。

    会出现线程阻塞的情况:

    1. 加锁
    2. I/O操作
    3. 获取数据库资源限制(并发数量超过了连接数,就需要等待其他线程释放连接后才能访问,并发线程越多,等待连接时间越多)

    这些从web请求者角度来看,响应时间变长,系统变慢.

    思考: 记得我今年开发了一个数据同步的程序,到最后数据总是无法实时的同步过来,通过今天的学习我明白了,线程被阻塞的,阻塞的第一个问题就是因为没有考虑最大并发数和数据库连接池的最大连接数.第二个原因可能是由于数据库阻塞了,但是没有对线程池中的线程进行超时关闭.

    解决系统响应变慢的主要手段:

    1. 分布式架构
    2. 集群负载均衡
    3. 请求入口限流,减少系统并发请求数量
    4. 应用内进行业务降级,减少线程资源消耗

    事实上,现代 CPU 和操作系统的设计远比这篇文章讲的要复杂得多,但是基础原理大致就是如此。为了让程序能很好地被执行,软件开发的时候要考虑很多情况,为了让软件能更好地发挥效能,需要在部署上进行规划和架构。软件是如何运行的,应该是软件工程师和架构师的常识,在设计开发软件的时候,应该时刻以常识去审视自己的工作,保证软件开发在正确的方向上前进。

    思考: 出现问题要考虑到很多中情况,比如我的同步程序无法被同步,可能是被阻塞了,有哪些原因会引发阻塞.这就是一个好的程序员思考的问题,要了解软件的基础性原理.

    相关文章

      网友评论

          本文标题:程序运行原理:程序是如何运行又是如何崩溃的?

          本文链接:https://www.haomeiwen.com/subject/wprewctx.html