0%

关于面试的一些思考🤔

记录一些值得思考的问题

回答的思路

诞生背景

优势
缺点

硬件 (CPU,GPU) + 驱动 + 操作系统(linux,Android) + 操作系统API调用 + 应用层

进程

程序: 程序就是一个可执行文件,是存储再硬盘上的一列列指令,就像 win 系统里的 .exe 文件,这是一个可执行的安装文件,解压缩我们可以看到好多好多的代码,只是这些代码现在都是静态的,都还没有运行起来。

进程: 当一个可执行文件(apk)被加载进内存,程序就变成了进程。进程就是已经被加载进内存的一系列相互关联的课执行指令。
进程把第一条指令加载进内存,然后按顺序一条条的执行指令。进程本质就是程序计数器+运行时数据程序

线程: CPU 执行的任务就是一个个线程,线程中有栈帧,栈帧就是一个个将要运行的方法和临时数据

面试时这样回答:进程是操作系统资源分配、保护和调度的基本单位,线程是CPU调度的基本单位

可执行文件

编写 => 编译 => 可执行文件 => 加载 => 回收

地址

  • 虚拟内存是操作系统内核为了对进程地址空间进行管理(process address space management)而精心设计的一个逻辑意义上的内存空间概念。我们程序中的指针其实都是这个虚拟内存空间中的地址。
  • 物理地址:物理地址就是内存位真实的物理存储位置
  • 虚拟地址: 这是说内存寻址的
  • 虚拟内存: 这来源于 WIN 系统,说的是用硬盘来扩展内存的大小,把一部分硬盘当内存

虚拟内存

每个进程都拥有独立的虚拟地址空间,通过虚拟地址进行内存访问主要具备以下几点优势:

1.进程可使用连续的地址空间来访问不连续的物理内存,内存管理方面得到了简化。
2.实现进程与物理内存的隔离,对各个进程的内存数据起到了保护的作用。
3.程序可使用远大于可用物理内存的地址空间,虚拟地址在读写前不占用实际的物理内存,并为内存与磁盘的交换提供了便利。

虚拟内存使用:分段、分页 技术进一步优化内存使用,具体由操作系统和CPU硬件中的MMU单元来管理

在计算机系统中,映射的工作是由硬件和软件共同来完成的。承担这个任务的硬件部分叫做存储管理单元MMU,软件部分就是操作系统的内存管理模块了

虚拟内存

虚拟地址到物理地址的转换

虚拟存储实现需要依靠硬件的支持
MMU (Memory Management unit)的部件进行页映射

CPU -> Virtual Address -> MMU -> Physical Address -> Physical Memory

分段

通过分段技术,实现了进程间内存隔离,进程之间不能访问其他进程的内存了,因为每个进程虚拟地址都是独一份,单独维护的,单独和物理内存映射的,其他进程拿到也没用,没有虚拟内存映射关系你是找不到真实物理地址的

分段

分页

linux把虚拟内存分为若干个大小相等的存储分区,分区称之为页。

为了换入换出方便,物理内存也是大小分为按块分配,物理内存块是存储虚容页的容器,所以物理容器称之为页框。

页中的页指的是内存管理单元把内存按页这个基本的单位分配,一页是4K大小,虚拟内存中的页叫页,物理内存中的页叫页框,记录页于页框之间映射关系的叫页表

分页

页的分配原则是按需分配,进程A告诉操作系统我需要60M内存,那么操作系统就先给了进程A60M虚拟内存,但是没给物理内存。在进程运行时计算真的需要1M内存,此时才分配1M物理内存给进程A,实现进程A虚拟内存于这1M物理内存的映射,等不够用了再分配物理内存,但是总量不能超过进程A启动时申请的60M虚拟内存这个阀值

页表

每个进程都有自己独立的虚拟地址空间,这些地址空间需要通过页表映射到不同的物理地址

偏移量 - 数据位于该页中的位置,一页是4K的大小,所以这个偏移量就是数据在这个页中内存位置的首地址。

可以理解为前面的是页码,后面偏移量数据代表着页中的行数

页表记录页于页框的映射关系,页和页框地址的偏移量,页表核心的就是记录页码和页框码了,看下图就是这个意思

页表

数据共享

大家想想要是能在2个进程中,要是都是指向相同的物理内存上,是不是就能实现跨进程内存共享啦,内存共享是进程间通信的一种方式

数据共享

swap

Linux 提出 SWAP 的概念,Linux 中可以使用 SWAP 分区,在分配物理内存,但可用内存不足时,将暂时不用的内存数据先放到磁盘上,让有需要的进程先使用,等进程再需要使用这些数据时,再将这些数据加载到内存中,通过这种”交换”技术,Linux 可以让进程使用更多的内存。

物理内存管理要处理的事情就是页面的换出。每个进程都有自己的虚拟地址空间,虚拟地址空间都非常大,而不可能有这么多的物理内存。所以对于一些长时间不使用的页面,将其换出到磁盘,等到要使用的时候,将其换入到内存中,以此提高物理内存的使用率

mmap理解

mmap 实现的是内核缓冲区与用户进程的地址空间的映射。

也就是说用户进程通过操作自己的逻辑虚拟地址就可以实现操作内核空间缓冲区,这样就不用再因为内核空间和用户空间相互隔离而需要将数据在内核缓冲区和用户进程所在内存之间来回拷贝。

mmap是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系;实现这样的映射关系后,进程就可以采用指针的方式读写操作这一块内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必调用read,write等系统调用函数,相反,内核空间堆这段区域的修改也直接反应到用户空间,从而可以实现不同进程间的文件共享。

日志常规方案的缺陷

数据从程序写入到磁盘的过程中,其实牵涉到两次数据拷贝:一次是用户空间内存拷贝到内核空间的缓存,一次是回写时内核空间的缓存到硬盘的拷贝。当发生回写时也涉及到了内核空间和用户空间频繁切换。
dirty page 回写的时机对应用层来说又是不可控的,所以性能瓶颈就出现了。

流畅性不仅包括了系统没有卡顿,还要尽量保证没有 CPU 峰值。

进一步思考

因为要写入大量的 IO 导致程序卡顿,那是否可以先把日志缓存到内存中,当到一定大小时再加密写进文件,为了进一步减少需要加密和写入的数据,在加密之前可以先进行压缩。

方案描述:把日志写入到作为 log 中间 buffer 的内存中,达到一定条件后压缩加密写进文件。

但这个方案却存在一个致命的问题:丢日志。

mars 的日志模块 xlog 就是在兼顾这四点的前提下做到:高性能高压缩率、不丢失任何一行日志、避免系统卡顿和 CPU 波峰

mmap 的回写时机:

内存不足

进程 crash

调用 msync 或者 munmap

不设置 MAP_NOSYNC 情况下 30s-60s(仅限FreeBSD)

log -> mmap -> 明文 -> 压缩 -> 加密

就是怎么对单行日志进行压缩,也就是其他模块每写一行日志日志模块就必须进行压缩

性能问题:一开始日志的写入就是通过标准I/O直接写文件,当有一条日志要写入的时候,首先,打开文件,然后写入日志,最后关闭文件。但是写文件是 IO 操作,随着日志量的增加,更多的IO操作,一定会造成性能瓶颈。为什么这么说呢?因为数据从程序写入到磁盘的过程中,其实牵涉到两次数据拷贝:一次是用户空间内存拷贝到内核空间的缓存,一次是回写时内核空间的缓存到硬盘的拷贝。当发生回写时也涉及到了内核空间和用户空间频繁切换。
丢日志:为了解决性能问题,直接想到就是减少I/O操作,我们可以先把日志缓存到内存中,当达到一定数量或者在合适的时机将内存里的日志写入磁盘中。这样似乎可以减少I/O操作,但是在将内存里的日志写入磁盘的过程中,app被强杀了或者Crash了的话,这样会造成更严重的问题,日志丢失

减少系统调用。我们只需要一次 mmap() 系统调用,后续所有的调用像操作内存一样,而不会出现大量的 read/write 系统调用。

减少数据拷贝。普通的 read() 调用,数据需要经过两次拷贝;而 mmap 只需要从磁盘拷贝一次就可以了,并且由于做过内存映射,也不需要再拷贝回用户空间。

可靠性高。mmap 把数据写入页缓存后,跟缓存 I/O 的延迟写机制一样,可以依靠内核线程定期写回磁盘。

作者:喊我小飞哥
链接:https://juejin.cn/post/6844904118088105991
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Java

手写一个单例

这个基本上大多数公司都会考察的。
要写一个 基于懒汉式的 双重检测的单例。

  • 单例有三个比较关键的点:
  • 私有构造方法,避免外部 new 出对象;
  • 保证唯一性;
  • 提供一个全局访问点。

另外懒汉式双重检测的实现方式有三点需要注意的地方:

全局访问点必须是静态的,外界使用可以通过类直接调用;
在进入锁之后还需要校验;
保存单例例对象的私有变量一定要用 volatile 修饰,这个地方 可以多说一些,
比如 volatile 防止指令重排序,保证内存可见性(JVM 层面 和 CPU 层面 可以分别说)。
volatile 这个地方能说的东西还是很多的,基本上可以与面试官再聊二十分钟了。

学习的链接


线程和进程

从调度、并发性、拥有的资源和系统开销四个方面回答的。

JVM 四种引用类型

这个问题比较简单,强引用、弱引用、软引用、虚引用,说一下它们各自的特点和 GC 对
它们的不同处理方式,再说一下常见的应用场景 或者 jdk 的实现中对它们的使用,比如
ThreadLoca 的静态内部类 ThreadLocalMap,它的 Key 是弱引用的,也可以说一下 在
你的理解中为什么它是弱引用的,假如不是会怎么样。


类加载过程

加载、链接、初始化,链接又分为验证准备和解析,每一个阶段是做了什么要说清楚。
Object a = new Object(); 这⾏代码做了了哪些事情,需要从类加载开始说起,这个相
当于上面问题的延续,所以 一定要清楚 每一个环节 做了哪些事情的,否则这个问题不可
能说清楚。说完类加载的过程 再说一下 开辟内存空间、初始化内存空间以及把内存地址
赋值给变量 a,接下来可以进一步说一下 JVM 或者 CPU 层面对指令的优化,以及在某些
时刻我们需要避免它做这样的优化,比如在单例中我们的实例需要用 volatile 修饰 避免指
令重排序(可以说一下 在 new 一个对象的过程中如果指令重排序了会导致什么结果)。


多线程:怎么实现线程安全,各个实现方法有什么区别,volatile 关键字的使用,可重入锁的理解,Synchronized 是不是可重入锁

1
2
3
4
这里我就主要讲了 Synchronized 关键字,还有并发包下面的一些锁,以及各自的优缺点
和区别。volatile 关键字我主要从可见性、原子性和禁止 JVM 指令重排序三个方面讲的,
再讲了一下我在多线程的单例模式 double-check 中用到 volatile 关键字禁止 JVM 指令重
排优化。

JVM 的垃圾回收机制了解吗?

标记-清楚算法、复制算法、标记-压缩算法、分代收集算法的实现原理。

一、 技术背景

JVM 内存主要分为五个区,哪些是线程共享的,哪些是线程独享的,每个区存放什么。

二、 哪些内存需要回收?

1
2
3
4
5
6
7
8
2.1 引用计数算法
2.1.1 算法分析
2.1.2 优缺点
2.1.3 是不是很无趣,来段代码压压惊
2.2 可达性分析算法
2.3 Java 中的引用你了解多少
2.4 对象死亡(被回收)前的最后一次挣扎
2.5 方法区如何判断是否需要回收

三、常用的垃圾收集算法

1
2
3
4
5
6
7
3.1 标记-清除算法
3.2 复制算法
3.3 标记-整理算法
3.4 分代收集算法
3.4.1 年轻代(Young Generation)的回收算法
3.4.2 年老代(Old Generation)的回收算法
3.4.3 持久代(Permanent Generation)的回收算法

四、常见的垃圾收集器

五、GC 是什么时候触发的(面试最常见的问题之一)

1
2
3
5.1 Scavenge GC
5.2 Full GC
https://www.cnblogs.com/1024Community/p/honery.html
  1. 项目中用的是 CMS 垃圾回收器吗?为什么要分为四个阶段?
    1
    2
    初始标记,并发标记,重新标记,并发清理四阶段,系统要求快速响应低延迟,对于多核
    CPU 性能更佳等方面去回答即可,网上文章很多,这里不多赘述了。
    学习的链接-1
    学习的链接-2

网络

我看你简历里说熟悉计算机网络,来聊一聊计算机网络吧。了不了解

tcp/udp,简单说下两者的区别?
区别:

1
2
3
4
5
6
7
8
9
10
11
12
TCP 面向连接(如打电话要先拨号建立连接);UDP 是无连接的,即发送数据之前不
需要建立连接。
TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错、不丢失、不重
复,且按序到达,UDP 尽最大努力交付,即不保证可靠交付。
TCP 面向字节流,实际上是 TCP 把数据看成一连串无结构的字节流;UDP 是面向报文

UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很
有用,如 IP 电话,实时视频会议等)。
每一条 TCP 连接只能是点到点的,UDP 支持一对一、一对多、多对一和多对多的交
互通信。
TCP 首部开销 20 字节;UDP 的首部开销小,只有 8 个字节。
TCP 的逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道。

两次握手为什么不可以?为什么要四次挥手?

1
2
3
4
5
6
7
8
9
10
采用三次握手是为了防止失效的连接请求报文段突然又传送到主机 B,因而产生错误。失
效的连接请求报文段是指:主机 A 发出的连接请求没有收到主机 B 的确认,于是经过一段
时间后,主机 A 又重新向主机 B 发送连接请求,且建立成功,顺序完成数据传输。考虑这
样一种特殊情况,主机 A 第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟
达到主机 B,主机 B 以为是主机 A 又发起的新连接,于是主机 B 同意连接,并向主机 A 发
回确认,但是此时主机 A 根本不会理会,主机 B 就一直在等待主机 A 发送数据,导致主机
B 的资源浪费。
TCP 关闭链接四次握手原因在于 TCP 链接是全双工通道,需要双向关闭。client 向 server
发送关闭请求,表示 client 不再发送数据,server 响应。此时 server 端仍然可以向 client
发送数据,待 server 端发送数据结束后,就向 client 发送关闭请求,然后 client 确认。

TCP 怎么保证有序传输的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
应用数据被分割成 TCP 认为最适合发送的数据块。
超时重传:当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文
段。如果不能及时收到一个确认,将重发这个报文段。
TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用

校验和:TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测
数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段
和不确认收到此报文段。
TCP 的接收端会丢弃重复的数据。
流量控制:TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端
发送接收端缓冲区能接纳的我数据。当接收方来不及处理发送方的数据,能提示发送
方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协
议。
拥塞控制:当网络拥塞时,减少数据的发送。

time_wait 状态,这个状态出现在什么地方,有什么用?

1
2
3
4
5
6
7
8
9
1. 为实现 TCP 全双工连接的可靠释放
当服务器先关闭连接,如果不在一定时间内维护一个这样的 TIME_WAIT 状态,那么当被动关
闭的一方的 FIN 到达时,服务器的 TCP 传输层会用 RST 包响应对方,这样被对方认为是
有错误发生,事实上这只是正常的关闭连接工程,并没有异常。
2. 为使过期的数据包在网络因过期而消失
在这条连接上,客户端发送了数据给服务器,但是在服务器没有收到数据的时候服务器就
断开了连接。
现在数据到了,服务器无法识别这是新连接还是上一条连接要传输的数据,一个处理不当
就会导致诡异的情况发生。

HTTP 与 HTTPS 有啥区别?HTTPS 是怎么做到安全的?

HTTPS 安全的原因:

1
2
3
4
5
6
7
8
HTTPS 把 HTTP 消息进行加密之后再传送,这样就算坏人拦截到了,得到消息之后也看不
懂,这样就做到了安全,具体来说,HTTPS 是通过对称加密和非对称加密和 hash 算法共
同作用,来在性能和安全性上达到一个平衡,加密是会影响性能的,尤其是非对称加密,
因为它的算法比较复杂,那么加密了就安全了吗?不是的,HTTPS 除了对消息进行了加密
以外还会对通信的对象进行身份验证。
HTTPS 并不是一种新的协议,而是使用了一种叫做 TLS(Transport layer secure)的安
全层,这个安全层提供了数据加密的支持,让 HTTP 消息运行在这个安全层上,就达到了
安全,而运行在这个安全层上的 HTTP 就叫做 HTTPS。

作者:前行的乌龟
链接:https://juejin.cn/post/6860153911366385678
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。