1. Java学习笔记:1月30日核心知识点复盘
今天整理的是近期Java开发中几个容易被忽视但至关重要的技术要点,主要涉及集合框架优化、并发编程陷阱以及JDK新特性实战。这些内容来自我最近参与的高并发项目踩坑实录和代码评审中的高频问题,适合有半年以上Java经验的开发者查漏补缺。
2. 集合框架性能优化实战
2.1 ArrayList初始容量设定原则
在电商秒杀场景的压测中,我们发现未初始化容量的ArrayList在插入10万条订单数据时,因频繁扩容导致耗时增加47%。通过JOL工具分析内存布局可见:
java复制// 错误示范
List<Order> orders = new ArrayList<>();
// 正确做法(已知最终大小)
List<Order> orders = new ArrayList<>(100000);
扩容机制底层实现:
- 默认初始容量10
- 每次扩容为原容量的1.5倍(int newCapacity = oldCapacity + (oldCapacity >> 1))
- 数据迁移使用Arrays.copyOf
关键经验:在批量处理数据前,如果能预估最终规模,务必显式指定初始容量。实测显示初始化容量后,插入百万数据耗时从320ms降至210ms。
2.2 HashMap负载因子调优
支付系统中遇到HashMap查询性能骤降的问题,根源在于默认0.75负载因子不适合高频更新场景。我们通过以下参数重构:
java复制// 适合读多写少的场景
Map<String, Transaction> cache = new HashMap<>(1024, 0.6f);
// 适合写密集型的场景
Map<String, Transaction> cache = new HashMap<>(2048, 0.5f);
性能对比测试结果:
| 负载因子 | 写入10万次(ms) | 查询10万次(ms) |
|---|---|---|
| 0.75 | 285 | 120 |
| 0.6 | 240 | 110 |
| 0.5 | 210 | 130 |
3. 并发编程避坑指南
3.1 ThreadLocal内存泄漏现场还原
在用户会话管理中,错误使用ThreadLocal导致PermGen持续增长。通过MAT分析堆dump发现:
java复制// 危险用法(未实现remove)
ThreadLocal<UserContext> context = new ThreadLocal<>();
// 安全方案
try {
context.set(new UserContext());
// ...业务逻辑
} finally {
context.remove(); // 必须清理
}
泄漏原理图解:
- Thread持有ThreadLocalMap强引用
- Entry的key是弱引用,但value是强引用
- 线程池场景下线程长期存活导致value堆积
3.2 CompletableFuture组合陷阱
在微服务调用链中,错误的多任务组合导致接口超时:
java复制// 错误组合方式(串行执行)
CompletableFuture<Void> future = CompletableFuture
.runAsync(this::queryDB)
.thenRun(this::callServiceA) // 需等待前序完成
.thenRun(this::callServiceB);
// 正确并行写法
CompletableFuture<Void> future = CompletableFuture.allOf(
CompletableFuture.runAsync(this::queryDB),
CompletableFuture.runAsync(this::callServiceA),
CompletableFuture.runAsync(this::callServiceB)
);
实测对比:串行执行耗时1200ms,并行执行仅需400ms。特别注意IO密集型任务必须采用并行组合。
4. JDK17新特性生产实践
4.1 模式匹配简化类型判断
在处理多态消息时,传统写法:
java复制if (msg instanceof LoginMessage) {
LoginMessage lm = (LoginMessage) msg;
// 处理逻辑
}
JDK16+模式匹配写法:
java复制if (msg instanceof LoginMessage lm) {
// 直接使用lm变量
System.out.println(lm.getUsername());
}
字节码对比显示新语法不会产生额外开销,但可读性显著提升。在消息中间件改造中,使代码行数减少35%。
4.2 Records替代DTO的实践
传统POJO的样板代码:
java复制public class UserDTO {
private String name;
private int age;
// 构造方法/getter/setter/equals/hashCode/toString...
}
Records声明方式:
java复制public record UserDTO(String name, int age) {}
实际项目中的限制注意:
- 字段默认final不可变
- 不适合需要继承的场景
- 验证逻辑需通过compact constructor实现:
java复制public record UserDTO(String name, int age) {
public UserDTO {
Objects.requireNonNull(name);
if (age < 0) throw new IllegalArgumentException();
}
}
5. JVM参数调优实录
5.1 G1GC关键参数配置
在8核32G的订单处理服务器上,经过多次压测得出的最优配置:
bash复制-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
-XX:ConcGCThreads=4
-XX:G1ReservePercent=15
各参数作用解析:
- MaxGCPauseMillis:目标停顿时间(不是保证值)
- InitiatingHeapOccupancyPercent:触发并发标记的堆占用比
- ConcGCThreads:并发GC线程数(建议为总核数1/4)
- G1ReservePercent:堆内存保留比例
5.2 内存溢出快速定位技巧
当收到OOM报警时,按以下步骤取证:
- 立即保存现场(不要重启服务)
- 执行快速dump命令:
bash复制
jmap -dump:live,format=b,file=heap.bin <pid> - 使用OQL查询可疑对象:
sql复制SELECT s FROM java.lang.String s WHERE s.count > 1000 - 检查GC日志关键指标:
bash复制grep "Full GC" gc.log | awk '{print $11}' | sort -n
6. 开发工具链升级建议
6.1 JUnit5参数化测试进阶
传统参数化测试写法:
java复制@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void testProcess(int input) {
// 测试逻辑
}
新型CSV数据源用法:
java复制@ParameterizedTest
@CsvSource({
"1, true, 'success'",
"2, false, 'failed'"
})
void testMultiArgs(int id, boolean flag, String msg) {
// 可接收多参数组合
}
6.2 诊断神器Arthas实战
线上问题诊断经典场景:
- 方法调用追踪:
bash复制watch com.example.Service * '{params,returnObj,throwExp}' -x 3 - 热修复代码:
bash复制
redefine /tmp/Fix.class - 监控方法RT:
bash复制
monitor -c 5 com.example.Service getData
我在排查Redis连接泄漏时,通过Arthas的stack命令快速定位到未正确关闭连接的代码位置,比查日志效率提升10倍以上。
7. 编码规范强化要点
7.1 异常处理黄金法则
在金融系统中总结的异常处理规范:
- 禁止捕获Throwable
- 日志必须包含上下文信息:
java复制// 错误示范 log.error("save failed"); // 正确写法 log.error("保存用户{}订单失败,金额:{}", userId, amount, e); - 事务方法内异常需明确回滚策略:
java复制@Transactional(rollbackFor = {BusinessException.class, DBException.class})
7.2 日期时间API规范
时区问题导致的跨天bug解决方案:
java复制// 错误示范(依赖系统时区)
LocalDate now = LocalDate.now();
// 正确做法(显式指定时区)
ZoneId zone = ZoneId.of("Asia/Shanghai");
LocalDateTime now = LocalDateTime.now(zone);
// 数据库存储统一用UTC
Instant dbTime = Instant.now();
8. 设计模式实战精要
8.1 策略模式支付场景实现
电商支付路由的优雅实现:
java复制// 策略接口
public interface PaymentStrategy {
Result pay(Order order);
}
// 上下文路由器
public class PaymentRouter {
private Map<String, PaymentStrategy> strategies;
public Result process(Order order) {
String channel = order.getPayChannel();
return strategies.get(channel).pay(order);
}
}
// 使用示例
router.register("wechat", new WechatPayment());
router.register("alipay", new AlipayPayment());
8.2 观察者模式事件总线
基于Spring Event的改进方案:
java复制// 自定义事件
public class OrderEvent extends ApplicationEvent {
private Order order;
public OrderEvent(Object source, Order order) {
super(source);
this.order = order;
}
}
// 异步事件处理器
@EventListener
@Async("eventExecutor")
public void handleOrderEvent(OrderEvent event) {
// 处理逻辑
}
// 发布事件
applicationContext.publishEvent(new OrderEvent(this, order));
配置线程池注意事项:
properties复制# 事件线程池配置
spring.task.execution.pool.core-size=4
spring.task.execution.pool.max-size=8
spring.task.execution.pool.queue-capacity=1000
9. 性能优化专项
9.1 日志输出性能陷阱
错误日志导致的性能问题案例:
java复制// 问题代码(即使不输出也会执行字符串拼接)
log.debug("Process result: " + heavyCompute());
// 修正方案(使用占位符延迟计算)
log.debug("Process result: {}", () -> heavyCompute());
性能测试数据对比:
| 日志级别 | 错误写法(QPS) | 正确写法(QPS) |
|---|---|---|
| DEBUG | 1250 | 9800 |
| INFO | 9800 | 9900 |
9.2 反射优化方案对比
不同反射调用方式的性能测试(调用100万次):
| 方式 | 耗时(ms) |
|---|---|
| 直接调用 | 15 |
| Method.invoke | 1200 |
| MethodHandle | 800 |
| 预编译MethodAccess | 50 |
推荐使用Spring的ReflectionUtils或CGLIB的FastClass,在框架开发中可提升20倍性能。
10. 线上问题排查工具箱
10.1 线程阻塞快速诊断
使用jstack分析线程卡死:
- 抓取线程dump:
bash复制
jstack -l <pid> > thread.txt - 分析关键状态:
bash复制grep -A 10 "BLOCKED" thread.txt grep "waiting on" thread.txt - 查找资源竞争:
bash复制awk '/deadlock/ {flag=1} flag' thread.txt
10.2 内存泄漏分析流程
MAT分析步骤图解:
- 查找支配树中的大对象
- 检查GC Roots引用链
- 分析对象保留集
- 对比多个dump文件
关键OQL查询示例:
sql复制SELECT * FROM java.util.HashMap$Node
WHERE toString(key) LIKE "%.OrderCache"
