后端面试八股文(120+题·深入解析版)

6 阅读54分钟

说明

本文档针对操作系统、计算机网络、Java编程、Springboot框架四大核心领域,整理出高频面试题。每题不仅给出核心要点,更深入剖析原理、提供实际案例、阐明应用场景,帮助读者真正理解而非死记硬背。


第一部分:操作系统(30题)

1.1 进程与线程

1. 进程和线程的区别是什么?

解析
进程是资源分配的基本单位,线程是CPU调度的基本单位。

  • 资源拥有:进程拥有独立的地址空间、文件描述符、信号处理等资源;线程共享所属进程的地址空间和资源。
  • 切换开销:进程切换涉及页表切换、TLB刷新、Cache失效,开销大;线程切换只需保存/恢复寄存器、程序计数器,开销小。
  • 通信方式:进程间通信(IPC)需要特殊机制(管道、共享内存等);线程间通信可直接读写共享内存,但需同步。

举例

  • 在浏览器中,每个标签页可以是一个进程(如Chrome的多进程架构),一个标签页内的多个子任务(如渲染、脚本执行)可以是线程。
  • 服务器处理并发请求:可以为每个请求创建线程(轻量级),而不是进程,以支持更高并发。

2. 进程间通信(IPC)有哪些方式?

解析

  • 管道(Pipe):半双工,常用于父子进程。命名管道(FIFO)允许无亲缘关系进程通信。
  • 消息队列:消息链表,克服了管道只能传输无格式字节流的缺点。
  • 共享内存:最快的IPC,多个进程映射同一块内存,但需同步机制(如信号量)。
  • 信号量:用于同步,本身不传递数据。
  • 信号(Signal):异步通知,如SIGKILL
  • 套接字(Socket):可用于不同主机间的进程通信。

举例

  • 数据库和应用程序之间可能使用共享内存+信号量实现高效数据交换。
  • Nginx使用共享内存实现多个worker进程间的负载均衡计数器。

3. 什么是协程?与线程的区别?

解析
协程是用户态轻量级线程,由程序自身控制调度(协作式),而非内核抢占式。

  • 切换开销:协程切换只需保存少量寄存器,无需陷入内核,开销极小(纳秒级),线程切换需微秒级。
  • 栈大小:协程栈可动态增长,初始很小(几KB),线程栈固定(通常MB级)。
  • 并发模型:协程适合大量并发任务,如Go语言的goroutine(实际是协程+多线程混合模型)。
  • 区别:线程由内核调度,协程由用户调度器调度;协程不能利用多核(需配合多线程),但可通过多线程实现并行。

举例

  • 使用Java虚拟线程(Project Loom)编写高并发Web服务器,每个请求一个虚拟线程,可支持百万级连接。
  • Python的asyncio库使用协程实现异步I/O。

4. 进程的状态有哪些?状态转换图?

解析
进程生命周期包含以下状态:

  • 创建(New):进程正在被创建。
  • 就绪(Ready):已获得所需资源,只等待CPU分配。
  • 运行(Running):正在CPU上执行。
  • 阻塞(Blocked/Waiting):等待某事件(如I/O完成)而暂停执行。
  • 终止(Terminated):进程执行结束。

状态转换

  • 就绪→运行:调度程序选择进程执行。
  • 运行→就绪:时间片用完或优先级被抢占。
  • 运行→阻塞:请求I/O或等待事件。
  • 阻塞→就绪:I/O完成或事件发生。

举例

  • 当用户打开一个文本编辑器,进程创建;加载完成后进入就绪队列;用户输入时,CPU执行该进程;当编辑器等待磁盘保存文件时,进程阻塞;保存完成回到就绪。

5. 什么是孤儿进程?僵尸进程?如何处理?

解析

  • 孤儿进程:父进程先于子进程结束,子进程被init进程(PID=1)收养,init会自动回收子进程资源,因此无害。
  • 僵尸进程:子进程结束,但父进程未调用wait()waitpid()回收其进程描述符,导致进程表项残留。僵尸进程已释放大部分资源,但占用内核进程表项,过多会耗尽系统资源。
  • 处理:父进程应调用wait()捕获子进程退出状态,或设置信号处理函数处理SIGCHLD信号。也可采用signal(SIGCHLD, SIG_IGN)忽略(某些系统会自动回收)。

举例

  • 一个长期运行的服务器程序如果频繁创建子进程而不wait,会导致大量僵尸进程,最终无法创建新进程。

6. 上下文切换的开销包括哪些?

解析

  • 寄存器保存/恢复:CPU寄存器(通用寄存器、程序计数器、栈指针等)。
  • 内存管理开销:切换进程时需切换页表,导致TLB刷新,新进程首次访问内存会大量TLB miss,增加访问延迟。
  • 内核态切换:从用户态进入内核态,再返回,涉及特权级别切换。
  • 缓存影响:CPU Cache、BTB等因地址空间变化而失效,降低命中率。
  • 调度器开销:调度算法执行时间。

举例

  • 高并发网络服务器(如Nginx)采用多进程/线程模型,但若进程数过多,频繁上下文切换会导致CPU大量时间花在切换上而非处理请求,因此需控制进程数或使用协程。

7. 线程同步的方式有哪些?

解析

  • 互斥锁(Mutex):保护临界区,一次只有一个线程访问。
  • 读写锁(RWLock):读共享,写独占,适合读多写少场景。
  • 信号量(Semaphore):允许多个线程同时访问资源(计数信号量),可做同步或互斥。
  • 条件变量(Condition Variable):与互斥锁配合,使线程等待特定条件满足。
  • 屏障(Barrier):多个线程到达屏障后再一起继续执行。
  • Java特有synchronizedReentrantLockCountDownLatchCyclicBarrierSemaphorePhaser

举例

  • 生产者-消费者问题:使用互斥锁保护缓冲区,使用条件变量让消费者在缓冲区空时等待,生产者通知。

8. 什么是死锁?四个必要条件是什么?

解析
死锁指多个进程因竞争资源而互相等待,无法继续执行。
四个必要条件(缺一不可):

  1. 互斥:资源一次只能被一个进程使用。
  2. 请求并保持:进程已保持至少一个资源,又请求新资源,且不释放已有资源。
  3. 不可剥夺:资源只能由进程主动释放。
  4. 循环等待:存在进程循环链,每个进程等待下一个进程持有的资源。

处理策略

  • 预防:破坏任一条件,如采用资源一次性分配(破坏请求并保持)、可剥夺资源(破坏不可剥夺)、资源按序分配(破坏循环等待)。
  • 避免:银行家算法,动态检查安全性。
  • 检测与恢复:允许死锁发生,检测后剥夺资源或终止进程。

举例

  • 两个线程:线程A持有锁1,请求锁2;线程B持有锁2,请求锁1,形成循环等待。

1.2 内存管理

9. 虚拟内存的作用是什么?

解析

  • 内存隔离:每个进程拥有独立的虚拟地址空间,不会互相干扰。
  • 扩展容量:可使用比物理内存更大的地址空间,通过页面置换将不常用页面换出到磁盘。
  • 简化内存管理:程序员无需关心物理内存分配,只需关注虚拟地址。
  • 共享与保护:不同进程可映射同一物理页面(如共享库),同时通过页表权限位实现保护。

举例

  • 运行一个需要10GB内存的数据分析程序,物理内存只有4GB,通过虚拟内存将部分数据放在磁盘交换分区,程序仍可运行(但变慢)。

10. 分页和分段有什么区别?

解析

  • 分页:将物理内存划分为固定大小的页框,虚拟地址也划分为等大页面。页表管理映射。目的:实现虚拟内存,消除外部碎片。
  • 分段:按逻辑单位(如代码段、数据段、堆栈)划分,段大小可变。目的:方便共享和保护,符合程序员视角。
  • 区别:分页对用户透明,分段可见;分页大小固定,分段可变;分页易产生内部碎片,分段易产生外部碎片。现代系统通常采用段页式:先分段,段内分页。

举例

  • Linux进程的地址空间:代码段、数据段、堆、栈是逻辑分段,内部使用分页管理。

11. 页面置换算法有哪些?

解析

  • 最佳置换(OPT):置换未来最长时间不会使用的页面,理论最优但无法实现。
  • 先进先出(FIFO):置换最早进入内存的页面,实现简单但可能换出频繁使用的页面(Belady异常:增加内存反而缺页更多)。
  • 最近最久未使用(LRU):置换最长时间未使用的页面,性能好但硬件支持成本高。
  • 最不经常使用(LFU):置换访问次数最少的页面,需计数,可能无法适应变化。
  • 时钟(Clock):近似LRU,使用页表项的访问位,循环扫描置换。

举例

  • 数据库中Buffer Pool常用LRU或Clock算法管理缓存页。

12. LRU如何实现?(手撕代码)

解析

  • 数据结构:哈希表+双向链表。哈希表存储键到链表节点的映射,保证O(1)查找。链表按访问时间排序,头部为最近使用,尾部为最久未使用。
  • 操作
    • get(key):若存在,将节点移到头部,返回值。
    • put(key, value):若存在,更新值并移到头部;若不存在,插入头部,若容量超限,删除尾部节点。
  • Java实现:继承LinkedHashMap并重写removeEldestEntry

举例

  • 手写一个LRU缓存类,支持getput,容量为100。

13. 什么是缺页中断?

解析
当CPU访问的页面不在物理内存中时,MMU(内存管理单元)检测到页表项无效,触发缺页异常(Page Fault)。操作系统中断处理程序:

  1. 检查虚拟地址合法性(是否在进程地址空间内)。
  2. 寻找空闲物理页框,若无则按置换算法淘汰一个页面(若脏页需写回磁盘)。
  3. 从磁盘(交换区或文件)读入所需页面到页框。
  4. 更新页表,重新执行引起缺页的指令。

举例

  • 程序首次访问一个大型数组,数组分布在磁盘上,触发一系列缺页中断,逐步加载到内存。

14. 什么是TLB(快表)?作用是什么?

解析
TLB(Translation Lookaside Buffer)是CPU内部的高速缓存,存储最近使用的页表项(虚拟页号→物理页框号)。作用:加速虚拟地址到物理地址的转换,避免每次访问内存都去查页表(页表在内存中)。TLB命中则直接获得物理地址,未命中则需遍历页表(硬件或软件填充)。

举例

  • 现代CPU的TLB分为指令TLB和数据TLB,支持多级页表。当进程切换时,TLB需刷新(或使用ASID标识)。

15. 什么是内存抖动(Thrashing)?

解析
内存抖动指进程频繁缺页,导致大量磁盘I/O,CPU利用率急剧下降。原因:分配给进程的物理页面数小于其工作集(当前频繁访问的页面集合),或程序局部性差。抖动时,进程大部分时间在等待页面换入换出,系统吞吐量极低。

解决方法

  • 增加物理内存。
  • 采用工作集模型,保证进程有足够页面。
  • 引入页面置换算法的本地分配策略,防止全局抖动。

举例

  • 一个数据库系统若Buffer Pool设置太小,导致频繁的页面置换,系统响应变慢,CPU等待I/O。

16. 堆和栈的区别?

解析

  • :由编译器自动管理,存储局部变量、函数参数、返回地址。分配速度快,大小固定(一般几MB),溢出可能导致栈溢出。生长方向向下(向低地址)。
  • :由程序员手动分配(如malloc/new),存储动态数据。分配速度慢(需查找空闲块),大小受虚拟内存限制,生长方向向上(向高地址)。需防止内存泄漏。
  • 线程特有:每个线程有自己的栈,堆是所有线程共享。

举例

  • 在函数内定义int a = 10;,a在栈上。使用int *p = new int;,p指向的整型在堆上,指针p本身在栈上。

1.3 I/O模型与文件系统

17. 五种I/O模型是什么?

解析

  • 阻塞I/O:进程调用recvfrom直到数据准备好并拷贝完成才返回,期间进程阻塞。
  • 非阻塞I/O:recvfrom立即返回错误(EWOULDBLOCK),进程需轮询检查。
  • I/O多路复用:select/poll/epoll监听多个描述符,阻塞在select,有数据就绪时通知进程再调用recvfrom。
  • 信号驱动I/O:进程注册SIGIO信号处理函数,数据准备好时内核发送信号,进程在信号处理中调用recvfrom。
  • 异步I/O:进程调用aio_read后立即返回,内核负责数据拷贝完成后再通知进程(信号或回调)。前四种为同步I/O(实际读写过程阻塞),最后一种异步I/O。

举例

  • Redis使用I/O多路复用(epoll)处理大量客户端连接,单线程高效运行。

18. select、poll、epoll的区别?

解析

  • select
    • 用三个fd_set描述符集合(读、写、异常),有最大数量限制(通常1024)。
    • 每次调用需将集合从用户态拷贝到内核态,返回后需遍历所有fd检查是否就绪。
    • 效率随fd数增加线性下降。
  • poll
    • 使用pollfd数组,无最大数量限制(受内存限制)。
    • 同样需拷贝数组到内核,遍历所有fd检查,效率仍为O(n)。
  • epoll(Linux特有):
    • 事件驱动,内核维护一个事件表,用户通过epoll_ctl注册感兴趣的事件,epoll_wait只返回就绪的fd。
    • 使用mmap共享内存减少拷贝。
    • 支持水平触发(LT)和边缘触发(ET),效率O(1)。

举例

  • Nginx采用epoll实现高并发,每个worker进程可处理数十万连接。

19. epoll的LT和ET模式有什么区别?

解析

  • LT(水平触发):默认模式。只要fd上还有数据可读/可写,每次epoll_wait都会返回该fd。编程简单,但可能多次通知。
  • ET(边缘触发):仅当fd状态发生变化(从未就绪到就绪)时通知一次。需使用非阻塞fd,且一次性读写完所有数据,否则会丢失剩余数据。ET效率更高(减少系统调用),但编程复杂。

举例

  • 在ET模式下,读数据时必须循环调用read直到返回EAGAIN,确保读完。

20. 零拷贝技术原理?

解析
零拷贝指避免CPU参与数据在内核空间和用户空间之间的拷贝。

  • 传统读-写:磁盘→内核缓冲区(DMA)→用户缓冲区(CPU)→内核Socket缓冲区(CPU)→网卡(DMA),共4次拷贝,2次CPU参与。
  • mmap:将文件映射到用户地址空间,用户直接访问内核缓冲区,避免一次CPU拷贝。
  • sendfile:文件描述符到Socket描述符的直接传输,数据从磁盘→内核缓冲区(DMA)→Socket缓冲区(DMA),无需CPU拷贝。若支持DMA gather,甚至只需一次DMA拷贝。

举例

  • 文件服务器(如Nginx静态文件)使用sendfile高效传输文件,提升性能。

21. 硬链接和软链接的区别?

解析

  • 硬链接:多个文件名指向同一个inode(索引节点),inode中记录链接计数。删除一个硬链接只是减少计数,直到计数为0才删除文件。硬链接不能跨文件系统,不能链接目录。
  • 软链接(符号链接):是一个特殊文件,内容指向目标路径。删除原文件,软链接失效(悬空链接)。可跨文件系统,可链接目录。

举例

  • ln file1 file2创建硬链接,ln -s file1 file2创建软链接。硬链接共享同一数据块,软链接像快捷方式。

22. Linux文件系统的inode是什么?

解析
inode存储文件的元数据,包括:

  • 文件大小、权限、所有者、时间戳(atime/mtime/ctime)。
  • 数据块指针(直接、间接、二级间接指针)。
  • 链接计数等。
    inode不包含文件名,文件名存储在目录项(dentry)中。每个文件对应一个inode,目录也是一种文件,其内容为文件名到inode的映射。

举例

  • 执行ls -i可查看文件的inode号。stat filename显示inode详细信息。

23. BIO、NIO、AIO的区别?

解析

  • BIO(Blocking I/O):同步阻塞,一个连接一个线程,线程在I/O操作时阻塞,不适合高并发。
  • NIO(Non-blocking I/O):同步非阻塞,基于多路复用(Selector),一个线程可处理多个连接,数据准备好后才进行读写操作(但不阻塞在读写上)。
  • AIO(Asynchronous I/O):异步非阻塞,发起I/O操作后立即返回,内核完成数据拷贝后通知程序(回调或Future),程序无需主动读写。

举例

  • Netty框架基于NIO实现高性能网络应用。Java的AsynchronousFileChannel是AIO示例。

1.4 Linux命令与调优

24. 查看CPU负载用哪个命令?

解析

  • top:动态显示进程和CPU负载信息,包括平均负载(load average)1/5/15分钟。
  • uptime:简洁显示当前时间、运行时间、登录用户数和平均负载。
  • htop:top的增强版,支持彩色和交互。
    平均负载指单位时间内运行队列中的平均进程数(包括正在运行和等待I/O的)。理想情况小于CPU核心数。

举例

  • uptime输出:14:32:06 up 10 days, 3 users, load average: 0.08, 0.03, 0.01,表示最近1分钟平均负载0.08。

25. 查看内存使用情况?

解析

  • free -h:显示物理内存和交换分区使用情况,-h人可读格式。
  • vmstat:报告进程、内存、分页、块IO、陷阱、CPU活动。
  • cat /proc/meminfo:详细内存信息。
    关键指标:total、used、free、buff/cache、available。available是可用内存估计。

举例

  • free -m输出:Mem: 15962 total, 10234 used, 5728 free, 1234 buff/cache

26. 查看磁盘I/O?

解析

  • iostat -x 1:显示设备利用率、平均I/O请求大小、等待时间等。
  • iotop:类似top,显示进程的I/O使用情况。
  • dstat:全能系统资源统计。
    重点关注:%util(设备忙碌时间百分比)、await(平均I/O响应时间)、svctm(服务时间)。

举例

  • %util接近100%,说明磁盘可能成为瓶颈。

27. 查看网络连接状态?

解析

  • netstat -anp:显示所有连接和监听端口,-t只TCP,-u只UDP,-l监听状态,-p显示进程。
  • ss -tunlp:更快速高效的socket统计工具。
  • 常见状态:ESTABLISHED、LISTEN、TIME_WAIT、CLOSE_WAIT。

举例

  • netstat -tlnp查看哪些端口在监听。

28. 如何查找CPU占用高的线程?

解析

  • 使用top -H -p <pid>显示该进程所有线程的CPU占用,找到高占用线程ID(十进制)。
  • 或用ps -L -p <pid>显示线程列表。
  • 对于Java程序,可将线程ID转换为十六进制,用jstack <pid> | grep -A 10 <hex_tid>查看堆栈。

举例

  • 线程ID 12345,十六进制0x3039,jstack 1234 | grep -A 20 0x3039

29. OOM(Out Of Memory)如何排查?

解析

  • 查看系统日志:dmesg | grep -i oom/var/log/messages,查找OOM Killer记录,确定被杀的进程。
  • free -m检查内存是否耗尽。
  • top找出内存占用高的进程。
  • 对于Java应用,启用GC日志(-XX:+PrintGCDetails),分析堆转储(jmap dump后用MAT分析)。
  • 调整JVM参数或优化代码。

举例

  • OOM Killer日志显示:Out of memory: Kill process 1234 (java)

30. Linux的Namespace和Cgroup作用?

解析

  • Namespace:隔离资源,使进程拥有独立的视图。包括PID、Network、Mount、UTS、IPC、User、Cgroup等namespace。
  • Cgroup:限制、记录、隔离进程组的资源使用(CPU、内存、磁盘I/O、网络带宽)。
  • 两者结合是容器(如Docker)的核心技术,Namespace提供隔离,Cgroup提供资源限制。

举例

  • Docker容器内看到独立的PID 1进程,网络命名空间有自己的IP和端口。

第二部分:计算机网络(35题)

2.1 网络模型与基础

31. OSI七层模型 vs TCP/IP四层模型?

解析

  • OSI七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
  • TCP/IP四层:网络接口层(对应OSI物理+数据链路)、网络层(IP)、传输层(TCP/UDP)、应用层(对应OSI上三层)。
  • 常见协议:HTTP/HTTPS/SMTP在应用层,TCP/UDP在传输层,IP在网络层,以太网在数据链路层。
  • 分层好处:模块化、标准化、便于开发和维护。

举例

  • 当你在浏览器输入网址,HTTP(应用层)将请求交给TCP(传输层),TCP添加端口信息交给IP(网络层),IP添加IP地址交给以太网驱动(网络接口层)。

32. 数据在各层之间如何封装解封?

解析

  • 封装:发送方逐层添加头部。应用层数据→传输层加TCP/UDP头(形成段)→网络层加IP头(形成包)→数据链路层加MAC头+帧尾(形成帧)→物理层比特流。
  • 解封:接收方逐层去掉头部。物理层接收比特流→数据链路层去掉MAC头→网络层去掉IP头→传输层去掉TCP/UDP头→应用层获得原始数据。

举例

  • HTTP请求:GET /index.html,在TCP层加上源/目的端口,IP层加上源/目的IP,以太网加上源/目的MAC。

33. 为什么要分层?

解析

  • 降低复杂性:每层只关注特定功能,易于实现和维护。
  • 标准化:各层协议独立发展,便于不同厂商设备互操作。
  • 灵活性:修改一层不影响其他层,如传输层从TCP改为UDP,上层应用可能不需改动。
  • 促进竞争:各层可独立优化。

举例

  • 物理层从铜缆换成光纤,上层协议无需改变。

34. 常见端口号:HTTP、HTTPS、FTP、SSH?

解析

  • HTTP:80
  • HTTPS:443
  • FTP:20(数据端口)、21(控制端口)
  • SSH:22
  • MySQL:3306
  • Redis:6379
  • DNS:53
  • SMTP:25
  • POP3:110

举例

  • 在浏览器中访问https://example.com:443,默认可省略端口。

35. 什么是MTU?MSS?

解析

  • MTU(最大传输单元):数据链路层能传输的最大数据包大小(包括IP头)。以太网MTU=1500字节。
  • MSS(最大分段大小):TCP层数据段的最大长度,MSS = MTU - IP头(20) - TCP头(20)= 1460字节(假设无选项)。
  • 如果IP包大于MTU,需分片。TCP通过MSS避免IP分片,提高效率。

举例

  • 当TCP发送数据时,每次最多发送MSS大小的数据段,避免IP分片。

2.2 TCP协议

36. TCP和UDP的区别?

解析

特性TCPUDP
连接面向连接(三次握手)无连接
可靠性可靠(确认重传、排序、去重)不可靠,可能丢失、乱序
流量控制有(滑动窗口)
拥塞控制
首部大小20-60字节8字节
应用场景文件传输、HTTP、邮件视频流、DNS、游戏

举例

  • 视频通话使用UDP容忍少量丢包,但要求低延迟;下载文件用TCP保证完整正确。

37. 三次握手过程?为什么是三次?

解析
过程

  1. 客户端→服务端:SYN=1, seq=x(客户端进入SYN_SENT)
  2. 服务端→客户端:SYN=1, ACK=1, seq=y, ack=x+1(服务端进入SYN_RCVD)
  3. 客户端→服务端:ACK=1, seq=x+1, ack=y+1(客户端进入ESTABLISHED,服务端收到后也进入ESTABLISHED)

为什么三次

  • 确保双方收发能力正常。
  • 同步初始序列号(ISN),防止历史连接干扰。两次握手可能因延迟的旧SYN导致错误连接。
  • 三次可让双方确认自己的发送和接收没问题。

举例

  • 若两次握手:客户端发送SYN后,服务器就建立连接,但客户端可能未收到确认(网络问题),服务器却一直等待数据,浪费资源。

38. 四次挥手过程?为什么是四次?

解析
过程

  1. 主动关闭方→被动方:FIN=1, seq=u(主动方进入FIN_WAIT_1)
  2. 被动方→主动方:ACK=1, seq=v, ack=u+1(被动方进入CLOSE_WAIT,主动方进入FIN_WAIT_2)
  3. 被动方→主动方:FIN=1, seq=w, ack=u+1(被动方进入LAST_ACK)
  4. 主动方→被动方:ACK=1, seq=u+1, ack=w+1(主动方进入TIME_WAIT,被动方收到后CLOSED)

为什么四次

  • TCP半关闭特性:一方发送FIN只表示不再发送数据,但仍可接收数据。因此被动方可能还有数据要发送,ACK和FIN分开,避免数据丢失。

举例

  • 服务器处理完请求后发送FIN,客户端回复ACK,但客户端可能还有数据要发(如最后确认),所以客户端发送FIN前需等自己数据发完。

39. 为什么TIME_WAIT需要2MSL?

解析
MSL(Maximum Segment Lifetime):报文最大生存时间,通常30秒到2分钟。
2MSL原因

  1. 保证主动关闭方最后的ACK能到达被动方。若ACK丢失,被动方会超时重发FIN,主动方在2MSL内可收到重发的FIN,再次ACK。
  2. 让本连接的所有旧报文在网络中消失,防止影响后续使用相同四元组的新连接。

举例

  • 客户端主动关闭连接,进入TIME_WAIT,等待2MSL确保网络中没有残留报文,然后才真正释放端口。

40. TIME_WAIT过多怎么办?

解析
过多TIME_WAIT会占用端口和系统资源。
解决方案

  • 调整内核参数:net.ipv4.tcp_tw_reuse(允许将TIME_WAIT连接用于新连接,需配合tcp_timestamps
  • net.ipv4.tcp_tw_recycle(更快回收,但NAT环境有问题,不推荐)
  • net.ipv4.tcp_max_tw_buckets(限制最大数量,超出后系统会主动关闭)
  • 应用层优化:使用长连接(Keep-Alive),减少连接建立/关闭次数。

举例

  • 高并发短连接Web服务器,可开启tcp_tw_reuse并设置tcp_max_tw_buckets

41. TCP如何保证可靠性?

解析

  • 序列号:数据按序编号,接收方按序重组,乱序可缓存。
  • 确认应答(ACK):接收方返回已成功接收的最大序列号。
  • 超时重传:发送方未收到ACK,超时后重传。
  • 校验和:检测数据损坏,丢弃坏包。
  • 流量控制:接收方通告窗口大小,控制发送速率。
  • 拥塞控制:网络拥塞时降低发送速率。

举例

  • 发送方发seq=1-1000,收到ack=1001表示1-1000已收到,继续发后续。

42. 流量控制和拥塞控制的区别?

解析

  • 流量控制:点对点,防止发送方过快导致接收方缓冲区溢出。通过滑动窗口实现,接收方在ACK中通告自己的剩余窗口(rwnd)。
  • 拥塞控制:全局性,防止过多数据注入网络导致网络过载。通过拥塞窗口(cwnd)实现,由发送方根据网络状况调整。最终发送窗口 = min(rwnd, cwnd)。

举例

  • 接收方处理慢,通告rwnd=1000;网络拥塞,cwnd=500,则实际发送窗口=500。

43. 拥塞控制算法有哪些?

解析

  • TCP Reno:慢启动(cwnd指数增长)、拥塞避免(线性增长)、快重传(收到3个重复ACK重传)、快恢复(不回到慢启动)。
  • TCP Cubic:Linux默认,拥塞避免阶段使用三次函数,更适合高带宽长延迟网络。
  • TCP BBR:Google开发,基于瓶颈带宽和RTT探测,不依赖丢包,适应现代网络。

举例

  • 在丢包率高但非拥塞的网络(如无线),Reno误判拥塞导致性能下降,BBR则表现更好。

44. 滑动窗口机制?

解析
滑动窗口用于流量控制和可靠传输。

  • 发送窗口:允许连续发送但未确认的字节范围。窗口左边界是已确认序号+1,右边界=左边界+窗口大小-1。
  • 接收窗口:接收方告知的空闲缓冲区大小。
  • 发送方收到ACK后,窗口滑动,可发送新数据。
  • 零窗口:接收方窗口为0,发送方停止发送,并定期发送窗口探测包。

举例

  • 发送窗口大小为4,已发数据1-4未确认,收到ack=3后窗口滑动到4-7。

45. Nagle算法和延迟确认?

解析

  • Nagle算法:减少小包数量。若发送方有多个小数据段,只发送第一个,后续小段需等待前一个被确认或凑满MSS再发送。
  • 延迟确认:接收方不立即发ACK,延迟一小段时间(如200ms),希望合并数据或反向数据捎带ACK。
  • 两者结合可能造成延迟增加(发送方等ACK,接收方等数据)。

举例

  • Telnet交互:每次按键产生一个字节,Nagle会延迟发送,导致响应慢,通常关闭Nagle。

46. TCP粘包/拆包问题?

解析
TCP是流协议,无消息边界,发送方多个写操作可能合并成一个包(粘包),或一个大写操作被拆分成多个包(拆包)。
解决方案

  1. 固定长度:每个消息定长,不足补零。
  2. 分隔符:如\r\n,但需转义。
  3. 长度字段:消息头包含消息长度。

举例

  • 应用层协议如HTTP:使用Content-Length或分块传输解决粘包。

47. SYN Flood攻击原理与防范?

解析
攻击者发送大量SYN包,不完成握手,耗尽服务端的半连接队列(SYN Queue),导致正常连接无法建立。
防范

  • SYN Cookie:不保存连接状态,根据源信息计算初始序列号,收到ACK时重建。
  • 增大半连接队列:调整net.ipv4.tcp_max_syn_backlog
  • 丢弃延迟连接:设置tcp_synack_retries重试次数。
  • 防火墙过滤

举例

  • 攻击者伪造IP发送SYN,服务器回复SYN-ACK后等待ACK,但一直等不到,半连接队列满。

2.3 HTTP/HTTPS协议

48. HTTP状态码分类及常见状态码?

解析

  • 1xx(信息):100 Continue,继续请求。
  • 2xx(成功):200 OK,204 No Content,206 Partial Content。
  • 3xx(重定向):301 Moved Permanently(永久),302 Found(临时),304 Not Modified(未修改,使用缓存)。
  • 4xx(客户端错误):400 Bad Request,401 Unauthorized,403 Forbidden,404 Not Found。
  • 5xx(服务器错误):500 Internal Server Error,502 Bad Gateway,503 Service Unavailable,504 Gateway Timeout。

举例

  • 访问不存在页面返回404;服务器过载返回503。

49. GET和POST的区别?

解析

  • GET:参数在URL,有长度限制(浏览器限制),可缓存,幂等(多次请求结果相同),用于获取资源。
  • POST:参数在Body,无长度限制,不可缓存,非幂等,用于提交数据。
  • 安全性:GET参数暴露在URL,POST稍好但也不安全,都需HTTPS。

举例

  • 搜索用GET,因为可分享链接;登录用POST,因为密码不宜暴露。

50. Cookie和Session的区别?

解析

  • Cookie:客户端(浏览器)存储的小文本,大小限制4KB,可被篡改,每次请求自动携带。
  • Session:服务器端存储的用户数据,安全,但占用服务器内存。通常SessionID通过Cookie传输。
  • 分布式场景:Session共享需使用中间件(Redis)或客户端Token(JWT)。

举例

  • 登录后服务器生成Session,Set-Cookie返回SessionID,后续请求携带Cookie,服务器识别用户。

51. HTTP1.0、1.1、2.0、3.0的区别?

解析

  • HTTP/1.0:短连接,每个请求新建TCP,不支持Host头(单IP多域名困难)。
  • HTTP/1.1:默认持久连接(Connection: keep-alive),支持管道化(但队头阻塞),Host头,分块传输编码。
  • HTTP/2.0:二进制分帧、多路复用(一个TCP并发多个请求)、头部压缩(HPACK)、服务器推送。但TCP队头阻塞仍在(丢包影响所有流)。
  • HTTP/3.0:基于QUIC(UDP),0-RTT握手,多路复用无队头阻塞(流独立),连接迁移。

举例

  • 加载一个包含100个小资源的网页,HTTP/1.1需多个连接,HTTP/2一个连接多路复用更快。

52. HTTP2.0的多路复用原理?

解析
HTTP/2.0将请求和响应拆分为独立的(Frame),并标记属于哪个(Stream)。多个流的帧可在同一TCP连接上交错发送,接收方按流ID重组。解决了HTTP/1.x的队头阻塞(一个请求阻塞,后面请求等待)。

举例

  • 浏览器同时请求style.css和script.js,两个流的帧交替发送,同时到达,无需等待。

53. HTTP3.0为什么基于QUIC?

解析
QUIC(Quick UDP Internet Connections)是Google设计的基于UDP的传输协议。

  • 解决队头阻塞:TCP的队头阻塞是因丢包导致整个连接暂停,QUIC在一个连接内多个流独立,丢包只影响单个流。
  • 快速握手:QUIC合并了TLS握手,0-RTT可发送数据。
  • 连接迁移:使用连接ID而非IP/端口标识连接,切换网络(如WiFi→4G)不中断。
  • 内置加密:QUIC默认使用TLS1.3。

举例

  • 手机从WiFi切换到5G,TCP连接会断开,需重新握手;QUIC连接ID不变,无缝继续。

54. HTTPS工作原理?

解析

  1. 客户端发起HTTPS请求(默认443)。
  2. 服务端返回数字证书(含公钥)。
  3. 客户端验证证书(是否可信、是否过期、域名匹配)。
  4. 验证通过后,客户端生成随机对称密钥,用证书公钥加密发送给服务端。
  5. 服务端用私钥解密得到对称密钥。
  6. 后续通信使用对称密钥加密,保证机密性;同时通过MAC或AEAD保证完整性。

举例

  • 访问银行网站,浏览器显示锁标志,说明HTTPS加密。

55. TLS1.2和TLS1.3的区别?

解析

  • 握手延迟:TLS1.2需2-RTT完成握手(不包括TCP),TLS1.3简化到1-RTT,支持0-RTT(需风险)。
  • 加密套件:TLS1.3移除了不安全算法(如RC4、CBC模式),只保留AEAD(如AES-GCM)。
  • 会话恢复:TLS1.3使用PSK(预共享密钥)更高效。
  • 扩展性:TLS1.3减少扩展协商的复杂性。

举例

  • TLS1.3的0-RTT允许客户端在发送第一条消息时携带加密数据,适合低延迟应用。

56. 什么是中间人攻击?如何防范?

解析
中间人攻击(MITM)指攻击者拦截通信双方,冒充一方与另一方通信,窃听或篡改数据。
防范

  • 使用HTTPS并严格验证证书(包括证书颁发机构、域名、有效期)。
  • 启用HSTS(HTTP Strict Transport Security),强制浏览器使用HTTPS。
  • 证书公钥固定(Public Key Pinning),防止伪造证书。
  • 使用双向认证(客户端证书)。

举例

  • 在公共WiFi,攻击者可伪造证书,若用户忽略浏览器警告,则可能被劫持。

57. 跨域问题及解决方案?

解析
浏览器的同源策略(协议+域名+端口相同)禁止跨域请求。
解决方案

  • CORS(跨域资源共享):服务端设置Access-Control-Allow-Origin响应头,允许特定或所有域访问。
  • JSONP:利用<script>标签无跨域限制,只支持GET,需服务端返回回调函数调用。
  • 代理:前端请求同源代理,代理转发到目标服务器(如Nginx反向代理)。
  • WebSocket:本身无跨域限制。

举例

  • 前端http://a.com请求http://b.com/api,需b.com设置Access-Control-Allow-Origin: http://a.com

58. WebSocket与HTTP的区别?

解析

  • 协议:WebSocket是独立协议,但通过HTTP升级(Upgrade: websocket)建立连接。
  • 通信方式:HTTP是请求-响应模式,服务端不能主动推送;WebSocket是全双工,服务端可主动发消息。
  • 开销:WebSocket握手后,数据帧头部小,开销低。
  • 应用:适合实时应用(聊天、游戏、股票行情)。

举例

  • 在线聊天室使用WebSocket,服务端有新消息可立即推送给所有客户端。

59. 浏览器输入URL到页面显示全过程?

解析

  1. URL解析:判断是搜索关键字还是URL,补全协议。
  2. DNS解析:查找域名对应的IP,从浏览器缓存、OS缓存、hosts、本地DNS服务器、根DNS等递归/迭代查询。
  3. TCP连接:若为HTTPS,先TCP三次握手,然后TLS握手(若HTTPS)。
  4. 发送HTTP请求:构造请求行、请求头、请求体,发送到服务器。
  5. 服务器处理:返回HTTP响应。
  6. 浏览器解析:解析HTML生成DOM树,解析CSS生成CSSOM树,执行JS。
  7. 布局与绘制:根据DOM和CSSOM计算布局,绘制页面。
  8. 连接关闭:若HTTP/1.0或未启用keep-alive,关闭连接;否则保持。

举例

  • 整个过程涉及网络、渲染引擎、JS引擎协同工作。

2.4 DNS与网络安全

60. DNS解析过程?递归vs迭代?

解析

  • 递归查询:客户端向本地DNS服务器请求,本地DNS负责完成整个查询(可能向根、TLD、权威服务器询问),最后返回结果给客户端。
  • 迭代查询:本地DNS服务器向根DNS询问,根返回TLD服务器地址;本地DNS再向TLD询问,TLD返回权威服务器地址;最后向权威服务器查询得到IP。

举例

  • 浏览器访问www.example.com,操作系统向本地DNS(如8.8.8.8)发起递归查询,本地DNS则迭代查询根、com TLD、example.com权威,最后返回IP。

61. DNS污染和劫持是什么?

解析

  • DNS污染:中间设备(如路由器、ISP)伪造DNS响应,返回错误的IP,导致用户访问钓鱼网站或无法访问。
  • DNS劫持:篡改DNS请求或响应,将域名指向恶意IP。
  • 防范:使用DNSSEC(数字签名验证),或使用加密DNS(DoH/DoT)。

举例

  • 某ISP为了节省成本,将无法解析的域名返回自己的广告页面,这就是DNS劫持。

62. DoH/DoQ是什么?

解析

  • DoH(DNS over HTTPS):将DNS查询封装在HTTPS请求中,通过443端口传输,加密且难以被中间人篡改。
  • DoQ(DNS over QUIC):基于QUIC协议传输DNS,结合了加密和低延迟。
  • 目的:提高DNS隐私和安全性,防止窥探和劫持。

举例

  • 浏览器启用DoH后,DNS查询通过HTTPS发送到指定服务器(如Cloudflare的1.1.1.1)。

63. ARP协议工作原理?

解析
ARP(地址解析协议)用于将IP地址解析为MAC地址。

  • 主机广播ARP请求:“谁有IP 192.168.1.1?请告诉我的MAC。”
  • 目标主机单播ARP响应:“我是192.168.1.1,我的MAC是xx:xx:xx:xx:xx:xx。”
  • 收到响应后,主机将映射存入ARP缓存。

举例

  • 同一局域网内的主机A想和B通信,已知B的IP,需通过ARP获取B的MAC。

64. NAT的作用和类型?

解析
NAT(网络地址转换)用于将私有IP转换为公网IP,实现多个设备共享一个公网IP。

  • 静态NAT:一对一的固定映射。
  • 动态NAT:从地址池中动态分配。
  • PAT(端口地址转换):多对一,通过端口区分不同内网主机,最常见的NAT类型。

举例

  • 家庭路由器将内网192.168.1.x的请求映射到同一个公网IP,用不同端口区分。

65. DDoS攻击与防范?

解析
DDoS(分布式拒绝服务)攻击指利用大量僵尸主机向目标发送海量请求,耗尽资源。
常见类型

  • 带宽消耗型:UDP洪水、ICMP洪水。
  • 连接耗尽型:SYN洪水、HTTP慢速攻击。
  • 应用层攻击:HTTP洪水。

防范

  • 流量清洗(云服务商)、限流、CDN防护、IP黑名单、Syn Cookie、WAF等。

举例

  • 攻击者控制数千台肉鸡向某网站发送大量HTTP GET请求,导致服务器过载。

第三部分:Java编程(35题)

3.1 Java基础

66. 面向对象的三大特性?

解析

  • 封装:将数据和行为包装在类中,对外隐藏实现细节,通过访问修饰符控制访问。
  • 继承:子类继承父类,复用代码,可扩展和覆盖。
  • 多态:同一操作作用于不同对象产生不同结果,包括编译时多态(方法重载)和运行时多态(方法重写)。

举例

  • Animal类有makeSound()方法,DogCat分别重写,调用时根据实际类型发出不同叫声。

67. ==和equals的区别?

解析

  • ==:比较基本数据类型时比较值;比较引用类型时比较内存地址(是否同一对象)。
  • equals():默认同==,但通常被重写(如String、Integer)以比较内容。

举例

  • String s1 = new String("abc"); String s2 = new String("abc");
    s1 == s2为false,s1.equals(s2)为true。

68. hashCode()和equals()的关系?

解析

  • 若两个对象相等(equals返回true),则hashCode必须相等。
  • 若两个对象hashCode相等,它们不一定相等(哈希碰撞)。
  • 重写equals必须重写hashCode,保证逻辑一致性。

举例

  • HashSet判断元素是否重复:先比较hashCode,若相等再equals,提高效率。

69. String、StringBuffer、StringBuilder的区别?

解析

  • String:不可变,每次修改生成新对象,线程安全(因为不可变)。
  • StringBuffer:可变,线程安全(方法用synchronized),性能较低。
  • StringBuilder:可变,线程不安全,性能最高。

举例

  • 字符串拼接频繁时使用StringBuilder,如在循环中。

70. 重载和重写的区别?

解析

  • 重载(Overload):同一类中方法名相同,参数列表不同(个数、类型、顺序),与返回值和修饰符无关,编译时多态。
  • 重写(Override):子类重新定义父类方法,方法签名必须相同,返回类型协变,访问权限不能更严格,运行时多态。

举例

  • println有多种重载:println(int), println(String)
  • 子类重写toString()方法。

71. 接口和抽象类的区别?

解析

  • 接口:多继承,成员变量默认public static final,方法默认public abstract(Java8后有default/static方法),无构造方法。
  • 抽象类:单继承,可有实例变量,可有构造方法,方法可实现也可抽象,访问修饰符任意。
  • 设计上:接口定义能力(can-do),抽象类定义模板(is-a)。

举例

  • List接口,AbstractList抽象类提供部分实现。

72. 什么是自动装箱/拆箱?

解析
自动装箱:基本类型自动转为包装类(如int → Integer)。
自动拆箱:包装类自动转为基本类型。
发生在赋值、方法调用、运算时。
注意:可能产生NullPointerException(包装类为null拆箱时)。

举例

  • Integer i = 100; // 自动装箱
  • int j = i; // 自动拆箱

73. 异常体系结构?

解析

  • Throwable:所有异常父类,分为Error和Exception。
  • Error:虚拟机错误、内存溢出等,程序无法处理。
  • Exception:分为受检异常(Checked)和运行时异常(RuntimeException)。
    • 受检异常:如IOException,必须捕获或声明抛出。
    • 运行时异常:如NullPointerException,无需处理,由程序逻辑错误引起。

举例

  • 文件不存在抛出FileNotFoundException(受检),需try-catch。

3.2 Java集合框架

74. ArrayList和LinkedList的区别?

解析

  • 底层结构:ArrayList基于动态数组,LinkedList基于双向链表。
  • 随机访问:ArrayList O(1),LinkedList O(n)。
  • 插入删除:ArrayList在尾部快,中间慢(需移动元素);LinkedList在头尾快,中间需遍历(但节点修改本身O(1))。
  • 内存占用:ArrayList预分配空间,可能浪费;LinkedList每个节点存前后指针,开销大。

举例

  • 频繁按索引访问用ArrayList,频繁在头部插入/删除用LinkedList。

75. HashMap底层原理?(JDK1.7 vs 1.8)

解析

  • JDK1.7:数组+链表(Entry),头插法,扩容时可能死循环(多线程并发)。
  • JDK1.8:数组+链表+红黑树。当链表长度≥8且数组长度≥64,链表转为红黑树,提高查询效率;尾插法避免死循环。
  • put过程:计算hash,找到数组索引,若空则插入;否则遍历链表/树,若key存在则替换,否则插入。
  • 扩容:当size > threshold(负载因子*容量)时,resize为2倍,重新hash。

举例

  • 存储键值对,HashMap利用hash码确定位置。

76. HashMap的put方法流程?

解析

  1. 计算key的hash值:(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
  2. 根据hash计算索引:(n - 1) & hash
  3. 若table[i]为空,直接插入。
  4. 若不为空,判断key是否相等(==或equals),若相等则替换value。
  5. 否则判断节点类型:若为树节点,调用红黑树插入;若为链表,遍历链表,若找到相同key则替换,否则在链表尾部插入。
  6. 插入后若链表长度≥8,且数组长度<64则扩容,否则树化。
  7. 若size超过阈值,扩容。

举例

  • 插入map.put("name", "John"),先计算hash,定位索引,操作。

77. 为什么HashMap线程不安全?

解析

  • JDK1.7:并发put可能导致循环链表,get死循环。
  • JDK1.8:并发put可能导致数据覆盖(如两个线程同时判断同一位置为空,都插入,一个被覆盖);同时put与扩容并发可能导致数据丢失。

举例

  • 线程A和B同时put,同时发现索引为空,同时插入,其中一个数据丢失。

78. ConcurrentHashMap如何保证线程安全?

解析

  • JDK1.7:使用分段锁(Segment),继承ReentrantLock,每个Segment独立加锁,默认16个Segment,可并行操作不同Segment。
  • JDK1.8:放弃分段锁,采用CAS + synchronized。对每个桶的头节点加锁(链表或树),并发度高。
  • 使用Unsafe的CAS操作更新size等。

举例

  • 并发put时,对桶加synchronized,其他桶可同时操作。

79. HashSet底层实现?

解析
HashSet内部使用HashMap存储元素,元素作为key,value是一个固定的Object(PRESENT)。

  • add(e)调用map.put(e, PRESENT),若返回null则成功。
  • 不保证顺序,允许null。

举例

  • Set<String> set = new HashSet<>();实际是HashMap。

80. LinkedHashMap如何实现LRU?

解析
LinkedHashMap继承HashMap,维护双向链表记录插入顺序或访问顺序。

  • 构造参数accessOrder为true时,每次访问(get/put)会将节点移到链表尾部(最近使用)。
  • 重写removeEldestEntry方法,当返回true时删除链表头部(最久未使用)。

举例

  • 实现LRU缓存:继承LinkedHashMap,设置accessOrder=true,重写removeEldestEntry判断size>capacity时删除最老。

81. Comparable和Comparator的区别?

解析

  • Comparable:内部比较器,类实现该接口,重写compareTo,定义自然排序。
  • Comparator:外部比较器,单独实现compare方法,可定义多种排序规则,更灵活。

举例

  • String实现了Comparable,按字典序比较。
  • 若想按字符串长度排序,可使用Comparator.comparing(String::length)

3.3 Java并发编程

82. 线程的生命周期及状态转换?

解析
Java线程状态定义在Thread.State枚举中:

  • NEW:创建未启动。
  • RUNNABLE:就绪或运行(包含操作系统层面的Ready和Running)。
  • BLOCKED:等待监视器锁(synchronized)。
  • WAITING:无限期等待(如wait()join()无超时)。
  • TIMED_WAITING:限期等待(如sleep(timeout)wait(timeout))。
  • TERMINATED:终止。

转换

  • NEW → RUNNABLE(start())
  • RUNNABLE → BLOCKED(未获得锁)
  • RUNNABLE → WAITING/TIMED_WAITING(调用wait/join/park等)
  • 锁释放后BLOCKED → RUNNABLE
  • WAITING/TIMED_WAITING被唤醒 → RUNNABLE

举例

  • 调用Thread.sleep(1000)使线程进入TIMED_WAITING。

83. sleep()和wait()的区别?

解析

  • 所属类:sleep是Thread的静态方法,wait是Object的方法。
  • 锁释放:sleep不释放锁,wait释放锁并进入等待队列。
  • 唤醒:sleep时间到自动唤醒,wait需notify/notifyAll。
  • 使用条件:wait必须在同步块中,sleep无此要求。

举例

  • 生产者-消费者中,消费者缓冲区空时调用wait释放锁,生产者填充后notify唤醒。

84. volatile关键字的作用?

解析

  • 可见性:对volatile变量的写操作立即刷新到主存,读操作从主存读取,避免线程本地缓存不一致。
  • 禁止指令重排:插入内存屏障,防止JVM重排序优化。
  • 不保证原子性:如count++仍非线程安全。

举例

  • 状态标志:volatile boolean flag = true;线程A修改flag,线程B能立即看到。

85. synchronized底层原理?

解析

  • 对象头(Mark Word)存储锁状态。
  • 偏向锁:单线程竞争时,偏向第一个线程,避免CAS。
  • 轻量级锁:少量竞争时,线程自旋尝试获取锁,避免阻塞。
  • 重量级锁:竞争激烈时,锁升级为重量级锁(依赖操作系统互斥量),线程阻塞。
  • 锁升级过程:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(不可逆)。

举例

  • 同步实例方法锁的是当前对象实例,静态方法锁的是Class对象。

86. ReentrantLock和synchronized的区别?

解析

  • 锁实现:synchronized是JVM内置,ReentrantLock是API(基于AQS)。
  • 灵活性:ReentrantLock支持公平/非公平、可中断、超时、绑定多个条件。
  • 释放:synchronized自动释放,ReentrantLock需手动unlock(通常finally中)。
  • 性能:早期synchronized较差,现优化后两者相近。

举例

  • 使用ReentrantLock实现超时获取锁:tryLock(1, TimeUnit.SECONDS)

87. AQS是什么?原理?

解析
AQS(AbstractQueuedSynchronizer)是Java并发包的基础框架,用于构建锁和同步器(如ReentrantLock、CountDownLatch)。

  • 核心:一个volatile int state(表示资源状态),一个CLH双端队列(存放等待线程)。
  • 获取资源:tryAcquire尝试获取,若失败则封装成节点入队,自旋或阻塞。
  • 释放资源:tryRelease释放,唤醒后继节点。
  • 子类需实现tryAcquire/tryRelease等模板方法。

举例

  • ReentrantLock的Sync继承AQS,实现tryAcquire判断state。

88. 线程池的核心参数?

解析

  • corePoolSize:核心线程数(即使空闲也保留)。
  • maximumPoolSize:最大线程数。
  • keepAliveTime:非核心线程空闲存活时间。
  • unit:时间单位。
  • workQueue:任务队列(如ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue)。
  • threadFactory:线程工厂。
  • handler:拒绝策略。

举例

  • new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100))

89. 线程池工作原理?

解析

  1. 提交任务时,若线程数 < corePoolSize,创建新线程执行。
  2. 若 >= corePoolSize,任务放入workQueue。
  3. 若队列满,且线程数 < maximumPoolSize,创建新线程执行。
  4. 若队列满且线程数已达maximum,执行拒绝策略。
  5. 线程空闲超过keepAliveTime,回收非核心线程。

举例

  • 线程池core=5,max=10,队列容量100,提交120个任务,先创建5个核心线程,队列满100,再创建5个非核心,最后10个任务被拒绝。

90. 拒绝策略有哪些?

解析

  • AbortPolicy(默认):抛出RejectedExecutionException。
  • CallerRunsPolicy:由提交任务的线程自己执行任务。
  • DiscardPolicy:直接丢弃任务。
  • DiscardOldestPolicy:丢弃队列中最早的任务,然后重新提交。

举例

  • 若不想丢失任务,可用CallerRunsPolicy让主线程执行,但可能阻塞提交。

91. ThreadLocal原理及内存泄漏?

解析

  • 原理:每个Thread维护一个ThreadLocalMap,键是ThreadLocal弱引用,值是set的值。
  • get/set:先获取当前线程的Map,操作之。
  • 内存泄漏:ThreadLocal的key是弱引用,若外部强引用置null,key会被GC,但value仍有强引用(线程存活),导致value无法回收。
  • 解决:使用完调用remove()清理。

举例

  • Web应用中使用ThreadLocal存储用户会话,请求结束后需remove,否则线程复用导致数据错乱。

3.4 JVM

92. JVM内存区域划分?

解析

  • 程序计数器:线程私有,记录当前线程执行的字节码地址。
  • 虚拟机栈:线程私有,存储栈帧(局部变量表、操作数栈、方法出口等)。
  • 本地方法栈:为native方法服务。
  • :线程共享,存放对象实例,GC主要区域,分新生代、老年代。
  • 方法区:线程共享,存储类信息、常量、静态变量,运行时常量池。
  • 直接内存:NIO的DirectByteBuffer使用,不受JVM堆限制。

举例

  • new Object()分配在堆,方法参数在栈。

93. 堆内存的分代结构?

解析

  • 新生代:占堆1/3,分为Eden、Survivor0、Survivor1(默认8:1:1)。新对象分配在Eden。
  • 老年代:占堆2/3,存放长期存活的对象(经过多次GC)。
  • 永久代/元空间:方法区在HotSpot的实现,JDK8后元空间使用本地内存。

举例

  • 大部分对象在新生代分配,Minor GC频繁,对象年龄增加后进入老年代。

94. 如何判断对象是否可回收?

解析

  • 引用计数法:每个对象维护引用计数,为0回收(无法解决循环引用)。
  • 可达性分析:从GC Roots向下搜索,不可达的对象可回收。
  • GC Roots包括:虚拟机栈引用、静态属性引用、JNI引用、活跃线程等。

举例

  • 两个对象互相引用,但无GC Roots引用它们,可达性分析判定不可达。

95. GC Roots包括哪些?

解析

  • 虚拟机栈(栈帧中的本地变量表)引用的对象。
  • 方法区中静态属性引用的对象。
  • 方法区中常量引用的对象(如字符串常量池)。
  • 本地方法栈中JNI引用的对象。
  • Java虚拟机内部的引用(如基本数据类型对应的Class对象、常驻异常对象)。
  • 所有被同步锁(synchronized)持有的对象。

举例

  • 局部变量Object obj = new Object(),obj在栈中,是GC Root。

96. 垃圾回收算法有哪些?

解析

  • 标记-清除:先标记可回收对象,然后统一清除,产生内存碎片。
  • 复制:将内存分为两块,只使用一块,存活对象复制到另一块,清理原块,适合新生代(存活率低)。
  • 标记-整理:标记存活对象,然后向一端移动,清理边界外内存,适合老年代。
  • 分代收集:结合不同算法,新生代用复制,老年代用标记-整理或标记-清除。

举例

  • HotSpot的CMS使用标记-清除,G1使用区域化复制。

97. 垃圾回收器有哪些?CMS和G1的区别?

解析

  • Serial:单线程,简单,适合单核。
  • Parallel:多线程,追求高吞吐量。
  • CMS:并发标记清除,低延迟,但产生碎片,CPU敏感,可能Concurrent Mode Failure。
  • G1:分区式,可预测停顿,将堆划分为Region,混合收集(新生代+老年代),避免碎片。

区别

  • CMS老年代收集,G1全堆收集。
  • CMS使用标记-清除,G1使用复制+标记-整理。
  • G1更能控制停顿时间。

举例

  • 低延迟应用(如Web)常用G1。

98. 类加载机制?双亲委派模型?

解析
类加载过程:加载→验证→准备→解析→初始化。
双亲委派:当类加载器收到加载请求,先委派给父类加载器加载,只有父类无法加载才自己尝试。

  • 作用:防止重复加载,保证核心类库安全(如java.lang.Object始终由引导类加载器加载)。
  • 破坏:如Tomcat的WebAppClassLoader先自己加载,实现隔离。

举例

  • 自定义java.lang.String类不会被加载,因为父类加载器已加载了标准String。

99. OOM(内存溢出)有哪些类型?如何排查?

解析

  • Java heap space:堆内存溢出,对象过多或泄漏。
  • GC overhead limit exceeded:GC时间过长。
  • PermGen/Metaspace:元空间溢出,类太多。
  • StackOverflowError:栈深度超限。
  • Direct buffer memory:直接内存溢出。

排查

  • 使用jmap -dump生成堆转储,用MAT分析。
  • 查看GC日志。
  • 分析代码,检查大对象、集合无限制增长。

举例

  • 循环中不断向List添加对象,导致堆溢出。

100. JVM调优常用参数?

解析

  • 堆设置-Xms(初始堆)、-Xmx(最大堆)、-Xmn(新生代大小)、-XX:SurvivorRatio
  • 元空间-XX:MetaspaceSize-XX:MaxMetaspaceSize
  • GC选择-XX:+UseG1GC-XX:+UseConcMarkSweepGC
  • GC日志-XX:+PrintGCDetails-Xloggc:gc.log
  • 内存溢出-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath

举例

  • 生产环境常见配置:-Xms4g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200

第四部分:Springboot框架(20题)

4.1 Spring核心

101. IOC(控制反转)是什么?

解析
IOC(Inversion of Control)将对象的创建和依赖关系的管理交给容器(Spring IoC容器),而不是由对象自己创建依赖。

  • 依赖注入(DI):容器通过构造器、setter等方式注入依赖。
  • 好处:解耦、提高可测试性、组件可替换。

举例

  • 传统方式:Service service = new Service(new Dao());
    Spring方式:@Autowired private Service service;,容器注入依赖。

102. AOP(面向切面编程)是什么?

解析
AOP通过动态代理将横切关注点(如日志、事务、权限)与业务逻辑分离。

  • 核心概念:切面(Aspect)、连接点(Joinpoint)、通知(Advice)、切入点(Pointcut)、织入(Weaving)。
  • 实现:基于JDK动态代理(接口)或CGLIB(类)。

举例

  • 声明式事务:@Transactional注解,Spring AOP在方法前后开启/提交事务。

103. Bean的生命周期?

解析

  1. 实例化:通过构造器或工厂创建Bean实例。
  2. 属性赋值:注入依赖(如@Autowired)。
  3. 初始化
    • 调用BeanNameAwareBeanFactoryAware等Aware接口。
    • 执行BeanPostProcessorpostProcessBeforeInitialization
    • 调用@PostConstructInitializingBeanafterPropertiesSet
    • 执行BeanPostProcessorpostProcessAfterInitialization
  4. 使用:Bean就绪。
  5. 销毁:容器关闭时,调用@PreDestroyDisposableBeandestroy

举例

  • 自定义Bean实现InitializingBeanDisposableBean可看到生命周期回调。

104. Bean的作用域有哪些?

解析

  • singleton(默认):整个容器一个实例。
  • prototype:每次获取新创建。
  • request:每个HTTP请求一个实例(Web应用)。
  • session:每个HTTP会话一个实例。
  • application:每个ServletContext一个实例。

举例

  • 无状态的Service用singleton,有状态的Bean(如购物车)用session。

105. Spring的循环依赖如何解决?

解析

  • 三级缓存
    • singletonObjects:一级缓存,存放完整Bean。
    • earlySingletonObjects:二级缓存,存放早期暴露的Bean(半成品,已实例化未初始化)。
    • singletonFactories:三级缓存,存放ObjectFactory,用于创建代理。
  • 解决流程:A依赖B,B依赖A。
    1. 实例化A,放入三级缓存。
    2. A注入B,发现B未创建,去创建B。
    3. 实例化B,从三级缓存拿到A的ObjectFactory,获取早期引用(若A需要代理,在此生成代理),放入二级缓存。
    4. B完成注入,放入一级缓存。
    5. A从二级缓存拿到B的代理,完成注入。
  • 只能解决setter注入循环依赖,构造器注入无法解决

举例

  • 两个Service相互@Autowired,Spring能正常启动。

106. Spring用了哪些设计模式?

解析

  • 工厂模式:BeanFactory。
  • 单例模式:Bean默认单例。
  • 代理模式:AOP实现。
  • 模板方法:JdbcTemplate、RestTemplate。
  • 观察者模式:ApplicationListener、事件机制。
  • 适配器模式:HandlerAdapter适配不同Controller。
  • 策略模式:Resource访问策略。

举例

  • 事件发布:ApplicationEventPublisher发布事件,监听者收到通知。

107. @Autowired和@Resource的区别?

解析

  • @Autowired:Spring注解,默认按类型(byType)注入,若多个同类型,需配合@Qualifier指定名称。
  • @Resource:JSR-250注解,默认按名称(byName)注入,名称通过name指定,找不到再按类型。

举例

  • 若有两个UserService实现,用@Autowired@Qualifier("userServiceImpl1"),用@Resource(name="userServiceImpl1")

108. Spring事务的传播机制?

解析
事务传播行为定义方法被嵌套调用时的事务行为(Spring中通过@Transactional(propagation=...)指定)。

  • REQUIRED(默认):支持当前事务,若无则新建。
  • SUPPORTS:支持当前事务,若无则以非事务执行。
  • MANDATORY:必须有事务,否则抛异常。
  • REQUIRES_NEW:新建事务,挂起当前事务。
  • NOT_SUPPORTED:以非事务执行,挂起当前事务。
  • NEVER:不能有事务,否则抛异常。
  • NESTED:嵌套事务(JDBC保存点实现,需数据库支持)。

举例

  • 方法A调用方法B,若B传播为REQUIRES_NEW,则B独立提交,不受A回滚影响。

4.2 Spring Boot

109. Spring Boot自动配置原理?

解析

  • 启动类@SpringBootApplication包含@EnableAutoConfiguration
  • @EnableAutoConfiguration导入AutoConfigurationImportSelector,其通过SpringFactoriesLoader加载META-INF/spring.factories中配置的自动配置类(如DataSourceAutoConfiguration)。
  • 自动配置类使用@Conditional条件注解(如@ConditionalOnClass@ConditionalOnMissingBean),按条件生效。

举例

  • 若classpath下有HikariCP,且没有自定义DataSource,则自动配置Hikari数据源。

110. Spring Boot Starter是什么?

解析
Starter是一组方便的依赖描述符,聚合了特定功能所需的依赖和自动配置。

  • 例如spring-boot-starter-web包含Spring MVC、Tomcat、Jackson等,引入后即可开发Web应用。
  • 自定义Starter:创建自动配置模块,在spring.factories中配置。

举例

  • 引入spring-boot-starter-data-redis后,可使用RedisTemplate。

111. Spring Boot启动流程?

解析

  1. 运行SpringApplication.run(主类, args)
  2. 创建SpringApplication实例,设置应用类型(Web/非Web),加载初始化器(ApplicationContextInitializer)和监听器(ApplicationListener)。
  3. 调用run方法:
    • 启动StopWatch。
    • 配置环境(Environment)。
    • 打印Banner。
    • 创建ApplicationContext(根据类型)。
    • 准备Context:设置环境、注册单例Bean、执行初始化器。
    • 刷新Context(加载Bean定义、实例化单例)。
    • 执行Runner(ApplicationRunner、CommandLineRunner)。
    • 发布ApplicationReadyEvent。

举例

  • 启动时日志显示Spring Boot的Banner和启动时间。

112. 嵌入式Servlet容器(Tomcat、Jetty)?

解析
Spring Boot内嵌了Servlet容器,无需部署WAR到外部容器。

  • 通过spring-boot-starter-tomcat(默认)、spring-boot-starter-jettyundertow引入。
  • 容器配置可通过ServerProperties(如server.port)或自定义WebServerFactoryCustomizer

举例

  • 修改端口:server.port=8081

113. application.properties和application.yml区别?

解析

  • properties:键值对格式,如server.port=8080
  • yml:YAML格式,层次结构清晰,如:
    server:
      port: 8080
    
  • Spring Boot均支持,推荐yml更易读。

举例

  • 多环境配置:可使用application-dev.yml,通过spring.profiles.active=dev激活。

114. Spring Boot如何实现热部署?

解析

  • 使用spring-boot-devtools依赖。
  • 原理:监控classpath变化,自动重启应用(比冷启动快,因只重新加载用户类)。
  • 可通过spring.devtools.restart.exclude排除资源。

举例

  • 开发时修改代码,保存后自动重启,提高开发效率。

4.3 Spring MVC

115. Spring MVC工作流程?

解析

  1. 用户请求到达DispatcherServlet(前端控制器)。
  2. DispatcherServlet根据请求路径查询HandlerMapping,找到对应的HandlerExecutionChain(含拦截器)。
  3. 执行拦截器前置方法,调用HandlerAdapter处理请求(执行Controller方法)。
  4. Controller返回ModelAndView
  5. 若返回逻辑视图名,ViewResolver解析为具体视图(如JSP)。
  6. 视图渲染,返回响应。

举例

  • 请求/user/list映射到UserController.list(),返回用户列表视图。

116. @RequestMapping作用?

解析
用于映射HTTP请求到控制器方法。

  • 可标注在类上(定义基础路径)和方法上。
  • 属性:value路径,method请求方法(GET/POST等),params请求参数,headers请求头,produces响应类型。
  • 派生注解:@GetMapping@PostMapping等。

举例

  • @GetMapping("/user/{id}")处理GET请求。

117. @RestController和@Controller的区别?

解析

  • @Controller:返回视图名(如JSP),配合@ResponseBody可返回数据。
  • @RestController = @Controller + @ResponseBody,所有方法默认返回JSON/XML,不进行视图解析。

举例

  • RESTful API通常用@RestController

118. 如何全局处理异常?

解析

  • 使用@ControllerAdvice + @ExceptionHandler
  • @ControllerAdvice定义全局异常处理类,@ExceptionHandler指定处理的异常类型。
  • 可返回ResponseEntity自定义错误信息。

举例

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        return ResponseEntity.status(500).body(e.getMessage());
    }
}

119. 拦截器和过滤器的区别?

解析

  • 过滤器(Filter):Servlet规范,基于回调函数,可拦截所有请求(包括静态资源),在Servlet之前/后执行。
  • 拦截器(Interceptor):Spring MVC组件,基于反射,只能拦截Controller请求,可访问Spring容器。
  • 执行顺序:Filter → Interceptor → Controller。

举例

  • 权限验证可用拦截器,字符编码设置可用过滤器。

120. 文件上传如何实现?

解析

  • 在Spring MVC中,使用MultipartFile接收上传文件。
  • 配置multipart解析器(Spring Boot自动配置)。
  • Controller方法参数:@RequestParam("file") MultipartFile file
  • 调用file.transferTo(dest)保存。

举例

@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
    file.transferTo(new File("/tmp/" + file.getOriginalFilename()));
    return "ok";
}

全文完 —— 以上120+题,每题均深入解析并附实例,可作为面试复习的全面指南。祝面试顺利!