gitbook/后端技术面试 38 讲/docs/174252.md
2022-09-03 22:05:03 +08:00

71 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 答疑丨Java Web程序的运行时环境到底是怎样的
今天是第一模块的最后一讲。在这一讲中,我们主要讲了软件的基础原理,今天,我将会针对这一模块中大家提出的普遍问题进行总结和答疑,让我们整理一下,再接着学习下一个模块的内容。
## 问题一
> @小美
> 既然一个JVM是一个进程JVM上跑TomcatTomcat上可以部署多个应用。这样的话每个跑在Tomcat上的应用是一个线程吗该怎么理解“如果一个应用crash了其他应用也会crash”
理解程序运行时的执行环境直观感受程序是如何运行的对我们开发和维护软件很有意义。我们以小美同学提的这个场景为例看下Java Web程序的运行时环境是什么样的来重新梳理下进程、线程、应用、Web容器、Java虚拟机和操作系统之间的关系。
我们用Java开发Web应用开发完成编译打包以后得到的是一个war包这个war包放入Tomcat的应用程序路径下启动Tomcat就可以通过HTTP请求访问这个Web应用了。
在这个场景下进程是哪个线程有哪些Web程序的war包是如何启动的HTTP请求如何被处理Tomcat在这里扮演的是什么角色JVM又扮演什么角色
首先我们是通过执行Tomcat的Shell脚本启动Tomcat的而在Shell脚本里其实启动的是Java虚拟机大概是这样一个Shell命令
```
java org.apache.catalina.startup.Bootstrap "$@" start
```
所以我们在Linux操作系统执行Tomcat的Shell启动脚本Tomcat启动以后其实在操作系统里看到的是一个**JVM虚拟机进程**。这个虚拟机进程启动以后加载class进来执行首先加载的就这个`org.apache.catalina.startup.Bootstrap`类,这个类里面有一个`main()`函数是整个Tomcat的入口函数JVM虚拟机会启动一个**主线程**从这个入口函数开始执行。
主线程从Bootstrap的main()函数开始执行初始化Tomcat的运行环境这时候就需要**创建一些线程**比如负责监听80端口的线程处理客户端连接请求的线程以及执行用户请求的线程。创建这些线程的代码是Tomcat代码的一部分。
初始化运行环境之后Tomcat就会扫描Web程序路径扫描到开发的war包后再加载war包里的类到JVM。因为Web应用是被Tomcat加载运行的所以我们也称**Tomcat为Web容器**。
如果有外部请求发送到Tomcat也就是外部程序通过80端口和Tomcat进行HTTP通信的时候Tomcat会根据war包中的web.xml配置决定这个请求URL应该由哪个Servlet处理然后Tomcat就会**分配一个线程去处理这个请求**,实际上,就是**这个线程执行相应的Servlet代码**。
我们回到小美同学的问题Tomcat启动的时候启动的是JVM进程这个进程首先是执行JVM的代码而JVM会加载Tomcat的class执行并分配一个主线程这个主线程会从main函数开始执行。在主线程执行过程中Tomcat的代码还会启动其他一些线程包括处理HTTP请求的线程。
而我们开发的应用是一些class被Tomcat加载到这个JVM里执行所以即使这里有多个应用被加载也只是加载了一些class我们的应用被加载进来以后并没有增加JVM进程中的线程数也就是web应用本身和线程是没有关系的。
而Tomcat会根据HTTP请求URL执行应用中的代码这个时候可以理解成每个请求分配一个线程每个线程执行的都是我们开发的Web代码。如果Web代码中包含了创建新线程的代码Tomcat的线程在执行代码时就会**创建出新的线程**,这些线程也会被操作系统调度执行。
如果Tomcat的线程在执行代码时代码抛出未处理的异常那么当前线程就会结束执行这时控制台看到的异常信息其实就是线程堆栈信息线程会把异常信息以及当前堆栈的方法都打印出来。事实上这个异常最后还是会被Tomcat捕获然后Tomcat会给客户端返回一个500错误。单个线程的异常不影响其他线程执行也就是不影响其他请求的处理。
但是如果线程在执行代码的时候抛出的是JVM错误比如`OutOfMemoryError`这个时候看起来是应用crash事实上是整个进程都无法继续执行了也就是进程crash了进程内所有应用都不会被继续执行了。
从JVM的角度看Tomcat和我们的Web应用是一样的都是一些Java代码但是Tomcat却可以加载执行Web代码而我们的代码又不依赖Tomcat这也是一个很有意思的话题。Tomcat是如何设计的我将会在下个模块讲述。
## 问题二
> @黄海峰
> 有点难以想象“Hash表的时间复杂度为什么是O(1)”这个问题居然有阿里大厂的面试官觉得难。
这不是一个疑问,但其实是一个有意思的话题,我们花一点时间讨论下,也许会对你的职业规划有所启发。
文中这个故事大概发生在2009年整整十年前那个时候互联网还不像今天这样炙手可热提供的薪水也不像今天这样有竞争力也没有BAT这样的专有名词指代所谓的互联网巨头。那个时候计算机专业优秀的毕业生向往的是微软、Oracle、IBM这样的外资IT巨头退而求其次国内好的IT公司是联想、用友这些企业。
事实上那个时候在技术研发能力上互联网公司的技术能力也是落后传统企业的阿里巴巴最核心的数据存储依赖的是IBM、Oracle、EMC的解决方案即所谓的IOE。
所以在十年前的人才市场上国内互联网公司的形象一般是技术落后、薪水一般、加班严重、没有名气。可以说在人才市场的竞争中相比国内外的IT巨头是落于下风的。
我个人感觉互联网公司的崛起大概是在七八年前移动互联网开始出现互联网的渗透率得到加速BAT逐渐开始成为家喻户晓的名字名气大涨。其次经过前面时间的积累互联网企业主导的各种分布式技术、大数据技术、移动互联网技术、云计算技术的风头超过传统IT巨头阿里巴巴开始去IOE打造自己的云计算平台成为先进技术的代表者最主要的还是互联网企业盈利能力大幅增加能够提供市场上更有竞争力的薪水和股票。
于是互联网企业在人才市场上开始变得灼手可热BAT这些企业开始被人称为“大厂”。我们今天感觉这些互联网巨头高高在上人们纷纷向往。事实上这个现象出现的时间非常短。今天这些企业有足够的名气和资源将自己营造得高高在上可以在众多优秀的候选人中间挑来选去仅仅在十年前还不是这样的。
但是事情真正的吊诡之处还不在这里,当今这些互联网大厂的核心技术和业务模式在十几年前就已经奠定了,经过几年的摸索,大概在七八年前开始稳定成熟。也就是说,互联网企业的技术实力和商业能力是在这些企业还默默无闻的时候就发展起来的,而在这些企业成为明星之后,并没有什么突破性的进展。想想这些所谓的互联网大厂,最近几年,并没有什么值得称道的商业模式创新和技术创新。
也就是说十多年前可能是一些并不优秀的技术人员加入一个并不出名的公司然后这些人开创出了一个杰出的事业。用马云的话说就是“二流的人做一流的事”。然后公司开始挑选一流的人但结果似乎只是在维持这个事业并没有开创出更加杰出的事业。今天的BAT似乎成为当年的IBM历史好像进入了某种循环。
如果这就是事情的真相,我想你或许可以从其中得到某些启发,重新考虑下未来的职业规划。也许你会发现,你可能不需要追逐当前所谓的热门技术,而应该好好想想需要为自己的未来准备些什么。
最后,在第一模块中,我在每一篇文章的下面都留了几道思考题,各位同学在评论区都有很好的答案。但只有[第五篇文章](https://time.geekbang.org/column/article/169533),我似乎没有看到比较准确的答案,我在这里回答一下。
RAID5中校验位之所以螺旋式地落在所有硬盘上主要原因是因为如果将校验位记录在同一块硬盘上那么对于其他多块数据盘任何一块硬盘修改数据都需要修改这个校验盘上的校验数据也就是说对于有8块硬盘的RAID5阵列校验盘的数据写入压力是其他数据盘的7倍。而硬盘的频繁写入会导致硬盘寿命缩短校验盘会频繁损坏存储的整体可用性和维护性都会变差。
所以,作为软件架构师,当你在进行软件设计的时候,你不光需要考虑软件本身,你还需要了解软件的各种约束,硬盘的特性约束是一种,当然还有其他一些约束,我会在专栏的后面模块中继续讲解如何在各种约束下,设计出符合期望的软件系统。