当前位置 博文首页 > hebtu666:当年,兔子学姐靠这个面试小抄拿了个22k

    hebtu666:当年,兔子学姐靠这个面试小抄拿了个22k

    作者:[db:作者] 时间:2021-06-06 11:23

    本文顺序是操作系统(jvm)、网络、数据库(mysql/redis),都是当时兔子的学姐准备面试的时候总结的,学生面试基本不会跑出这个范围,懂行的应该能看出来。

    学姐原话:因为我本身的知识是A集合,我觉得一次记不住,需要反复看的,我就写了下来B集合。这些并不一定是所有,特别简单的或者特别生僻的A-B都没写。

    所以按我的理解,那些,特别特别基础的,不用学姐说,自己也应该会,然后再看这个文章。

    目录

    生产消费读者写者

    进程vs线程

    线程生命周期

    进程间通信方式

    线程间通信方式

    进程调度算法

    死锁

    页面置换算法

    磁盘调度算法

    jvmjvmjvmjvm区域

    判断对象死亡?引用计数 vs 可达性分析

    如何回收对象?垃圾收集算法

    垃圾收集器

    分配回收策略(老年代新生代)

    GC调优

    JVM常用参数

    Cookie session

    网络分层

    握手挥手

    UDP想可靠

    网络访问过程

    为什么tcp可靠?

    UDP可靠

    HTTP and HTTPS

    HTTP方法及相互区别

    状态码

    http结构

    Tcp结构

    Socket

    redisredisredis数据结构

    对象

    跳表

    HyperLogLog

    单线程

    持久化

    Redis和数据库双写一致性

    缓存击穿雪崩穿透

    解决Redis并发竞争Key问题

    Redis缓存策略

    哨兵

    解决会话

    sqlsqlsqlsqllsqlslqslqslq数据类型

    mysql特点

    存储引擎

    增删改

    范式

    索引优缺点和使用原则

    索引分类

    索引区别

    事务特性:ACID

    并发错误

    隔离级别

    并发控制技术(锁)

    死锁

    分布式事务

    SQL语句性能优化

    索引的优化

    分库分表

    Kafka

    Es


    生产消费读者写者

    读者写者:

    多读者/多写者互斥/多读者互斥

    消费者生产者:全互斥

    生产者:p(empty),p(mutex),v(mutex),v(full)

    消费者:p(full),p(mutex)v(mutex)v(empty)

    进程vs线程

    进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。

    线程是程序执行时的最小单位,是CPU调度和分派的基本单位。

    线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。

    线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。

    线程生命周期

    (五个状态):新建、就绪、运行、阻塞、死亡

    新建状态:线程对象已经创建,还没有在其上调用start()方法

    就绪状态:当线程调用start方法,但调度程序还没有把它选定为运行线程时线程

    运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。(是线程进入运行状态的唯一方式)

    阻塞(等待/睡眠)状态:线程仍旧是活的,但是当前没有条件运行。当某件事件出现,他可能返回到可运行状态

    死亡状态:当线程的run()方法完成时就认为它死去。线程一旦死亡,就不能复生。 一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常

    进程间通信方式

    1. 匿名管道:管道的实质是一个缓冲区,该缓冲区是一个循环队列,进程以先进先出的方式从缓冲区存取数据,管道一端的进程顺序的将数据写入缓冲区,另一端的进程则顺序的读出数据。管道的局限性体现在:只支持单向数据流、由于管道没有名字,只能用于有亲缘关系(共同祖先)的进程间通信、缓冲区有限,效率不高
    2. 有名管道:有名管道不同于匿名管道之处在于它提供了一个路径名与之关联,即使与有名管道的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过有名管道相互通信。
    3. 消息队列:消息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。
    4. 信号量:信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。
    1. 创建信号量:指定初始值,对于二值信号量通常是1。(2)等待信号量:测试信号量的值,如果小于0就阻塞。也称P操作。(3)挂出信号量:信号量值加1,称V操作。

    ?

    为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常是在内核中实现的。Linux环境中,有三种类型:Posix(可移植性操作系统接口)有名信号量(使用Posix IPC名字标识)、Posix基于内存的信号量(存放在共享内存区中)、System V信号量(在内核中维护)。这三种信号量都可用于进程间或线程间的同步。

    ?

    1. 套接字:是一种通信机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。

    (套接字位于传输层与应用层之间)

    套接字是支持TCP/IP网络通信的基本操作单元,可以看做主机间进程进行双向通信的端点

    ?套节字通信基本过程

    线程间通信方式

    1.锁机制:包括互斥锁(对应于JAVA中的Synchronized)、条件变量(对应于JAVA中的volatile)

    *互斥锁提供了以排他方式防止数据结构被并发修改的方法。

    *条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

    2.信号机制:包括无名线程信号量和命名线程信号量

    JAVA中线程通信的具体方式有:1.Volatile和Synchronized 2.wait()和notify()机制 3.管道输入/输出流:PipedReader, PipedWriter 4.Thread.join() 5.TheadLocal类:线程变量,用于线程内部共享数据。以ThreadLocal对象为键,任意对象为值的存储结构 ThreadLocal<String> tl = new ThreadLocal<>();

    进程调度算法

    先来先服务(FCFS)最简单调度算法。按照进入后备队列的先后次序来加入就绪队列等待执行。是非抢占式,易于实现,效率不高,利于长作业(CPU繁忙)不利于短作业(I/O繁忙)

    短作业优先(Short Job First)是非抢占式的,具有很好性能,降低平均等待时间,提高吞吐量。不利于长作业,可能一直处于等待出现饥饿;未考虑作业紧迫程度,不能用于实时系统。

    高响应比优先调度算法(Highest Reponse Ratio First, HRRF)(响应比高意味着等待时间长而服务时间短,优先处理)响应比 = (等待 + 服务) / 服务时间 = 等待 / 服务时间 + 1

    时间片轮转:用于分时系统的进程调度,抢占式。

    基本思想:将CPU时间分为若干时间片(q),进程按到达顺序排列。每次调度队首,执行1个时间片后,该进程移至队尾。能在给定时间响应所有用户请求,达到分时系统的目的。

    其性能主要取决于时间片q的大小,q太大,则所有的进程在1个时间片完成;太小则进程频繁切换,系统开销大。

    多级反馈队列调度算法:将时间片轮转与优先级调度相结合,把进程按优先级分成不同的队列,先按优先级调度,优先级相同的,按时间片轮转。优点是兼顾长短作业,有较好的响应时间,可行性强,适用于各种作业环境。

    死锁

    什么是死锁?

    当两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,就称为死锁。

    必要条件

    禁止抢占 - 资源不能被强制从一个进程中退出

    持有和等待 - 一个进程可以在等待时持有系统资源

    互斥 – 某个资源在一段时间内只能由一个进程占有

    循环等待 - 一系列进程互相持有其他进程所需要的资源,银行家算法-破环循环等待条件,合理分配系统资源

    死锁的应对方法

    1. 最简单、最常用方法是重新启动,不过代价很大,意味着之前所有进程计算都将付之东流

    2. 撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。

    分两种情况:一次性剥夺全部资源;或逐步撤消参与死锁的进程,选择逐步撤消目的是撤消代价最小的进程,比如按进程优先级确定代价;考虑进程运行代价和此进程相关作业代价等;

    3. 进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处。操作起来系统开销大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。

    页面置换算法

    最佳置换算法(OPT)

    最佳(OPT)置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。

    先进先出置换算法(FIFO)

    最简单的页面置换算法是先入先出(FIFO)法。这种算法的实质是,总是选择在主存中停留时间最长(即最老)的一页置换,即先进入内存的页,先退出内存。

    最近最久未使用(LRU)算法

    它的实质是,当需要置换一页时,选择在最近一段时间里最久没有使用过的页面予以置换。

    ?

    ?

    磁盘调度算法

    1.FIFO:先来先服务算法;(依次处理)

    2.SSTF: 最短寻道时间算法;(距离当前磁道最近的有限处理)

    3.SCAN:电梯调度算法;(这样命名很形象,先按一个方向,扫描的过程依次访问要求服务的队列,当扫描到最里层的一个服务序列时就反向扫描)

    4.CSCAN: 循环扫描算法(从最里面一个磁道访问完之后,立即返回最外层,也称单向扫描调度算法)

    5.FSCAN:分步电梯调度算法(分两个队列)

    ?

    jvmjvmjvmjvm区域

    线程私有的:程序计数器、(虚拟机栈、本地方法栈)

    线程共享的:堆、方法区、直接内存 (非运行时数据区的一部分)

    程序计数器是一块较小的内存空间,是当前线程执行字节码的行号指示。

    工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。

    作用:

    通过改变程序计数器来依次读取指令实现流程控制,如:顺序执行、选择、循环、异常处理。

    多线程:记录当前线程执行的位置,线程被切换回来时能够知道该线程上次运行到哪儿了。

    虚拟机栈(Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack))

    存放基本数据类型(boolean、byte、char、short、int、float、long、double)和对象引用

    会出现两种异常:StackOverFlowError 和 OutOfMemoryError。

    StackOverFlowError:不动态拓展,?线程请求栈的深度超过当前虚拟机栈的最大深度

    OutOfMemoryError:?动态扩展,且内存用完了,无法扩展

    堆:唯一目的就是存放对象实例,几乎所有实例和数组都在这里。

    方法区(永久代):存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等。

    《Java 虚拟机规范》规定了有方法区和作用,并没实现。(像接口),在不同的 JVM 上方法区的实现肯定是不同的了。?永久代是 HotSpot 虚拟机对方法区的一种实现方式。(像类)

    判断对象死亡?引用计数 vs 可达性分析

    引用计数算法:给对象添加一个引用计数器,每当有一个地方引用到他,就加1;引用失效就减1。有问题,有可能存在循环引用,导致对象无法被回收。

    可达性分析算法:以GC Roots对象为起始点,从这些节点向下搜索

    可以作为GC ROOT对象的有:Java虚拟机栈中的引用对象。本地方法栈中JNI(既一般说的Native方法)引用的对象。方法区中类静态属性所引用的对象。方法区中常量所引用的对象。

    如何回收对象?垃圾收集算法

    标记清除算法:分为标记和清除两个阶段。

    首先从根集合进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收(老年代)

    主要不足有两个:效率问题:标记和清除两个过程的效率都不高;

    空间问题:不进行对象的移动,并且仅对不存活的对象进行处理,因此标记清除之后会产生大量不连续的内存碎片,可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

    复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这种算法适用于对象存活率低的场景,比如新生代。

    标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,类似于磁盘整理的过程,该垃圾回收算法适用于对象存活率高的场景(老年代)

    垃圾收集器

    Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

    Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;

    ParNew收集器 (复制算法): 新生代并行收集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;

    Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;

    CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

    收集过程分为:初始标记,并发标记,重新标记,垃圾回收

    G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

    ?

    分配回收策略(老年代新生代)

    自动内存管理可以归结为自动化地解决两个问题:

    分配内存、回收内存。

    一般而言,对象主要分配在新生代的Eden区上,少数情况下也可能直接分配在老年代中。

    1)??? 对象优先在Eden分配,当Eden区没有足够空间时,虚拟机将发起一次MinorGC。

    2)??? 大对象直接进入老年代。所谓的大对象是指,很长的字符串以及数组。

    3)??? 长期存活对象进入老年代。在新生代中经历n次(默认15)Minor GC后,晋升老年代。

    4)??? 动态对象年龄判定。为了更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

    ?

    GC调优

    各分区的大小对GC的性能影响很大。

    如何将各分区调整到合适的大小,分析活跃数据的大小是很好的切入点。

    活跃数据的大小是指,应用程序稳定运行时长期存活对象在堆中占用的空间大小,也就是Full GC后堆中老年代占用空间的大小。可以通过GC日志中Full GC之后老年代数据大小得出,比较准确的方法是在程序稳定后,多次获取GC数据,通过取平均值的方式计算活跃数据的大小

    通过收集GC信息,结合系统需求,确定优化方案,例如选用合适的GC回收器、重新设置内存比例、调整JVM参数等。

    根据对象生命周期的分布情况:如果应用存在大量的短期对象,应该选择较大的年轻代;如果存在相对较多的持久对象,老年代应该适当增大。

    ?

    JVM常用参数

    -Xms: 初始堆大小

    -Xmx:最大堆大小

    -XX:NewSize=n:设置年轻代大小

    -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3表示年轻代和年老代比值为1:3

    -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如3表示Eden:3 Survivor:2(3:1:1),一个Survivor区占整个年轻代的1/5

    -XX:MaxPermSize=n:设置永久代大小

    ?

    Cookie session

    Cookie与Session都是用来跟踪浏览器用户身份的会话方式。

    Cookie放客户浏览器,Session放服务器。

    Cookie不安全,别人可以分析本地Cookie进行Cookie欺骗,用加密的Cookie或Session。

    Session存服务器。占服务器性能,如果减轻负担方面,用Cookie。

    单个Cookie在客户端的限制是4K,很多浏览器都限制一个站点最多保存20个Cookie。

    网络分层

    物理层:在传输媒体上传输数据比特流。屏蔽介质和通信手段差异,使数据链路层感觉不到

    数据链路层:主机间有很多链路,为相邻结点间服务。把网络层传来的分组封装成帧。

    网络层:为主机间提供传输服务,把运输层传递下来的报文段或数据报封装成分组。

    运输层:进程间的通用数据传输服务。

    ???????? 传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;

    ???????? 用户数据报协议 UDP,提供无连接数据传输服务,数据单位为用户数据报。

    ???????? TCP 主要提供完整性服务,UDP 主要提供及时性服务。

    应用层:为特定应用程序提供数据传输服务,例如 HTTP、DNS 等。数据单位为报文。

    握手挥手

    序号seq :对字节流编号,序号为 301,编号为 301,如携带数据 100 字节,下一个报文段的序号应为 401。用来标记顺序

    确认号 ack:期望收到的下一个报文段序号。例如 B 收到 A报文段序号为 501,长度 200 ,因此 B 期望701,B 发送给 A 的确认报文段中确认号就为 701。

    确认 ACK :当 ACK=1 时确认号ack字段有效,否则无效。

    同步 SYN :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。

    终止 FIN :用来释放一个连接,当 FIN=1 时,表示发送方已发送完毕,要求释放连接。

    窗口 :窗口值作为接收方让发送方设置其发送窗口的依据。这个限制是因为接收方的数据缓存空间有限

    ?

    1、第一次握手:客户端给服务器发送一个 SYN 报文。

    2、第二次握手:服务器收到 SYN 报文之后,会应答一个 SYN+ACK 报文。

    3、第三次握手:客户端收到 SYN+ACK 报文之后,会回应一个 ACK 报文。

    4、服务器收到 ACK 报文之后,三次握手建立完成。

    第一次:seq随机x,ACK=0,syn=1,ack=0,

    第二次:seq随机y,ACK=X+1,syn=1,ack=1

    第三次:seq=x+1,ACK=Y+1,syn=0,ack=1

    第一次握手:客户端发,服务端收到。服务端就能得出结论:客户端的发送能力、服务端接收能力是正常

    第二次握手:服务端发,客户端收到。客户端就能得出结论:服务端的接收发送能力,客户端的接收发送能力是正常的。服务器不能确认客户端的接收能力是否正常。

    第三次握手:客户端发,服务端收到了。这样服务端就能得出结论:客户端的接收发送能力正常,服务器自己的发送、接收能力也正常。

    第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。

    客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。

    为什么三次四次?

    三次:Server在收到建立连接请求后,可以直接把ACK和SYN放在一个报文发送给Client。

    四次:关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,因此,己方ACK和FIN一般都会分开发送。

    ?

    四次挥手: