记录一些值得思考的问题
回答的思路
诞生背景
优势
缺点
硬件 (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 | 这里我就主要讲了 Synchronized 关键字,还有并发包下面的一些锁,以及各自的优缺点 |
JVM 的垃圾回收机制了解吗?
标记-清楚算法、复制算法、标记-压缩算法、分代收集算法的实现原理。
一、 技术背景
JVM 内存主要分为五个区,哪些是线程共享的,哪些是线程独享的,每个区存放什么。
二、 哪些内存需要回收?
1 | 2.1 引用计数算法 |
三、常用的垃圾收集算法
1 | 3.1 标记-清除算法 |
四、常见的垃圾收集器
五、GC 是什么时候触发的(面试最常见的问题之一)
1 | 5.1 Scavenge GC |
- 项目中用的是 CMS 垃圾回收器吗?为什么要分为四个阶段?学习的链接-1
1
2初始标记,并发标记,重新标记,并发清理四阶段,系统要求快速响应低延迟,对于多核
CPU 性能更佳等方面去回答即可,网上文章很多,这里不多赘述了。
学习的链接-2
网络
我看你简历里说熟悉计算机网络,来聊一聊计算机网络吧。了不了解
tcp/udp,简单说下两者的区别?
区别:
1 | TCP 面向连接(如打电话要先拨号建立连接);UDP 是无连接的,即发送数据之前不 |
两次握手为什么不可以?为什么要四次挥手?
1 | 采用三次握手是为了防止失效的连接请求报文段突然又传送到主机 B,因而产生错误。失 |
TCP 怎么保证有序传输的?
1 | 应用数据被分割成 TCP 认为最适合发送的数据块。 |
time_wait 状态,这个状态出现在什么地方,有什么用?
1 | 1. 为实现 TCP 全双工连接的可靠释放 |
HTTP 与 HTTPS 有啥区别?HTTPS 是怎么做到安全的?
HTTPS 安全的原因:
1 | HTTPS 把 HTTP 消息进行加密之后再传送,这样就算坏人拦截到了,得到消息之后也看不 |
作者:前行的乌龟
链接:https://juejin.cn/post/6860153911366385678
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。