加群联系作者vx:xiaoda0423
仓库地址:https://webvueblog.github.io/JavaPlusDoc/
https://1024bat.cn/
https://github.com/webVueBlog/fastapi_plus
https://webvueblog.github.io/JavaPlusDoc/
点击勘误issues,哪吒感谢大家的阅读
1. 四种事务隔离级别
隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-repeatable Read) | 幻读 (Phantom Read) | MySQL 默认 |
---|---|---|---|---|
READ UNCOMMITTED (读未提交) | ✅ 可能发生 | ✅ 可能发生 | ✅ 可能发生 | 否 |
READ COMMITTED (读已提交) | ❌ 阻止 | ✅ 可能发生 | ✅ 可能发生 | 否(Oracle 默认) |
REPEATABLE READ (可重复读) | ❌ 阻止 | ❌ 阻止 | ✅*(InnoDB 下几乎阻止) | ✅(MySQL 默认) |
SERIALIZABLE (可串行化) | ❌ 阻止 | ❌ 阻止 | ❌ 阻止 | 否 |
✅ 表示可能发生,❌ 表示可以避免
InnoDB 默认通过 间隙锁(Gap Lock) 解决了大部分幻读
2. 关键概念
脏读:读取到另一个事务未提交的数据
不可重复读:同一事务中两次读同一行数据,结果不一致(因为被其他事务修改)
幻读:同一事务中两次相同条件查询,结果行数不同(因为其他事务插入/删除了符合条件的行)
3. 配置事务隔离级别
查询当前隔离级别
SELECT @@transaction_isolation;
设置隔离级别(会话级)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
设置隔离级别(全局)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
4. MySQL InnoDB 特点
默认隔离级别:REPEATABLE READ
为了避免幻读,InnoDB 在可重复读下使用 Next-Key Lock(行锁 + 间隙锁)
MVCC(多版本并发控制)在 READ COMMITTED 和 REPEATABLE READ 下生效
5. 选择建议
READ UNCOMMITTED:几乎不用,数据不安全
READ COMMITTED:常用于高并发、能容忍不可重复读(Oracle 默认)
REPEATABLE READ:MySQL 默认,平衡性能和一致性
SERIALIZABLE:最高安全,但性能差,基本不用在高并发
synchronized
不是乐观锁,它是悲观锁。
我给你分三部分说明:
1. 悲观锁 vs 乐观锁
特性 | 悲观锁( | 乐观锁(CAS、版本号机制) |
---|---|---|
加锁方式 | 操作前直接加锁,假设会发生冲突 | 操作时不加锁,先尝试更新,失败再重试 |
开销 | 阻塞线程,需要上下文切换 | 自旋重试,无阻塞(但可能CPU消耗高) |
实现方式 | synchronized 、 | AtomicInteger 、CAS、自增版本号字段 |
适合场景 | 并发高、冲突概率大 | 冲突少、读多写少 |
2. synchronized
是悲观锁
public synchronized void increment() {
count++;
}
JVM 会在进入
increment()
方法时直接加锁(monitorenter 指令),其他线程必须等锁释放才能执行。这是悲观策略:假设每次都会冲突,所以先锁住资源。
3. 乐观锁实现(Java CAS)
Java 里常见的乐观锁实现是 CAS(Compare And Swap) :
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
int oldValue, newValue;
do {
oldValue = count.get();
newValue = oldValue + 1;
} while (!count.compareAndSet(oldValue, newValue));
}
}
compareAndSet:比较当前值是否和预期值相等,如果相等就更新,否则重试。
无需加锁,适合高并发读多写少场景。
1. Feign 是什么
声明式 HTTP 客户端:只需要定义 Java 接口 + 注解,就能发 HTTP 请求。
集成了 Ribbon(负载均衡)和 Hystrix(容错降级,旧版),新版可配合 Spring Cloud LoadBalancer 和 Sentinel。
在 Spring Cloud 中用于服务间调用,替代 RestTemplate 手写 URL。
2. Feign 的优势
调用简单:不需要手写
RestTemplate.getForObject()
,直接调用接口方法。可读性好:调用方看起来像本地方法调用。
支持负载均衡:配合注册中心自动选择服务实例。
可扩展:可自定义请求拦截器、编码器、解码器、日志等。
3. 快速上手
3.1 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
3.2 启用 Feign
@SpringBootApplication
@EnableFeignClients
public class MyApp {}
3.3 定义 Feign 接口
@FeignClient(name = "order-service") // name = 服务在注册中心的名字
public interface OrderClient {
@GetMapping("/orders/{id}")
Order getOrder(@PathVariable("id") Long id);
}
3.4 调用接口
@Service
public class UserService {
@Autowired
private OrderClient orderClient;
public void showOrder(Long id) {
Order order = orderClient.getOrder(id);
System.out.println(order);
}
}
注意:
name
必须与注册中心(Eureka / Nacos)里的服务名一致,且要启用负载均衡(默认开启)。
4. 常用配置
4.1 开启 Feign 日志
logging:
level:
com.example.client.OrderClient: DEBUG
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; // NONE, BASIC, HEADERS, FULL
}
4.2 配置超时
feign:
client:
config:
default: # default 表示所有 Feign 客户端
connectTimeout: 5000
readTimeout: 10000
4.3 请求拦截器(统一加 Token)
@Component
public class FeignAuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("Authorization", "Bearer xxx");
}
}
4.4 降级处理(Fallback)
@FeignClient(name = "order-service", fallback = OrderClientFallback.class)
public interface OrderClient { ... }
@Component
public class OrderClientFallback implements OrderClient {
@Override
public Order getOrder(Long id) {
return new Order(-1L, "服务不可用");
}
}
5. 使用建议
小项目可以直接用 Feign 默认配置,大项目要结合 Sentinel 做限流熔断。
对高并发调用,可以开启 Feign + HTTP/2 + 连接池(默认 OkHttp 支持)。
返回值最好定义统一响应结构,方便做错误处理。
尽量避免在 Feign 接口里写复杂逻辑,保持“纯接口”风格。
1. Spring Cloud 是什么
微服务全家桶:基于 Spring Boot,提供一套分布式系统基础设施。
目标:简化微服务架构下的服务注册、配置管理、负载均衡、服务调用、网关、安全、链路追踪、容错限流等复杂功能。
核心理念:关注业务逻辑,公共能力交给 Spring Cloud 组件。
2. 核心功能
功能 | 作用 | 常用组件 |
---|---|---|
服务注册与发现 | 让微服务能自动注册、互相调用 | Eureka 、Consul、Nacos |
负载均衡 | 多实例服务间流量分配 | Ribbon (已过时)、Spring Cloud LoadBalancer |
服务调用 | 微服务间 HTTP 调用封装 | OpenFeign |
配置中心 | 动态配置管理 | Spring Cloud Config 、Nacos |
网关 | 请求路由、鉴权、限流 | Spring Cloud Gateway |
容错限流 | 熔断、降级、隔离、限流 | Hystrix (过时)、Sentinel、Resilience4j |
消息驱动 | 事件驱动通信 | Spring Cloud Stream (Kafka/RabbitMQ) |
链路追踪 | 分布式调用跟踪 | Sleuth + Zipkin |
分布式任务 | 定时任务调度 | Spring Cloud Task 、xxl-job(第三方) |
3. Spring Cloud 常用架构图
┌──────────┐
│ Gateway │ ← 统一入口(鉴权/限流/路由)
└─────┬────┘
↓
┌────────────────────────┐
│ 服务注册中心 (Eureka) │ ← 服务实例注册/发现
└─────┬──────────────────┘
↓
┌───────────────┐ ┌───────────────┐
│ user-service │ │ order-service │ ← 微服务业务模块
└─────┬──────────┘ └─────┬────────┘
↓ ↓
┌───────────────┐ ┌───────────────┐
│ Config Server │ │ Message Broker│
│ (动态配置) │ │ (Kafka/Rabbit)│
└────────────────┘ └───────────────┘
4. 典型微服务调用流程
服务启动 → 注册到注册中心(Eureka/Nacos)。
其他服务通过注册中心获取地址列表。
调用时由 LoadBalancer 选择实例(轮询/随机/权重等)。
通过 Feign 发起请求,结果返回。
配置中心可动态推送配置信息到服务。
链路追踪记录调用路径和耗时。
5. 常用组件快速上手
5.1 Eureka 服务注册与发现
<!-- 服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApp {}
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
5.2 OpenFeign 服务调用
@FeignClient("order-service")
public interface OrderClient {
@GetMapping("/orders/{id}")
Order getOrder(@PathVariable Long id);
}
5.3 Gateway 网关
spring:
cloud:
gateway:
routes:
- id: user_route
uri: lb://user-service
predicates:
- Path=/users/**
6. 版本体系
Spring Cloud 依赖 Spring Boot,版本需要匹配:
Hoxton → Spring Boot 2.2/2.3
2021.x → Spring Boot 2.6+
2022.x → Spring Boot 3.x(Jakarta EE 迁移)
在
pom.xml
用 Spring Cloud BOM 管理版本:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2022.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
1. TTL 是什么
定义:Key 在 Redis 中还能存活的时间(秒或毫秒)。
到期后:
Key 会被自动删除(实际是惰性+定期删除结合)。
再访问会返回
nil
(不存在)。
用于缓存、会话、限时任务等场景。
2. 相关命令
命令 | 作用 | 返回值 |
---|---|---|
EXPIRE key seconds | 设置过期时间(秒) | 1=成功,0=key不存在或不能设置 |
PEXPIRE key ms | 设置过期时间(毫秒) | 同上 |
TTL key | 查看剩余过期时间(秒) | -1=永不过期,-2=已过期或不存在 |
PTTL key | 查看剩余过期时间(毫秒) | 同上 |
EXPIREAT key timestamp | 指定时间戳(秒)过期 | 同上 |
PEXPIREAT key ms_timestamp | 毫秒时间戳 | 同上 |
SET key value EX 60 | 创建 key 并设置 TTL(秒) | OK |
SET key value PX 60000 | 创建 key 并设置 TTL(毫秒) | OK |
PERSIST key | 移除 TTL(变为永久) | 1=成功,0=失败 |
3. 使用示例
# 设置过期时间
SET session:123 "Tom" EX 300 # 300秒后过期
EXPIRE product:1001 60 # 单独设置TTL
# 查看TTL
TTL session:123
# 毫秒精度
PEXPIRE tempkey 1500 # 1.5秒后过期
PTTL tempkey
# 移除TTL
PERSIST session:123
4. Redis 过期删除机制
Redis 不会精确到期时立刻删除 key,而是结合三种策略:
定时删除(主动扫描部分 key,减少内存占用)。
惰性删除(访问时才检查 TTL,过期就删除)。
淘汰策略(内存满时按策略淘汰)。
5. 常见应用
缓存数据:防止数据长期占内存。
会话管理:用户 token 自动过期。
分布式锁:
SET key value NX EX 30
。限时业务:秒杀、优惠券有效期。
防缓存雪崩:给 TTL 加随机值。
6. 注意事项
更新 key 会保留 TTL(如
SET
覆盖会重置 TTL,HSET
修改 hash 字段不会影响 TTL)。TTL 不保证精确到毫秒删除,删除时间取决于惰性检查/定期扫描。
批量 key 统一过期容易造成缓存雪崩 → 加随机 TTL。
持久化时 TTL 会一并保存,重启恢复后继续生效。
1. 为什么用 Redis 做缓存
速度快:内存存储,单线程事件驱动,延迟通常 < 1ms。
支持丰富数据结构:不仅能存字符串,还能存对象、集合、排序集等。
支持过期策略:TTL、LRU/LFU 淘汰,防止内存溢出。
分布式可用:可配合主从、哨兵、集群,支持高并发。
2. 缓存使用基本思路(Cache Aside Pattern)
读流程:
1. 先从 Redis 缓存查
2. 如果命中,直接返回
3. 如果未命中,从数据库查 → 回填 Redis(加过期时间)
写流程:
1. 更新数据库
2. 删除或更新 Redis 缓存
✅ 优点:保证数据库为最终权威数据
⚠️ 缺点:并发高时可能出现缓存击穿、雪崩、穿透问题(后面会讲解决方案)
3. Spring Boot 使用 Redis 缓存(示例)
3.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.2 配置 application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 2000
lettuce:
pool:
max-active: 8
max-idle: 4
min-idle: 1
3.3 代码示例
@Service
public class UserService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private UserMapper userMapper;
public User getUserById(Long id) {
String key = "user:" + id;
String json = redisTemplate.opsForValue().get(key);
if (json != null) {
return JSON.parseObject(json, User.class);
}
User user = userMapper.selectById(id);
if (user != null) {
redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 10, TimeUnit.MINUTES);
}
return user;
}
public void updateUser(User user) {
userMapper.updateById(user);
redisTemplate.delete("user:" + user.getId());
}
}
4. 缓存常见问题与解决方案
问题 | 场景 | 解决方案 |
---|---|---|
缓存穿透 | 查询不存在的 key | 缓存空值(短 TTL)+ 布隆过滤器 |
缓存击穿 | 热点 key 过期瞬间大量请求涌入 | 设置热点 key 永不过期 + 后台异步更新;或加互斥锁 |
缓存雪崩 | 大量 key 同时过期 | 给 TTL 加随机值,错开过期时间;多层缓存 |
数据不一致 | 更新数据库后缓存没同步 | 写操作优先更新数据库,然后删除缓存(避免脏数据) |
5. 进阶优化
序列化优化
默认 JDK 序列化占用多,可用 JSON(Fastjson / Jackson)或 Kryo。
批量操作
用
pipeline
一次发送多条命令减少 RTT。
热点数据预热
系统启动时提前加载常用数据进缓存。
分布式锁
用
SET key value NX EX
或 Redisson 实现互斥更新。
监控
INFO
查看命中率、内存占用,结合 Prometheus+Grafana 可视化。
6. 简易缓存流程图
┌───────────┐ │ 请求数据 │ └─────┬─────┘ ↓ ┌─────────────┐ │ 查 Redis 缓存 │───命中──▶ 返回数据 └─────┬───────┘ ↓未命中 ┌─────────────┐ │ 查数据库 │ └─────┬───────┘ ↓ 回填 Redis 缓存(TTL) ↓ 返回数据
1. Redis 是什么
Remote Dictionary Server:基于内存的高性能 Key-Value 数据库。
主要特性:
内存存储(可持久化到磁盘)
数据结构丰富
单线程事件驱动(命令原子执行)
支持主从、哨兵、高可用、集群
常用于缓存、分布式锁、消息队列、排行榜等
2. 常用数据类型
类型
命令示例
使用场景
String (字符串/二进制)
SET key val
/
GET key
缓存对象、计数器
List (双端链表)
LPUSH key val
/
RPOP key
消息队列、时间线
Set (无序去重集合)
SADD key val
/
SISMEMBER key val
标签、去重
Hash (哈希表)
HSET user:1 name Tom
/
HGET user:1 name
对象存储
ZSet (有序集合)
ZADD rank 100 Tom
/
ZRANGE rank 0 10
排行榜、延时任务
Bitmap SETBIT key offset 1
/
GETBIT key offset
签到、活跃用户
HyperLogLog PFADD key val
/
PFCOUNT key
去重计数(近似)
Geo GEOADD city 116.4 39.9 Beijing
地理位置搜索
3. 持久化机制
RDB(快照)
定期将内存数据写入磁盘(
.rdb
文件)。优点:体积小,恢复快。
缺点:可能丢失最近数据。
配置:
save 900 1 # 900秒内有1次写操作就触发快照 save 300 10
AOF(追加文件)
每次写操作追加到
.aof
文件。优点:数据丢失更少(可配置每秒/每次写入刷盘)。
缺点:文件体积大,恢复慢。
常用策略:RDB + AOF 混合持久化(Redis 4.0+)。
4. 过期与淘汰策略
过期策略:
定时删除(定期扫描)
惰性删除(访问时检查)
淘汰策略(
maxmemory-policy
):volatile-lru
:从设置过期时间的 key 中挑 LRUallkeys-lru
:所有 key 中挑 LRUvolatile-ttl
:按 TTL 近过期时间noeviction
:内存满直接报错(默认)
5. 主从复制与高可用
主从复制(Master-Slave)
从节点实时复制主节点数据
读写分离
哨兵(Sentinel)
监控主从节点
主节点故障自动切换
集群(Cluster)
分片存储(哈希槽 0~16383)
自动故障转移
6. 常用实战场景
缓存(页面缓存、热点数据缓存)
分布式锁(
SET key val NX EX
)限流(
INCR
+ 过期时间)延时队列(ZSet + 时间戳)
排行榜(ZSet)
会话管理(String/Hash + TTL)
7. 性能与调优
减少大 key(避免一次性取超大数据)
批量命令(
MGET
/MSET
/ pipeline)合理 TTL(防止内存爆炸)
使用连接池(减少频繁建立 TCP 连接开销)
监控(
INFO
、slowlog
、redis-cli monitor
)
8. 常用命令速查
SET key value EX 60 NX # 设置过期时间 & 仅在不存在时设置 GET key DEL key EXPIRE key 60 INCR key HSET user:1 name Tom age 20 LPUSH queue a b c RPOP queue ZADD rank 100 Tom ZRANGE rank 0 -1 WITHSCORES
1. 老年代是什么
属于 Java 堆(Heap) 的一部分,线程共享。
存放 生命周期较长 或 体积较大 的对象。
与 新生代(Young Generation) 配合使用,形成 JVM 分代内存结构。
在 Minor GC 时通常不参与回收,但在 Major GC / Full GC 时会被回收。
2. 对象进入老年代的条件
常见有 4 种情况:
对象年龄达到阈值
对象在 Survivor 区经过多次 Minor GC 仍存活,年龄(Age)增加。
达到
-XX:MaxTenuringThreshold
(默认 15)就会晋升到老年代。
大对象直接进入老年代
如果对象过大(如大数组、长字符串),可能跳过新生代直接放入老年代。
控制参数:
-XX:PretenureSizeThreshold
(仅对 Serial/ParNew 有效)。
动态年龄判断
Survivor 区对象年龄总大小超过 Survivor 区的一半时,比这个年龄大的对象直接进入老年代。
新生代空间不足
Minor GC 后仍放不下存活对象,多余部分直接晋升老年代。
3. 老年代的 GC 特点
对象存活率高,回收频率低。
采用 标记-整理(Mark-Compact) 或 标记-清除(Mark-Sweep) 算法:
标记-整理:避免碎片化,但会有对象移动成本。
标记-清除:快,但可能导致内存碎片。
触发回收的情况:
老年代满(分配对象或晋升时触发)。
调用
System.gc()
(可能触发 Full GC)。元空间/永久代不足(间接触发 Full GC)。
4. 常见问题
频繁 Full GC
老年代空间太小。
短命的大对象直接进入老年代。
新生代晋升太快,挤占老年代。
内存碎片
CMS 收集器使用标记-清除,可能产生碎片,导致大对象分配失败 → 提前 Full GC。
GC 停顿长
老年代对象多,回收时要扫描 & 移动大量对象。
5. 调优建议
增大老年代
-Xms
/-Xmx
配合-XX:NewRatio
调整新生代与老年代比例。合理设置晋升阈值
-XX:MaxTenuringThreshold
,延缓晋升,减轻老年代压力。控制大对象创建
避免一次性分配大数组/长字符串,必要时分片处理。减少对象在 Survivor 区的溢出
调整 Survivor 区大小(-XX:SurvivorRatio
)。收集器选择
低延迟:G1、ZGC、Shenandoah。
吞吐量:Parallel GC。
6. GC 日志中的老年代
示例(G1 GC 日志):
[GC pause (G1 Evacuation Pause) (mixed), 0.0456789 secs] [Eden: 512M(512M)->0B(512M) Survivors: 32M->32M Heap: 2G(4G)->1.6G(4G)]
Heap
中的减少量主要来自老年代 & 新生代混合回收。
✅ 总结口诀
新生代放不下 → 晋升老年代
老年代满 → Full GC
晋升条件:年龄阈值 / 大对象 / 动态年龄 / Survivor 溢出1. 垃圾回收的目标
释放内存:自动回收不再被引用的对象占用的堆空间。
保证可达对象可用:只清理不可达的对象。
减少停顿:平衡吞吐量与延迟。
2. GC 主要管理的区域
堆(Heap) :GC 核心关注区
存活时间长的大对象、晋升对象
Eden 区(对象初始分配)
Survivor 0 / Survivor 1 区(对象晋升前的中转)
新生代(Young Generation)
老年代(Old Generation)
方法区(Method Area / 元空间 Metaspace) :类元数据、常量等(部分 GC 会回收)。
3. 判断对象是否可回收
3.1 引用计数法(Reference Counting)
每个对象维护引用计数,计数为 0 时可回收。
缺点:循环引用无法回收 → Java 不采用。
3.2 可达性分析(Reachability Analysis)✅
从 GC Roots(栈、本地方法栈引用、静态变量等)出发,沿引用链可达的对象为存活对象,其余为垃圾。
4. 常用 GC 算法
算法
适用区域
原理
优点
缺点
标记-清除 老年代
标记存活对象 → 清除未标记对象
实现简单
碎片多
复制算法 新生代
复制存活对象到另一块区域 → 清空原区
无碎片
需要额外空间
标记-整理 老年代
标记存活对象 → 向一端移动
无碎片
移动对象成本高
分代收集 全堆
新生代复制算法 + 老年代标记整理
各取所长
需分代管理
5. GC 类型
Minor GC
回收新生代(Eden + Survivor)
速度快、停顿短
Major GC / Full GC
回收老年代 + 新生代(Full GC 还可能回收方法区)
停顿时间长
触发原因:老年代满、
System.gc()
、元空间不足等
6. 常见垃圾收集器
6.1 串行收集器(Serial)
单线程 GC,适合单核/小内存
参数:
-XX:+UseSerialGC
6.2 并行收集器(Parallel / 吞吐量优先)
多线程回收,适合批处理
参数:
-XX:+UseParallelGC
6.3 CMS(Concurrent Mark Sweep)
并发标记-清除,减少停顿时间
缺点:内存碎片、浮动垃圾
参数:
-XX:+UseConcMarkSweepGC
(JDK9 之后被标记废弃)
6.4 G1(Garbage First)✅
面向服务端,分区回收,低延迟
参数:
-XX:+UseG1GC
6.5 ZGC / Shenandoah(JDK11+ / JDK12+)
超低延迟(毫秒级停顿)
大堆支持(TB 级)
7. 常用调优参数
-Xms1g -Xmx1g # 堆初始/最大 -XX:NewRatio=2 # 新生代:老年代=1:2 -XX:SurvivorRatio=8 # Eden:Survivor=8:1:1 -XX:+UseG1GC # 使用G1收集器 -XX:MaxGCPauseMillis=200 # 目标最大停顿 -XX:+PrintGCDetails # 输出GC详情 -XX:+PrintGCDateStamps # 输出时间戳
8. GC 调优思路
确认目标:低延迟还是高吞吐?
收集数据:
jstat
/GC logs
/jmap
/jvisualvm
分析瓶颈:频繁 Full GC?老年代占满?
调整参数:堆大小、分代比例、GC 类型
压测验证:确认 GC 次数与停顿符合预期
9. 总结口诀
新生代复制,老年代整理;
GC Roots 做基准,可达即存活;
选对收集器,目标先明确。1. JVM 内存结构总览
Java 虚拟机在运行时会把内存分成几个区域,主要有:
┌───────────────────────────────┐ │ 线程私有(Thread-private) │ │ - 程序计数器(PC Register) │ │ - Java 虚拟机栈(JVM Stack) │ │ - 本地方法栈(Native Stack) │ ├───────────────────────────────┤ │ 线程共享(Thread-shared) │ │ - 堆(Heap) │ │ - 方法区(Method Area,JDK8+ 元空间 Metaspace)│ └───────────────────────────────┘
2. 各区域详细说明
2.1 程序计数器(Program Counter Register)
线程私有
保存当前线程执行的字节码行号指示器(下一条要执行的指令地址)。
如果执行的是本地方法(Native),计数器值为 undefined。
不会发生
OutOfMemoryError
。
2.2 Java 虚拟机栈(JVM Stack)
线程私有
每个方法执行时都会创建一个 栈帧(Stack Frame) :
局部变量表
操作数栈
动态链接
方法出口
深度递归或方法嵌套过多会 StackOverflowError。
如果 JVM 栈可动态扩展,扩展失败会 OutOfMemoryError。
2.3 本地方法栈(Native Method Stack)
与 JVM 栈类似,但为 Native 方法服务(JNI 调用 C/C++)。
可能抛
StackOverflowError
或OutOfMemoryError
。
2.4 堆(Heap)
线程共享
存放所有对象实例和数组,是 垃圾收集(GC) 管理的主要区域。
逻辑上分为:
新生代(Young Generation) - Eden - Survivor 0 / Survivor 1 老年代(Old Generation)
特点:
新生代对象存活时间短,频繁 Minor GC。
老年代对象存活时间长,Major/Full GC。
堆不够会抛
OutOfMemoryError: Java heap space
。
2.5 方法区(Method Area)
线程共享
存储已加载的类信息、常量、静态变量、即时编译后的代码等。
JDK8 之前:在堆的永久代(PermGen)
JDK8 之后:用 元空间(Metaspace) 存储在本地内存中。
常见 OOM:
JDK7 及以前:
OutOfMemoryError: PermGen space
JDK8+:
OutOfMemoryError: Metaspace
3. 常见内存错误类型
错误
原因
StackOverflowError 递归过深 / 方法调用层级过多(栈空间耗尽)
OutOfMemoryError: Java heap space 堆空间不足
OutOfMemoryError: GC overhead limit exceeded GC 占用时间过长但回收效果差
OutOfMemoryError: Metaspace 类元信息占用过多(JDK8+)
OutOfMemoryError: Direct buffer memory NIO 直接内存不足
OutOfMemoryError: unable to create new native thread 系统线程数达到上限
4. JVM 调优常用参数
# 堆设置 -Xms512m # 初始堆 -Xmx1024m # 最大堆 # 新生代 -Xmn256m # 新生代大小 # 元空间(JDK8+) -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m # 栈大小 -Xss1m # 每个线程的栈大小 # GC 日志(JDK9-) -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
5. 总结口诀
三私有:PC 寄存器、JVM 栈、本地方法栈
两共享:堆、方法区(元空间)
GC 主要盯堆,类信息在方法区,递归爆栈找 JVM 栈一、运行期注解 + AOP(Spring 场景最常用)
1) 定义注解
import java.lang.annotation.*; @Documented @Target({ElementType.METHOD}) // 作用在方法(也可 CLASS / FIELD / PARAMETER 等) @Retention(RetentionPolicy.RUNTIME) // 运行期可见:AOP/反射才能拿到 public @interface AuditLog { String value() default ""; boolean saveArgs() default true; }
2) 在业务方法上使用
@Service public class OrderService { @AuditLog(value = "创建订单", saveArgs = true) public String createOrder(String userId, int amount) { // ... 业务逻辑 return "OK"; } }
3) 用 AOP 拦截并处理
import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Arrays; @Slf4j @Aspect @Component public class AuditLogAspect { // 按“注解”为切点:标了 @AuditLog 的方法都会被拦截 @Around("@annotation(AuditLog)") public Object around(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Method method = ((MethodSignature) pjp.getSignature()).getMethod(); AuditLog anno = method.getAnnotation(AuditLog.class); Object ret; Throwable err = null; try { ret = pjp.proceed(); // 执行目标方法 return ret; } catch (Throwable e) { err = e; throw e; } finally { long cost = System.currentTimeMillis() - start; String argsStr = anno.saveArgs() ? Arrays.toString(pjp.getArgs()) : "<hidden>"; log.info("AUDIT | {} | method={} | args={} | cost={}ms | ok={}", anno.value(), method.getName(), argsStr, cost, err == null); // 这里也可以:落库、发MQ、埋点、告警…… } } }
4) 开启 AOP(Spring Boot 通常已开启)
@SpringBootApplication @EnableAspectJAutoProxy(exposeProxy = true) // 可选 public class App {}
到这里,你已经完成了自定义注解 + AOP 织入:谁打了
@AuditLog
,谁就触发你的横切逻辑(日志、鉴权、限流、事务补偿等都可这样做)。二、无 Spring 时的运行期反射处理(轻量示例)
public class Runner { public static void main(String[] args) throws Exception { Method m = OrderService.class.getMethod("createOrder", String.class, int.class); AuditLog anno = m.getAnnotation(AuditLog.class); if (anno != null) { System.out.println("Will audit: " + anno.value()); } // 直接反射调用 OrderService s = new OrderService(); m.invoke(s, "u1", 100); } }
适合简单场景;若要“全局拦截”,仍建议用 AOP 或代理。
三、进阶:常见注解型需求模板
权限校验
@RequireRole("ADMIN")
在 AOP 里先拿到当前用户角色,不满足则抛AccessDeniedException
。分布式锁
@DistributedLock(key="#orderId")
AOP 中解析 SpEL → Redis/Tair 加锁 →proceed()
→ finally 解锁。限流
@RateLimit(qps=100)
AOP 里用令牌桶/滑动窗口,超限直接拒绝或降级。缓存
@Cacheable(key="#id", ttl=300)
AOP:先查缓存 → 命中直接返回;未命中执行方法并写缓存。
以上都只是把“横切关注点”抽象成注解 + AOP 逻辑。
四、编译期注解处理(JSR 269,代码生成/校验)
如果你需要在编译期处理(如生成代码、做静态校验),用注解处理器:
@SupportedAnnotationTypes("com.example.AutoDto") @SupportedSourceVersion(SourceVersion.RELEASE_17) public class AutoDtoProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annos, RoundEnvironment env) { for (Element e : env.getElementsAnnotatedWith(AutoDto.class)) { // 读取元素信息,生成源文件/校验 // processingEnv.getFiler().createSourceFile("..."); } return true; } }
注册到
META-INF/services/javax.annotation.processing.Processor
,编译时自动执行(类似 Lombok、MapStruct 的机制)。五、易踩坑 & 建议
@Retention 必须是 RUNTIME(运行期要读到注解)。
代理限制:Spring AOP 基于代理,final 类/方法、private 方法不会被代理;需要可以用 CGLIB(默认对无接口类用 CGLIB),或换 AspectJ LTW。
自调用失效:同类内部方法互调不会过代理 → 用
AopContext.currentProxy()
或拆分到另一个 Bean。作用范围:
@Target
选对(方法、类、字段、参数)。性能:高频 AOP 里少用重度反射,缓存
Method/Annotation
元数据。顺序:多个切面需要可控顺序 →
@Order
。
在 Spring Bean 生命周期 里,AOP 不是单独的一个阶段,而是通过
BeanPostProcessor
(特别是AbstractAutoProxyCreator
及其子类) 在 初始化阶段之后 把代理逻辑织入的。我给你按顺序标一下 AOP 发生的位置:
1. Bean 生命周期回顾(关键位置标记)
1. 实例化 Bean 2. 属性注入 3. Aware 接口回调 4. BeanPostProcessor.postProcessBeforeInitialization() 5. 初始化方法(afterPropertiesSet / init-method) 6. BeanPostProcessor.postProcessAfterInitialization() ← ★ AOP 在这里织入 7. Bean 就绪可用(可能是代理对象) 8. 销毁阶段
2. 为什么 AOP 在 AfterInitialization
Spring AOP 本质是 JDK 动态代理 / CGLIB 代理。
代理对象必须在 Bean 完成初始化后 才能替换原对象(确保依赖注入和初始化逻辑已经执行)。
postProcessAfterInitialization()
返回的不是原始 Bean,而是一个 代理对象,里面包了目标 Bean 和切面逻辑。
3. 工作流程简述
容器启动时加载所有 Bean 定义。
创建 Bean → 执行依赖注入。
调用
BeanPostProcessor
:
如果是
AbstractAutoProxyCreator
及子类,会检查该 Bean 是否匹配切面(Advisor)。如果匹配,就用 JDK Proxy(接口)或 CGLIB Proxy(类)生成代理。
代理对象放回容器,后续你拿到的就是这个代理对象。
调用方法时,代理会先执行切面逻辑,再调用原方法。
4. 代码证明
@Component public class DemoService { public void hello() { System.out.println("Hello method executed"); } } @Aspect @Component public class LogAspect { @Before("execution(* com.example.DemoService.*(..))") public void before() { System.out.println("Before advice"); } }
启动后:
@Autowired DemoService demoService; @PostConstruct public void test() { System.out.println(demoService.getClass()); // class com.example.DemoService$$EnhancerBySpringCGLIB$$... }
说明 Spring 返回的是代理类,而不是原始类。
✅ 结论
Spring AOP 是通过BeanPostProcessor
在初始化后的后置处理阶段 创建代理对象实现的。
所以 AOP 织入点 在生命周期的postProcessAfterInitialization()
。1. 生命周期总流程(简化版)
实例化(Instantiation)
Spring 通过反射(构造函数/工厂方法)创建 Bean 实例。
属性赋值(Populate Properties)
给 Bean 的属性注入依赖(依赖注入阶段)。
BeanNameAware / BeanFactoryAware / ApplicationContextAware 回调
如果 Bean 实现了这些接口,会被注入对应的上下文信息。
BeanPostProcessor(前置处理)
所有
BeanPostProcessor.postProcessBeforeInitialization()
调用。
初始化(Initialization)
如果实现了
InitializingBean.afterPropertiesSet()
,会调用。如果配置了
init-method
,也会执行。
BeanPostProcessor(后置处理)
调用
BeanPostProcessor.postProcessAfterInitialization()
,可替换或增强 Bean(如 AOP)。
使用阶段
Bean 被应用调用。
销毁(Destruction)
如果实现了
DisposableBean.destroy()
,会调用。如果配置了
destroy-method
,也会执行。
容器关闭时:
2. 生命周期详细阶段(Spring IOC 内部执行顺序)
[1] Bean实例化(Constructor / Factory) [2] 属性注入(Dependency Injection) [3] Aware接口回调(BeanNameAware / BeanFactoryAware / ApplicationContextAware) [4] BeanPostProcessor 前置处理(postProcessBeforeInitialization) [5] 初始化: ├── afterPropertiesSet() (InitializingBean接口) └── 自定义 init-method [6] BeanPostProcessor 后置处理(postProcessAfterInitialization) [7] Bean 可用(被应用调用) [8] 容器关闭: ├── destroy() (DisposableBean接口) └── 自定义 destroy-method
3. 常用扩展点
扩展点
时机
作用
BeanNameAware 属性注入后
获取 Bean 名称
BeanFactoryAware 属性注入后
获取 BeanFactory
ApplicationContextAware 属性注入后
获取 ApplicationContext
BeanPostProcessor 初始化前/后
修改 Bean 或包装代理对象
InitializingBean 初始化阶段
自定义初始化逻辑
DisposableBean 销毁阶段
自定义销毁逻辑
4. 代码示例
@Component public class MyBean implements BeanNameAware, InitializingBean, DisposableBean { @Override public void setBeanName(String name) { System.out.println("BeanNameAware: " + name); } @Override public void afterPropertiesSet() { System.out.println("InitializingBean: 初始化逻辑"); } @Override public void destroy() { System.out.println("DisposableBean: 销毁逻辑"); } }
BeanPostProcessor
示例:@Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("前置处理: " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("后置处理: " + beanName); return bean; } }
5. 总结口诀
实例化 → 注入属性 → Aware回调 → 前置处理 → 初始化 → 后置处理 → 使用 → 销毁
Spring 在 Java 领域里指的是 Spring Framework,它是一个开源的轻量级 Java 企业级开发框架,最初是为了解决 企业级开发中过度复杂的配置与依赖管理 问题,现在已经发展成一个完整的生态体系(Spring Boot、Spring Cloud、Spring Data 等)。
我帮你分成 核心概念 → 核心模块 → 工作原理 → 常用子项目 → 学习建议 来讲。
1. 核心概念
IoC(控制反转)
把对象的创建与依赖管理交给 Spring 容器,而不是自己
new
。常用方式:XML 配置、Java 注解(
@Component
、@Autowired
)、Java Config。
DI(依赖注入)
IoC 的实现方式,通过构造器注入、Setter 注入或字段注入,把依赖自动注入到类中。
AOP(面向切面编程)
把通用功能(如日志、事务、安全、缓存)从业务逻辑中分离,通过切面统一织入。
声明式事务
通过
@Transactional
注解实现事务管理,不必手写begin/commit/rollback
。
模块化
核心容器、数据访问、Web、AOP、消息、测试等模块可以按需引入。
2. 核心模块
模块
功能
spring-core 核心容器与 IoC/DI 实现
spring-beans Bean 定义与管理
spring-context IoC 容器高级特性(事件发布、国际化等)
spring-aop 面向切面编程实现
spring-tx 事务管理
spring-jdbc 简化 JDBC 数据访问
spring-web 支持 Web 应用开发(Servlet API 集成)
spring-webmvc MVC 框架(Spring MVC)
3. 工作原理(简化流程)
容器启动
加载配置(XML/注解/JavaConfig)。
扫描包路径下的组件类(
@Component
、@Service
、@Controller
)。
Bean 创建
按定义实例化 Bean(构造函数、工厂方法)。
执行依赖注入(DI)。
AOP 织入
匹配切面定义,把代理对象注入到容器。
应用运行
控制器接收请求(Spring MVC)。
调用业务层与数据访问层。
销毁阶段
容器关闭,调用 Bean 的销毁方法。
4. 常用子项目
Spring Boot
简化 Spring 应用开发,内置 Tomcat/Jetty,无需繁琐 XML 配置。Spring Cloud
微服务框架,提供注册中心、配置中心、网关、负载均衡等。Spring Data
统一数据访问方式,支持 JPA、MongoDB、Elasticsearch 等。Spring Security
权限与认证框架。Spring Batch
批处理框架,适合处理大批量数据任务。
5. 学习建议
先学 Spring Core + Spring MVC,掌握 IoC、DI、AOP、MVC 流程。
过渡到 Spring Boot,理解自动配置与约定优于配置。
了解 Spring Data / Spring Security 等常用模块。
最后学习 Spring Cloud,理解分布式与微服务。
1. 原型(prototype)是什么
在 JavaScript 里:
每个函数(除了箭头函数)在创建时,都会自动获得一个
prototype
属性(是一个对象)。通过
new
调用函数创建的实例,会有一个 隐藏属性[[Prototype]]
(大多数浏览器可用__proto__
访问),它指向这个构造函数的prototype
对象。这个
prototype
对象可以挂载方法或属性,供所有实例共享。
示例:
function Person(name) { this.name = name; } Person.prototype.sayHi = function() { console.log(`Hi, I'm ${this.name}`); }; const p1 = new Person('Alice'); const p2 = new Person('Bob'); p1.sayHi(); // Hi, I'm Alice p2.sayHi(); // Hi, I'm Bob console.log(p1.sayHi === p2.sayHi); // true(共享方法)
2. 原型链(prototype chain)是什么
当你访问一个对象的属性时,JS 会先在对象自身查找,如果找不到,就沿着
[[Prototype]]
继续查找。这种对象通过原型连接起来形成的链式结构,就是 原型链。
查找顺序:
对象自身 → 对象的原型 → 原型的原型 → … → Object.prototype → null
示例:
console.log(p1.__proto__ === Person.prototype); // true console.log(Person.prototype.__proto__ === Object.prototype); // true console.log(Object.prototype.__proto__); // null
3. 原型链查找过程
p1.toString();
查找过程:
在
p1
自身找toString
→ 找不到在
Person.prototype
找 → 找不到在
Object.prototype
找到 → 调用这个方法找不到时返回
undefined
4.
__proto__
vsprototype
属性
属于谁
作用
prototype
构造函数 定义实例共享的属性/方法
__proto__
实例对象 指向创建它的构造函数的
prototype
5. 使用场景
方法共享:节省内存,避免每个实例都生成一份相同方法。
继承:通过修改
prototype
或__proto__
实现继承链。多层继承:子类原型指向父类实例或父类原型,形成多层原型链。
继承示例:
function Animal(name) { this.name = name; } Animal.prototype.run = function() { console.log(`${this.name} is running`); }; function Dog(name, breed) { Animal.call(this, name); this.breed = breed; } Dog.prototype = Object.create(Animal.prototype); // 继承 Dog.prototype.constructor = Dog; Dog.prototype.bark = function() { console.log(`${this.name} barks`); }; const d = new Dog('Tommy', 'Bulldog'); d.run(); // 来自 Animal d.bark(); // 自己的
6. 总结口诀
原型:对象的公共属性/方法存储地(构造函数的
prototype
)。原型链:对象属性查找的路径(沿
__proto__
向上找)。constructor:原型对象上默认有个
constructor
指回构造函数。
1. 核心区别对比
特性
var
let
const
作用域 函数作用域(function scope)
块作用域(block scope)
块作用域(block scope)
变量提升 会提升到作用域顶部,值为
undefined
会提升,但处于 暂时性死区(TDZ) ,未初始化前不能访问
同
let
重复声明 允许在同一作用域重复声明
不允许
不允许
重新赋值 允许
允许
不允许 (引用类型可修改内部值)
初始化 可选
可选
必须初始化 2. 示例
2.1 作用域
if (true) { var a = 1; let b = 2; const c = 3; } console.log(a); // ✅ 1(var 是函数作用域) console.log(b); // ❌ ReferenceError console.log(c); // ❌ ReferenceError
2.2 变量提升
console.log(x); // undefined(声明提升) var x = 5; console.log(y); // ❌ ReferenceError(暂时性死区) let y = 6;
2.3 const 引用类型
const arr = [1, 2, 3]; arr.push(4); // ✅ 可以修改内容 // arr = [5, 6]; // ❌ 不能重新赋值引用
3. 使用建议
**优先用
const
**:默认不变的值保持不可变,防止误改。**需要重新赋值时用
let
**:如循环变量、累加器等。**避免使用
var
**:容易因变量提升和作用域问题导致 bug。代码风格(ESLint 推荐):
默认用
const
仅在需要重新赋值时改用
let
不使用
var
4. 总结口诀
const 定常量,let 定变量,var 尽量别用
const / let 都是块级作用域,var 是函数作用域
var 会提升,let / const 有“暂时性死区”保护1. px(像素,绝对单位)
定义:屏幕上的一个物理像素点(或逻辑像素)。
特点:
固定大小,不会随父元素或页面字体大小变化。
在不同分辨率下可能显示效果差异(高分屏下 1px 实际会更细)。
示例:
div { font-size: 16px; /* 始终是 16 像素 */ }
适用场景:需要绝对精确尺寸的元素(比如边框、图标)。
2. em(相对单位,参考父元素字体大小)
定义:相对父元素
font-size
的倍数。计算方式:
1em = 父元素 font-size 的大小
特点:
会继承并叠加(嵌套时容易出现意外放大)。
对排版和可读性有更好的适配性。
示例:
.parent { font-size: 16px; } .child { font-size: 1.5em; /* 1.5 × 16px = 24px */ }
适用场景:希望跟随父级字体比例变化的情况(比如按钮内边距)。
3. rem(相对单位,参考根元素字体大小)
定义:相对 HTML 根元素
<html>
的font-size
。计算方式:
1rem = html 元素的 font-size 大小
特点:
不会受父元素影响(比 em 更稳定)。
常用在响应式布局,配合媒体查询动态调整根字体大小。
示例:
html { font-size: 16px; } div { font-size: 2rem; /* 2 × 16px = 32px */ }
适用场景:响应式字体、全局统一尺寸控制。
4. 对比总结
单位
参考对象
是否继承叠加
常用场景
px 屏幕像素
否
精确尺寸(边框、图片)
em 父元素 font-size
是
随父元素缩放(按钮、段落)
rem 根元素 font-size
否
响应式全局尺寸
5. 实战技巧
响应式布局推荐用
rem
:只改<html>
字体大小即可全局缩放。需要跟随父元素变化,用
em
。绝对固定的像素,直接用
px
。结合 JS 动态设置根字体大小,可以实现页面随屏幕宽度缩放:
document.documentElement.style.fontSize = window.innerWidth / 10 + 'px';
方法 1:Flex 布局(推荐)
<div class="row"> <div class="item">A</div> <div class="item">B</div> <div class="item">C</div> </div>
.row { display: flex; /* 开启 Flex 布局 */ } .item { padding: 10px; background: lightblue; margin: 5px; }
✅ 优点:简单、响应式好、支持等分、对齐控制方便。
❌ 缺点:IE9 以下不支持。方法 2:
display: inline-block
<div class="item">A</div> <div class="item">B</div> <div class="item">C</div>
.item { display: inline-block; /* 改为行内块元素 */ padding: 10px; background: lightgreen; margin: 5px; }
✅ 优点:兼容性好(IE8+)。
❌ 缺点:HTML 代码的空格会造成间距,可用font-size: 0
消除。方法 3:浮动(float)
<div class="item">A</div> <div class="item">B</div> <div class="item">C</div> <div style="clear: both;"></div> <!-- 清除浮动 -->
.item { float: left; padding: 10px; background: pink; margin: 5px; }
✅ 优点:老浏览器通吃。
❌ 缺点:需要清除浮动,布局灵活性差。方法 4:CSS Grid
<div class="row"> <div class="item">A</div> <div class="item">B</div> <div class="item">C</div> </div>
.row { display: grid; grid-auto-flow: column; /* 按列排列 */ } .item { padding: 10px; background: lightcoral; }
✅ 优点:适合复杂布局。
❌ 缺点:老旧浏览器不支持。如果只是简单让 div 一行显示,现在主流是 Flex 或 inline-block。
我建议你用 Flex,以后加对齐、等分、换行都方便。1. 父容器属性(决定子元素排列方式)
属性
常用值
作用
display flex
/
inline-flex
开启 Flex 布局(
inline-flex
不会独占一行)flex-direction row
/
row-reverse
/column
/column-reverse
主轴方向(水平/垂直/反向)
flex-wrap nowrap
/
wrap
/wrap-reverse
是否换行及方向
flex-flow <flex-direction> <flex-wrap>
上面两个属性的简写
justify-content flex-start
/
flex-end
/center
/space-between
/space-around
/space-evenly
沿主轴对齐方式
align-items stretch
/
flex-start
/flex-end
/center
/baseline
交叉轴对齐方式(子项在单行中如何对齐)
align-content 同上
多行时交叉轴的行与行之间的对齐(对多行布局才生效)
2. 子元素属性(决定自身在 Flex 容器里的表现)
属性
作用
order 控制子项在容器中的排列顺序(数字越小越靠前)
flex-grow 剩余空间的放大比例(默认
0
,不放大)flex-shrink 空间不足时的缩小比例(默认
1
,可缩小)flex-basis 子项在分配空间前占据的主轴尺寸(默认
auto
)flex flex-grow flex-shrink flex-basis
的简写
align-self 覆盖父容器
align-items
对当前子项的设置3. 常见组合示例
3.1 水平垂直居中
.parent { display: flex; justify-content: center; /* 主轴居中 */ align-items: center; /* 交叉轴居中 */ }
3.2 等宽布局
.item { flex: 1; /* 等分剩余空间 */ }
3.3 固定 + 自适应
.left { flex: 0 0 200px; /* 不放大、不缩小、固定 200px */ } .right { flex: 1; /* 占剩余空间 */ }
4. 记忆小技巧
主轴方向 → flex-direction
主轴对齐 → justify-content
交叉轴对齐 → align-items / align-content
子项伸缩 → flex-grow / flex-shrink
初始大小 → flex-basis
覆盖父设置 → align-self
如果你愿意,我可以帮你画一张 Flex 布局属性速查图,把主轴、交叉轴和这些属性的作用范围都标出来,一眼就能找到该用哪个属性。
要我帮你画吗?这样记忆会更直观。display: block
是 CSS 布局的基础属性之一,用来让一个元素以 块级元素(block-level element) 的方式渲染。1. 作用
当一个元素是
display: block
时,它会:独占一行(不与其他元素并排显示,默认会换行)。
宽度默认占满父容器(可以通过
width
改变)。可以设置宽高(
width
/height
有效)。上下可有
margin
,而inline
元素的上下margin
通常不生效。
2. 常见的 block 元素
即使不写
display: block
,以下标签默认就是块级:<div>, <p>, <h1>~<h6>, <section>, <article>, <header>, <footer>, <ul>, <ol>, <li>, <nav>, <form>, <table>
3. 示例
<style> .box { display: block; width: 200px; height: 80px; background: lightblue; } </style> <span class="box">我是块级元素</span>
即使原本
<span>
是行内元素(inline
),加了display: block
后,也会变成块级,能设置宽高、独占一行。4. 常见用途
把 行内元素(如
<a>
、<span>
、<img>
)转换为块级,用来:让它支持宽高
让它独占一行
方便做盒子模型布局
在响应式布局中切换显示方式(例如按钮在大屏并排,小屏用
display: block
堆叠)
5. 对比
值
特点
block
独占一行,可设置宽高
inline
不换行,宽高由内容决定,
width
/height
不生效inline-block
不换行,但可设置宽高
flex
/
grid
容器布局模式
响应式布局(Responsive Layout)就是让页面在不同设备、不同屏幕宽度下都能良好显示和交互,核心是布局会自动适配,而不是固定死的像素值。
我给你分成 核心思路 → 常用技术 → 示例代码 → 实战建议 来讲。
1. 核心思路
流式布局:用百分比、
auto
、min/max-width
等代替固定像素。媒体查询(Media Queries) :针对不同屏幕宽度、分辨率,切换样式。
弹性/网格布局:
flex
和grid
可以在不同空间下自动调整元素位置和大小。响应式图片:不同分辨率加载不同尺寸的图片。
断点(Breakpoints)设计:设定几个关键屏宽(如 576px、768px、1024px、1200px)来调整布局。
2. 常用技术
技术
作用
百分比宽度 容器和元素随屏幕大小变化
max-width
/min-width
限制元素在不同设备下的最大/最小尺寸
vh
/vw
按视口宽高比例设置尺寸
媒体查询(@media) 针对不同屏宽、设备应用不同样式
Flexbox 让子元素在空间变化时自动伸缩、换行
CSS Grid 更复杂的二维布局,自适应调整行列
响应式单位(rem / em) 字体和间距随根字体大小缩放
3. 示例代码
3.1 基于媒体查询的响应式布局
.container { display: flex; flex-wrap: wrap; } .item { flex: 1 1 100%; /* 默认占满一行 */ padding: 10px; background: lightblue; } /* 平板以上屏幕 */ @media (min-width: 768px) { .item { flex: 1 1 50%; /* 两列 */ } } /* 桌面端 */ @media (min-width: 1200px) { .item { flex: 1 1 25%; /* 四列 */ } }
3.2 百分比 + max-width
img { max-width: 100%; height: auto; }
3.3 使用
vw
/vh
.banner { height: 50vh; /* 高度占视口的一半 */ }
4. 实战建议
移动优先(Mobile First)
默认写小屏样式 → 用
@media (min-width)
逐渐增强适配大屏。
内容优先(Content First)
断点设计以内容布局变化为依据,不要只看设备型号。
组件化
每个组件自己处理响应式,减少全局样式冲突。
配合框架
Bootstrap、Tailwind CSS、Ant Design 都有现成的响应式工具类。
测试
Chrome DevTools → Device Toolbar → 模拟不同屏幕查看效果。
那就是局部容器用
display: flex
,它的子元素才会受 flex 规则影响,其它区域还是普通布局。1. 局部 Flex 布局的核心
.container { display: flex; /* 只让这个容器内的子元素参与 flex 布局 */ }
Flex 的作用域:只影响设置了
display: flex
的元素的 直接子元素。外部布局可以是普通的
block
、inline-block
、grid
等。内部可以自由用
flex-direction
、justify-content
、align-items
等属性控制排列。
2. 示例:局部区域水平垂直居中
<div class="header">普通头部</div> <div class="flex-box"> <div class="item">A</div> <div class="item">B</div> <div class="item">C</div> </div> <div class="footer">普通底部</div>
.flex-box { display: flex; /* 开启局部 flex */ justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */ height: 150px; background: #eee; } .item { padding: 10px 20px; background: lightblue; margin: 5px; }
✅ 只有
.flex-box
内的 A、B、C 受 flex 布局控制,header 和 footer 完全不受影响。3. 常见局部 Flex 用法
(1) 局部水平排列
.row { display: flex; gap: 10px; }
(2) 局部垂直排列
.column { display: flex; flex-direction: column; gap: 10px; }
(3) 局部等宽布局
.equal { display: flex; } .equal > div { flex: 1; /* 平分宽度 */ }
4. 小技巧
嵌套布局:Flex 容器里还可以再嵌套 flex 容器,实现更复杂的布局。
响应式:结合
flex-wrap: wrap
可以让子元素在小屏自动换行。局部与全局混搭:上层可以是 Grid,某些区域再用 flex 局部控制排列。
1. XXL-JOB 是什么
一个 分布式任务调度平台(类似轻量级定时任务中心)。
支持任务可视化管理、动态修改 Cron、失败重试、分片广播等功能。
核心由两部分组成:
调度中心(xxl-job-admin) :管理任务、分配执行器。
执行器(xxl-job-executor) :你的业务服务,接收调度中心的调度并执行任务。
2. 部署调度中心(xxl-job-admin)
下载源码
git clone https://github.com/xuxueli/xxl-job.git
导入 SQL
找到
xxl-job/doc/db/tables_xxl_job.sql
在 MySQL 中执行,初始化调度中心数据库表。
修改配置
编辑
xxl-job-admin/src/main/resources/application.properties
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 xxl.job.login.username=admin xxl.job.login.password=123456
启动调度中心
cd xxl-job-admin mvn spring-boot:run
访问
http://localhost:8080/xxl-job-admin
(默认 admin / 123456 登录)。3. 集成执行器(在你的 Spring Boot 项目中)
引入依赖
<dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>2.4.0</version> </dependency>
配置 application.yml
xxl: job: admin: addresses: http://127.0.0.1:8080/xxl-job-admin # 调度中心地址 executor: appname: my-job-executor address: ip: port: 9999 logpath: /data/applogs/xxl-job/jobhandler logretentiondays: 30 accessToken:
注册执行器配置类
@Configuration public class XxlJobConfig { @Bean public XxlJobSpringExecutor xxlJobExecutor() { XxlJobSpringExecutor executor = new XxlJobSpringExecutor(); executor.setAdminAddresses("http://127.0.0.1:8080/xxl-job-admin"); executor.setAppname("my-job-executor"); executor.setPort(9999); executor.setLogPath("/data/applogs/xxl-job/jobhandler"); executor.setLogRetentionDays(30); return executor; } }
编写任务 Handler
import com.xxl.job.core.handler.annotation.XxlJob; import org.springframework.stereotype.Component; @Component public class DemoJobHandler { @XxlJob("demoJobHandler") public void execute() throws Exception { System.out.println("XXL-JOB 任务执行:" + System.currentTimeMillis()); } }
4. 在调度中心创建任务
登录调度中心 → 任务管理 → 新增任务
选择执行器
my-job-executor
填写 JobHandler:
demoJobHandler
配置 Cron 表达式(如
0/10 * * * * ?
表示每 10 秒执行一次)保存 → 启动任务
5. 常用功能
分片广播:适合分布式批量处理任务(执行器数 = 分片数)。
失败重试:任务失败后按配置次数重试。
GLUE 模式:支持在线编写 Java 代码任务(无需部署)。
路由策略:如 FIRST、RANDOM、ROUND、CONSISTENT_HASH 等。
日志查看:执行日志可在页面直接查看。
6. XXL-JOB 优势
Web 界面管理任务,支持动态修改。
集群调度,支持多执行器部署。
支持分布式任务分片与路由策略。
任务失败报警(邮件/钉钉/企业微信)。
一、核心思路(最简流程)
采集文档并分配
docID
规范化文本(小写、Unicode 归一、去标点/HTML、同义与词干可选)
分词(英文可按词边界;中文需用分词器)
遍历每个
(term, docID, position)
,写入字典:term -> postings
对每个 term 的 Posting List 按
docID
升序,合并重复,计算tf
,可选保存positions
持久化:写出 词典(term→文件偏移、DF 等)与 倒排表文件(docID 列表、tf、positions),并进行压缩(gap+可变长编码)
额外表:
docID -> {length, stored fields}
用于排名与展示
二、极简可跑的 Python 示例(含位置与 BM25 所需统计)
from collections import defaultdict import re import math def tokenize(text): # 英文示例:小写+简单切词;中文请接入 jieba/HanLP 等 text = re.sub(r"<[^>]+>", " ", text) # 去HTML text = re.sub(r"[^a-zA-Z0-9]+", " ", text) # 非字母数字 return [w for w in text.lower().split() if w] def build_inverted_index(docs): """ docs: dict[int, str] e.g. {1: "Text...", 2: "More..."} return: index: dict[term] -> list of {"doc":id, "tf":n, "pos":[...]} df: dict[term] -> doc_freq doclen: dict[docID] -> length_in_tokens """ postings = defaultdict(lambda: defaultdict(list)) # term -> docID -> positions doclen = {} for doc_id, text in docs.items(): tokens = tokenize(text) doclen[doc_id] = len(tokens) for i, tok in enumerate(tokens): postings[tok][doc_id].append(i) index, df = {}, {} for term, doc_pos in postings.items(): plist = [] for d, pos_list in sorted(doc_pos.items()): # 按 docID 排序 plist.append({"doc": d, "tf": len(pos_list), "pos": pos_list}) index[term] = plist df[term] = len(plist) return index, df, doclen # 示例与查询(布尔与BM25打分) docs = { 1: "Elasticsearch builds on Lucene. Lucene uses inverted index.", 2: "Inverted index enables fast full-text search.", 3: "Lucene segments are immutable; merging reduces small files." } index, df, doclen = build_inverted_index(docs) N = len(docs); avgdl = sum(doclen.values())/N def bm25_score(query_terms, k1=1.2, b=0.75): # 简化版 BM25:不做查询词频统计 scores = defaultdict(float) for term in query_terms: if term not in index: continue ni = df[term] idf = math.log((N - ni + 0.5) / (ni + 0.5) + 1) for item in index[term]: d = item["doc"]; tf = item["tf"]; dl = doclen[d] denom = tf + k1*(1 - b + b*dl/avgdl) scores[d] += idf * (tf*(k1+1) / denom) return sorted(scores.items(), key=lambda x: x[1], reverse=True) print(bm25_score(tokenize("lucene inverted index")))
要点:
index[term]
的 posting 保存docID/tf/positions
,满足布尔/短语/临近搜索与排序。线上应将
positions
可选化(节省空间)。
三、工程化与性能优化清单
1) 词典与倒排存储
词典(term→{df, cf, offset, length}})放内存或 mmap;term 查找可用 FST/前缀树。
倒排:
[docID_gap编码][tf][pos_gap...]
;docID/pos 用 gap(差分)+ 可变长(VarInt/VarByte/VByte)或 PForDelta、QMX 等压缩。按块(block)切分并加 跳跃表/skip pointers,加快并集/交集与跳跃。
2) 写入路径
采用 段(segment)不可变 策略:内存缓冲 → 定期 flush 生成小段 → 后台 merge 合段(减少小文件)。
更新/删除:写新段与 删除标记(tombstone) ,在合并时清理;避免就地改写。
3) 分词与规范化
英文:lowercase、去停用词、词干化(Porter/Lemmatize,可按需)。
中文:接入
jieba
/HanLP
/ICTCLAS
/THULAC
等;保留位置以支持短语匹配。多语言:Unicode NFKC 归一、同义词扩展(离线字典或查询时扩展)。
4) 查询执行
先用倒排做候选集过滤,再用正排/DocValues 做精确筛选(数值/范围/日期)。
排序:BM25/PL2 + 学习排序(LTR);需要
doclen/avgdl
等统计。并行:多核并行处理多个 term 或多个段;大规模用分片(shards)并行,协调节点合并 TopK。
5) 大规模离线构建(MapReduce/Spark 思路)
Map:对每个文档输出
(term, (docID, pos))
Shuffle:按 term 分组并排序
Reduce:合并为有序 posting list,写出段文件,并记录词典偏移
后续再做段级合并与压缩/索引块化
(这正是搜索引擎和 ES/Lucene 的离线/在线混合套路)
6) 评测与质量
功能:AND/OR、短语、临近(slop)、高亮
质量:MRR、NDCG@K、Recall/Precision
性能:QPS、P99 延迟;索引大小、构建时长;合并对查询抖动的影响
四、常见坑
中文不分词 → 命中率极差;必须接入分词。
未按 docID 排序与 gap 压缩 → 空间/速度都很差。
positions 全保留 → 指数级膨胀;仅在需要短语/临近检索时开启。
频繁原地更新 → 复杂且慢;改用段不可变 + 合并。
长文本字段 → 建议只索引必要字段;展示内容走存储字段或外部存储。
五、如果你要“像 ES/Lucene 那样”
段式不可变结构 + 后台合并(tiered merge)
词典用 FST;posting 分块 + skip list
mmap 文件;
doc values
做排序/聚合刷新(refresh)让新段可见,flush 写磁盘,translog 保持崩溃恢复
1. ES 底层架构
Elasticsearch 是基于 Lucene 的分布式搜索引擎,底层核心结构:
Client ↓ Coordinator Node(协调节点) ↓ Primary Shard(主分片) ↓ Replica Shard(副本分片) ↓ Lucene Segment(倒排索引存储单元) ↓ 文件系统(底层存储)
Node:ES 集群中的单个实例。
Index:类似数据库的库。
Shard:索引分成的多个分片(Primary 主分片 + Replica 副本分片)。
Segment:Lucene 的最小搜索单元,底层不可变文件。
Cluster State:集群元数据(节点信息、索引信息等)。
2. 存储原理(基于 Lucene)
Elasticsearch 并不是直接存数据到数据库,而是用 倒排索引 来加速全文检索。
倒排索引结构:
term(关键词) → [doc1, doc3, doc7 ...]
正排索引:doc → field 值(用于精确获取)。
倒排索引:field 值(term) → doc 列表(用于搜索匹配)。
写入过程(Indexing Flow):
文档 JSON 通过 Analyzer(分词器) 分解成 Token。
Token 按字段构建倒排表,写入 Segment(不可变)。
Segment 写入文件系统(默认基于 Lucene + mmap)。
写入前先进入 内存缓冲区(Index Buffer)→ translog(事务日志)保证宕机恢复。
定期 flush:生成新的 Segment 并清空缓冲区。
定期 merge:将多个小 Segment 合并成大 Segment(减少文件数,提高搜索效率)。
3. 搜索原理
搜索是 分布式 + 并行 的过程。
查询流程(Search Flow):
协调节点(Coordinator Node)接收查询请求。
将请求转发给所有分片(Primary 或 Replica 都可)。
每个分片在自己的 倒排索引(Segment) 中执行查询。
分片返回 Top N 结果(docID + score)。
协调节点合并排序,返回最终结果。
4. 底层优化机制
4.1 存储优化
不可变 Segment:写入快、搜索快,但会产生大量小文件 → 需要 merge。
内存映射(mmap) :Lucene 使用操作系统内存映射文件,提高 IO 性能。
压缩存储:倒排表 + doc 值采用压缩(减少磁盘 & 提高缓存命中率)。
4.2 查询优化
跳表 / FST(Finite State Transducer) :优化 term 查找。
Bitset / Roaring Bitmap:快速标记匹配的 doc。
倒排 + 正排结合:先倒排快速筛选,再正排精确过滤。
4.3 分布式一致性
写入:先写主分片,再写副本分片,保证最终一致性。
刷新(refresh) :默认 1 秒刷新一次,使新文档可被搜索到(near real-time)。
副本分片:既能提高可用性,又能分担读取压力。
5. 关键点总结
ES 底层是 Lucene,核心是 倒排索引。
Segment 不可变 → 写快、并发安全,但需要合并。
搜索过程是 分布式并行,依赖 协调节点合并结果。
高性能来自:
内存映射文件
压缩索引
跳表 / FST
多分片并行
WebSocket 是一种 全双工、长连接 的网络通信协议,常用于实时通信场景(比如聊天、推送、股票行情、游戏等)。
1. 特点
特性
说明
双向通信 客户端和服务器都能主动发消息。
长连接 一次握手,持续保持连接,不必频繁建立 HTTP 请求。
低延迟 适合需要实时推送的场景。
基于 TCP 在 TCP 基础上定义了自己的帧格式,通常运行在 80/443 端口。
2. 通信过程
客户端发起 HTTP 请求(带有
Upgrade: websocket
头),请求升级为 WebSocket。服务器响应 101 状态码,表示协议切换成功。
握手完成后,双方可以通过 TCP 直接交换数据帧。
保持长连接,直到一方主动关闭。
3. 浏览器端示例
// 创建连接 const socket = new WebSocket("ws://localhost:8080"); // 连接成功 socket.onopen = () => { console.log("WebSocket connected"); socket.send("Hello Server"); }; // 接收消息 socket.onmessage = (event) => { console.log("Message from server:", event.data); }; // 连接关闭 socket.onclose = () => { console.log("WebSocket closed"); }; // 错误处理 socket.onerror = (error) => { console.error("WebSocket error:", error); };
4. Node.js 服务器端示例
const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', (ws) => { console.log('Client connected'); ws.on('message', (message) => { console.log('Received:', message); ws.send(`Echo: ${message}`); }); ws.on('close', () => { console.log('Client disconnected'); }); });
5. 常见应用场景
即时聊天(WhatsApp、微信网页版)
实时推送(股票、体育比分)
多人在线游戏
协同编辑(Google Docs 实时同步)
Docker 是一个容器化平台,可以让你把应用和它的依赖打包在一个轻量级、可移植的容器里运行,避免“在我电脑上可以跑”的问题。
1. 核心概念
概念
说明
镜像(Image) 应用运行所需的只读模板,比如一个包含 Ubuntu 系统 + Python 环境的打包文件。
容器(Container) 镜像的运行实例,可以启动、停止、删除。容器里的环境互相隔离。
Docker Hub 官方镜像仓库,你可以直接拉取别人做好的镜像,或者上传自己的镜像。
Dockerfile 用来定义镜像构建步骤的文件。
2. 常用命令
# 查看 Docker 版本 docker --version # 拉取镜像 docker pull nginx:latest # 运行容器(映射端口,后台运行) docker run -d -p 8080:80 nginx:latest # 查看正在运行的容器 docker ps # 查看所有容器(包括已停止的) docker ps -a # 停止容器 docker stop <容器ID或名字> # 删除容器 docker rm <容器ID或名字> # 删除镜像 docker rmi <镜像ID> # 构建镜像(在 Dockerfile 所在目录执行) docker build -t myapp:1.0 . # 进入容器终端 docker exec -it <容器ID> /bin/bash
3. Dockerfile 示例
# 基础镜像 FROM python:3.11 # 设置工作目录 WORKDIR /app # 复制项目文件到容器 COPY . . # 安装依赖 RUN pip install -r requirements.txt # 启动应用 CMD ["python", "app.py"]
构建并运行:
docker build -t myapp . docker run -d -p 5000:5000 myapp
4. Docker 使用优势
跨平台一致性:一次构建,到处运行。
轻量快速:比虚拟机启动快,资源占用少。
易于分发:直接推送到镜像仓库即可分享。
隔离性好:每个容器互不影响。