Java后端面试
面经
2025.03.19 小红书日常一面
- IO和NIO的区别
- JVM内存模型,堆里面具体有哪些东西
- 手写监听者模式/单例模式
- ThreadLocal实现原理,并发安全吗,map里面放什么东西,内存泄露如何排查
- get请求里面放什么,参数如何区分
- 讲一下泛型,泛型类的字节码文件是怎样的。
- Spring 注解,RequestBody什么用处
- ArrayList如何扩容
- 虚拟机栈有多大?一个栈帧有多大?
- SQL的UPDATE语句的准确写法
2025.03.20 SAP日常二面
- two sum, three sum
- 缓存空值有问题,应选择布隆过滤器
- 限流
- Zookeeper 了解吗(因为我讲 Redisson 的时候提到了)
- 如果用户名+密码登录,如何保证数据安全。如何加密?RSA?
- 点评项目中,点赞数量太多怎么办。(大 key问题)
2025.03.20 阿里云暑期一面
- 前缀树:输入法提示
- Redis内部有锁吗
- 讲讲SETNX
- sorted set的底层实现
- RDB,AOF的优缺点
- 分布式锁
- 登录验证,为什么要把token存在redis里面,还有更好的方法吗?了解Oauth2吗?
- ThreadLocal 使用的时候需要注意什么问题?
- 是先拿锁,再判断库存;还是先在 lua 脚本里面判断库存,再拿锁
- 有对商户列表做缓存吗?如何解决按照不同的条件排序还能做到缓存?
2025.03.21 PayPal上海 full stack 暑期实习一面
- 合并排序数组
- 数字符串里面一共有多少个 palindrome
2025.03.22 月之暗面搜推实习一面
- 协程
- 讲讲go reduce这个项目的细节
- 手写单例模式,除了双重检验锁,还有什么别的方法可以实现吗?懒汉/饿汉。为什么加 volatile,禁止指令重排序,开辟空间,(实例化,分配给该对象)。
- Redis的数据结构,为什么不用string而是用hash,我说少一步序列化操作
2025.03.24 SAP日常二面
- 为什么用Kafka?优点;用的是Confluent Kafka吗?
- 讲讲对分布式锁的理解,项目代码里面如何实现?我回答MultiLock.
2025.03.25 字节暑期一面
- 如果某条 SQL 执行太慢,怎么排查,用过什么工具。联表查询:小表驱动大表。
- EXPLAIN 会出现一个表格,表格最后有一个 extra column,请介绍
- 方法区里面有什么?
- 讲一讲 G1
- 为什么需要四次挥手?client 最后为什么要 wait?
- 讲一讲建立HTTPS连接的过程
- 手撕 48 和 3
- MySQL主从复制讲一下
- 讲一下Spring的AOP,并且除了动态代理的实现,还有什么实现方法?ASM 字节码操作。
2025.03.26 PayPal暑期二面
- 手写快排
- 为啥要把用户信息存到ThreadLocal里面去
- filter和interceptor的区别
- interceptor 和 AOP的区别
2025.03.27 字节暑期二面
- 合并 k个有序数组
- 讲讲 redo log
- 讲讲MySQL锁
2025.03.27 初创团队实习
- 有用过AWS实现多个服务之间通信吗?
2025.04.01 淘天暑期一面
- 讲SpringMVC如何处理请求
- 联合索引最左匹配
- 线程池
面试准备
git
git merge:适合团队协作
将两个分支的提交历史合并,生成一个新的合并提交(merge commit),保留原始分支的完整历史(包括分叉和合并的痕迹)。
|
|
如果遇到冲突:
|
|
git rebase
适合于自己操作, 主要作用是保持线性的project history。
|
|
如果有冲突
|
|
到这一步,此时是这样的
|
|
最后合并到主分支
|
|
执行完这一步之后
|
|
(main 和 feature-branch 现在指向同一个提交 D’)
密码加密
用户输入用户名 → 前端请求服务端获取动态 Salt。
服务端生成动态 Salt → 随机生成(如 16 字节),存入 Redis/Session(5 分钟过期),返回给前端。
前端计算哈希 → 拼接 密码 + 动态 Salt,用 SHA-256/PBKDF2 哈希,得到 clientHash。
发送登录请求 → 提交 username + clientHash + sessionId(HTTPS 传输)。
服务端验证 → 根据 sessionId 取出 dynamicSalt,结合数据库中的 bcrypt 哈希 计算预期哈希,对比 clientHash。
登录成功/失败 → 立即废弃 dynamicSalt,防止重放攻击。
数据库分库、分表
token 登陆验证
Token 自包含性:Token(如 JWT)本身携带了用户的身份信息(如用户ID、权限等),服务端无需在内存或数据库中保存会话状态。
服务端不存储会话:服务端仅需验证 Token 的合法性(如签名、有效期等),无需维护用户的登录状态。
每次请求独立:客户端每次请求需主动携带 Token,服务端通过解析 Token 直接获取用户信息,不依赖之前的请求上下文。
- 与有状态(Stateful)的对比 有状态示例:传统的 Session 机制,服务端需在内存或数据库中存储 Session ID 和用户数据的映射关系。
无状态优势:避免了服务端的存储开销,更适合分布式系统,容易横向扩展。
- 注意事项 Token 的无效化:无状态方案中,若需强制注销 Token(如用户退出登录),需额外设计机制(如短有效期 + 黑名单或使用 Refresh Token)。
安全性:Token 需通过加密签名(如 JWT 的 HMAC 或 RSA)防止篡改,且应通过 HTTPS 传输。
HTTPS
- 客户端发起 HTTPS 请求. 包含:
支持的加密算法(如 AES-256-GCM),支持的加密协议版本(如TLS1.2)
随机数 Client Random。
- 服务器响应.
选定的加密算法(如 ECDHE-RSA-AES256-GCM-SHA384)。
SSL 证书(含公钥 + 域名信息)。
随机数 Server Random。
- 客户端验证证书 检查证书是否:
由可信 CA(如 Let’s Encrypt) 签发。
域名匹配且未过期。
- 密钥交换(ECDHE)
客户端生成 Pre-Master Secret,用服务器公钥加密后发送。
双方通过 Client Random + Server Random + Pre-Master Secret 生成会话密钥(Session Key)。
客户端生成 finished 报文,用会话密钥加密生成摘要,发给服务器。
- 第四次握手
- 服务器拿私钥解密Pre-Master Secret,然后生成会话密钥;接着生成 finished 报文,用会话密钥加密,发给客户端。
- 对称加密
- 后续所有数据用 AES-256 对称加密传输,密钥仅本次会话有效。
ThreadLocal不适用于分布式系统
在分布式架构中,用户的请求可能被负载均衡到 不同的服务器节点(不同的 JVM 进程)。
即使用户登录后,在 Server A 的 ThreadLocal 中存储了用户信息,但下一次请求可能被路由到 Server B,导致 ThreadLocal 取不到数据。
自增的时候 Redis 宕机:本地缓存/集群多个写节点/服务降级(直接生成 UUID)
- RefreshTokenInterceptor
- LogInterceptor
操作系统内核态和用户态的区别、切换时会切换哪些资源。
程序计数器(PC,存放下一条要执行指令的地址)、通用寄存器(存储临时数据)
虚拟内存原理
虚拟内存是计算机系统内存管理的一种技术,它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
地址空间划分
操作系统为每个进程分配一个独立的虚拟地址空间,这个空间通常比计算机实际的物理内存大。虚拟地址空间被划分为多个页面,页面大小通常是固定的,常见的有 4KB 或 8KB 等。同样,物理内存也被划分为与虚拟页面大小相同的物理页面,也称为页框。
页表机制
为了实现虚拟地址到物理地址的映射,操作系统使用页表来记录这种映射关系。每个进程都有一个对应的页表,页表中的每一项记录了一个虚拟页面与物理页面的映射关系,包括物理页面的地址、页面的访问权限(如可读、可写、可执行等)以及一些状态位(如页面是否在内存中、是否被修改过等)。
当进程访问一个虚拟地址时,硬件会自动根据虚拟地址中的页号在页表中查找对应的物理页号,然后将虚拟地址中的页内偏移量与物理页号组合起来,形成实际的物理地址,从而实现对内存的访问。
缺页中断
如果进程访问的虚拟页面不在物理内存中(即页表中对应的页面状态位表示该页面不在内存),则会发生缺页中断。此时,操作系统会暂停当前进程的执行,将所需的页面从磁盘中读取到物理内存中,并更新页表中的映射关系和状态位。然后,操作系统会重新执行引发缺页中断的指令,使得进程能够继续访问所需的数据。
页面置换算法
当物理内存已满,而又需要加载新的页面时,操作系统需要选择一个已在内存中的页面将其置换到磁盘上,以便为新页面腾出空间。常见的页面置换算法有先进先出(FIFO)算法、最近最少使用(LRU)算法、最不经常使用(LFU)算法等。
- FIFO 算法:选择最早进入内存的页面进行置换,它的优点是实现简单,但可能会把一些经常使用的页面置换出去,导致性能下降。
- LRU 算法:选择最近一段时间内最久未使用的页面进行置换,它基于局部性原理,认为最近使用过的页面在未来一段时间内也可能会被频繁使用,因此具有较好的性能。
- LFU 算法:选择访问次数最少的页面进行置换,它通过记录页面的访问次数来判断页面的使用频率,但是实现相对复杂,并且可能会因为早期的访问次数过多而保留一些不再使用的页面。
Go协程
Go 的协程(Goroutine)说白了就是 Go 语言里的 “轻量级线程”,它和传统线程最大的区别是超级省资源—— 创建一个协程只需要几 KB 内存(线程要几 MB),而且调度由 Go 运行时(Goroutine 调度器)在用户态完成,不用像线程那样频繁陷入内核,所以能轻松创建上万个协程。
比如我启动 1000 个协程并发处理网络请求,每个协程就像一个独立的任务小兵,Go 会自动帮我把它们分配到 CPU 核心上跑,遇到 I/O 阻塞时还会智能切换,特别适合高并发场景。
将大量的 Goroutine(N)动态映射到少量的 OS 线程(M)上执行,由 Go 运行时(Runtime)负责调度,而非操作系统内核。
GIL
GIL(全局解释器锁)是什么?
GIL(Global Interpreter Lock) 是 Python 解释器(特别是 CPython)中的一个机制,它确保 同一时刻只有一个线程执行 Python 字节码。
为什么存在?
Python 的内存管理(如垃圾回收)不是线程安全的,GIL 可以防止多线程竞争导致的数据混乱。
影响:
多线程在 CPU 密集型任务中无法并行(因为 GIL 会串行化线程执行)。
I/O 密集型任务不受太大影响(因为线程在等待 I/O 时会释放 GIL)。
如何绕过?
用 多进程(multiprocessing) 或 协程(asyncio) 替代多线程。