说明
本文档针对操作系统、计算机网络、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特有:
synchronized、ReentrantLock、CountDownLatch、CyclicBarrier、Semaphore、Phaser。
举例
- 生产者-消费者问题:使用互斥锁保护缓冲区,使用条件变量让消费者在缓冲区空时等待,生产者通知。
8. 什么是死锁?四个必要条件是什么?
解析
死锁指多个进程因竞争资源而互相等待,无法继续执行。
四个必要条件(缺一不可):
- 互斥:资源一次只能被一个进程使用。
- 请求并保持:进程已保持至少一个资源,又请求新资源,且不释放已有资源。
- 不可剥夺:资源只能由进程主动释放。
- 循环等待:存在进程循环链,每个进程等待下一个进程持有的资源。
处理策略:
- 预防:破坏任一条件,如采用资源一次性分配(破坏请求并保持)、可剥夺资源(破坏不可剥夺)、资源按序分配(破坏循环等待)。
- 避免:银行家算法,动态检查安全性。
- 检测与恢复:允许死锁发生,检测后剥夺资源或终止进程。
举例
- 两个线程:线程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缓存类,支持
get和put,容量为100。
13. 什么是缺页中断?
解析
当CPU访问的页面不在物理内存中时,MMU(内存管理单元)检测到页表项无效,触发缺页异常(Page Fault)。操作系统中断处理程序:
- 检查虚拟地址合法性(是否在进程地址空间内)。
- 寻找空闲物理页框,若无则按置换算法淘汰一个页面(若脏页需写回磁盘)。
- 从磁盘(交换区或文件)读入所需页面到页框。
- 更新页表,重新执行引起缺页的指令。
举例
- 程序首次访问一个大型数组,数组分布在磁盘上,触发一系列缺页中断,逐步加载到内存。
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),分析堆转储(jmapdump后用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的区别?
解析
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(确认重传、排序、去重) | 不可靠,可能丢失、乱序 |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有 | 无 |
| 首部大小 | 20-60字节 | 8字节 |
| 应用场景 | 文件传输、HTTP、邮件 | 视频流、DNS、游戏 |
举例
- 视频通话使用UDP容忍少量丢包,但要求低延迟;下载文件用TCP保证完整正确。
37. 三次握手过程?为什么是三次?
解析
过程:
- 客户端→服务端:SYN=1, seq=x(客户端进入SYN_SENT)
- 服务端→客户端:SYN=1, ACK=1, seq=y, ack=x+1(服务端进入SYN_RCVD)
- 客户端→服务端:ACK=1, seq=x+1, ack=y+1(客户端进入ESTABLISHED,服务端收到后也进入ESTABLISHED)
为什么三次:
- 确保双方收发能力正常。
- 同步初始序列号(ISN),防止历史连接干扰。两次握手可能因延迟的旧SYN导致错误连接。
- 三次可让双方确认自己的发送和接收没问题。
举例
- 若两次握手:客户端发送SYN后,服务器就建立连接,但客户端可能未收到确认(网络问题),服务器却一直等待数据,浪费资源。
38. 四次挥手过程?为什么是四次?
解析
过程:
- 主动关闭方→被动方:FIN=1, seq=u(主动方进入FIN_WAIT_1)
- 被动方→主动方:ACK=1, seq=v, ack=u+1(被动方进入CLOSE_WAIT,主动方进入FIN_WAIT_2)
- 被动方→主动方:FIN=1, seq=w, ack=u+1(被动方进入LAST_ACK)
- 主动方→被动方: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原因:
- 保证主动关闭方最后的ACK能到达被动方。若ACK丢失,被动方会超时重发FIN,主动方在2MSL内可收到重发的FIN,再次ACK。
- 让本连接的所有旧报文在网络中消失,防止影响后续使用相同四元组的新连接。
举例
- 客户端主动关闭连接,进入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是流协议,无消息边界,发送方多个写操作可能合并成一个包(粘包),或一个大写操作被拆分成多个包(拆包)。
解决方案:
- 固定长度:每个消息定长,不足补零。
- 分隔符:如
\r\n,但需转义。 - 长度字段:消息头包含消息长度。
举例
- 应用层协议如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工作原理?
解析
- 客户端发起HTTPS请求(默认443)。
- 服务端返回数字证书(含公钥)。
- 客户端验证证书(是否可信、是否过期、域名匹配)。
- 验证通过后,客户端生成随机对称密钥,用证书公钥加密发送给服务端。
- 服务端用私钥解密得到对称密钥。
- 后续通信使用对称密钥加密,保证机密性;同时通过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到页面显示全过程?
解析
- URL解析:判断是搜索关键字还是URL,补全协议。
- DNS解析:查找域名对应的IP,从浏览器缓存、OS缓存、hosts、本地DNS服务器、根DNS等递归/迭代查询。
- TCP连接:若为HTTPS,先TCP三次握手,然后TLS握手(若HTTPS)。
- 发送HTTP请求:构造请求行、请求头、请求体,发送到服务器。
- 服务器处理:返回HTTP响应。
- 浏览器解析:解析HTML生成DOM树,解析CSS生成CSSOM树,执行JS。
- 布局与绘制:根据DOM和CSSOM计算布局,绘制页面。
- 连接关闭:若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()方法,Dog和Cat分别重写,调用时根据实际类型发出不同叫声。
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方法流程?
解析
- 计算key的hash值:
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)。 - 根据hash计算索引:
(n - 1) & hash。 - 若table[i]为空,直接插入。
- 若不为空,判断key是否相等(==或equals),若相等则替换value。
- 否则判断节点类型:若为树节点,调用红黑树插入;若为链表,遍历链表,若找到相同key则替换,否则在链表尾部插入。
- 插入后若链表长度≥8,且数组长度<64则扩容,否则树化。
- 若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. 线程池工作原理?
解析
- 提交任务时,若线程数 < corePoolSize,创建新线程执行。
- 若 >= corePoolSize,任务放入workQueue。
- 若队列满,且线程数 < maximumPoolSize,创建新线程执行。
- 若队列满且线程数已达maximum,执行拒绝策略。
- 线程空闲超过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的生命周期?
解析
- 实例化:通过构造器或工厂创建Bean实例。
- 属性赋值:注入依赖(如
@Autowired)。 - 初始化:
- 调用
BeanNameAware、BeanFactoryAware等Aware接口。 - 执行
BeanPostProcessor的postProcessBeforeInitialization。 - 调用
@PostConstruct或InitializingBean的afterPropertiesSet。 - 执行
BeanPostProcessor的postProcessAfterInitialization。
- 调用
- 使用:Bean就绪。
- 销毁:容器关闭时,调用
@PreDestroy或DisposableBean的destroy。
举例
- 自定义Bean实现
InitializingBean和DisposableBean可看到生命周期回调。
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。
- 实例化A,放入三级缓存。
- A注入B,发现B未创建,去创建B。
- 实例化B,从三级缓存拿到A的ObjectFactory,获取早期引用(若A需要代理,在此生成代理),放入二级缓存。
- B完成注入,放入一级缓存。
- 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启动流程?
解析
- 运行
SpringApplication.run(主类, args)。 - 创建
SpringApplication实例,设置应用类型(Web/非Web),加载初始化器(ApplicationContextInitializer)和监听器(ApplicationListener)。 - 调用
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-jetty或undertow引入。 - 容器配置可通过
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工作流程?
解析
- 用户请求到达
DispatcherServlet(前端控制器)。 DispatcherServlet根据请求路径查询HandlerMapping,找到对应的HandlerExecutionChain(含拦截器)。- 执行拦截器前置方法,调用
HandlerAdapter处理请求(执行Controller方法)。 - Controller返回
ModelAndView。 - 若返回逻辑视图名,
ViewResolver解析为具体视图(如JSP)。 - 视图渲染,返回响应。
举例
- 请求
/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+题,每题均深入解析并附实例,可作为面试复习的全面指南。祝面试顺利!