Java 开发日常排错
·
Java 开发日常排错是每个 Java 工程师的“必修课”。无论是新手还是老手,都会频繁遇到各种异常、性能问题、环境配置错误等。下面为你整理一份 系统化、结构化、实战导向的《Java 开发日常排错指南》,包含:
- 🧩 常见错误分类
- 🔍 排错方法论
- 🛠️ 工具推荐
- 📋 典型案例 + 解决方案(表格形式)
- 💡 预防建议
🎯 一、Java 日常排错方法论(四步法)
✅ STEP 1:看错误 → 定位类型
✅ STEP 2:查日志 → 找上下文
✅ STEP 3:复现问题 → 最小化场景
✅ STEP 4:修复验证 → 加监控/测试
🧰 二、必备排错工具清单
| 类型 | 工具 | 用途说明 |
|---|---|---|
| 日志分析 | grep, less, tail -f, ELK, Kibana |
快速搜索异常堆栈、关键字 |
| 调试工具 | IntelliJ IDEA Debugger, Eclipse Debugger | 断点调试、变量观察 |
| 内存分析 | jmap, jstat, VisualVM, MAT |
分析 OOM、内存泄漏 |
| 线程分析 | jstack, Arthas thread, fastthread.io |
查死锁、线程阻塞、CPU 飙高 |
| 性能监控 | Arthas, JProfiler, YourKit, Prometheus + Grafana |
方法耗时、GC 情况、调用链 |
| 反编译 | JD-GUI, CFR, Arthas jad |
查看线上/第三方 jar 包源码 |
| 热修复/诊断 | Arthas(阿里开源) |
不重启 JVM,在线 watch/trace/redefine 类 |
💡 强烈推荐掌握 Arthas —— Java 线上诊断神器!
📊 三、Java 开发常见错误排错表(含解决方案)
✅ 可打印 / 导入 Excel / 作为团队知识库
| 错误现象 / 异常信息 | 可能原因 | 排错步骤 | 解决方案 | 预防措施 |
|---|---|---|---|---|
启动报错:ClassNotFoundException |
类路径缺失、包名错误、依赖未引入 | 1. 检查 target/classes 是否有该类 2. 检查 pom.xml / build.gradle 依赖 3. 检查是否混淆或打包遗漏 |
添加依赖 / 修正包名 / 重新 clean install | 使用 Maven/Gradle 依赖管理,避免手动拷 jar |
运行时报:NoClassDefFoundError |
类在编译时存在,运行时缺失(如 jar 冲突、未打入 fat jar) | 1. java -verbose:class 查看类加载2. mvn dependency:tree 检查冲突3. 检查 fat jar 是否包含所需类 |
排除冲突依赖 / 使用 shade 插件合并 / 检查 Class-Path | 构建时检查依赖树,使用 maven-shade-plugin |
NullPointerException (NPE) |
对象为 null 却调用方法/属性 | 1. 看堆栈定位行号 2. 检查上游赋值逻辑 3. 使用 IDEA “Analyze Data Flow” |
判空处理 / 使用 Optional / 防御性编程 | 启用 SonarQube NPE 规则 / 使用 @NonNull 注解 |
OutOfMemoryError: Java heap space |
内存泄漏、大对象、缓存无上限 | 1. jmap -histo:live <pid> 查看对象分布2. 用 MAT 分析 dump 文件 3. Arthas watch 监控大对象创建 |
增加 -Xmx / 优化缓存淘汰策略 / 修复泄漏点 | 设置 JVM 监控告警 / 定期压测 / 使用弱引用 |
OutOfMemoryError: Metaspace |
动态生成类过多(如 CGLIB、反射、Lambda) | 1. jstat -gcmetacapacity <pid>2. 检查是否有类加载器泄漏 |
增加 -XX:MaxMetaspaceSize=512m或修复类加载器泄漏 |
避免频繁动态代理 / 使用缓存 ClassLoader |
StackOverflowError |
递归无终止条件、循环调用 | 1. 看堆栈找重复方法 2. 检查递归出口 |
添加递归终止条件 / 改为迭代实现 | 代码审查递归逻辑 / 单元测试边界值 |
| 接口响应慢 / CPU 100% | 死循环、复杂算法、频繁 GC、线程阻塞 | 1. top -H -p <pid> 找高 CPU 线程2. jstack <pid> 找对应线程栈3. Arthas thread -n 3 查最忙线程 |
优化算法 / 加缓存 / 异步化 / 调整线程池 | 性能压测 + Arthas trace 监控热点方法 |
数据库连接超时 / TooManyConnections |
连接未关闭、连接池配置不合理 | 1. 检查是否用 try-with-resources 2. 查看连接池 active / idle 数 3. show processlist 看 MySQL 连接 |
使用 HikariCP / Druid 监控 / 设置合理 maxPoolSize | 连接必须在 finally 或 try-with-resources 关闭 |
ConcurrentModificationException |
遍历集合时修改结构(非线程安全) | 1. 看堆栈定位操作位置 2. 检查是否在 for-each 中 remove/add |
使用 Iterator.remove() / CopyOnWriteArrayList / 加锁 | 使用线程安全容器 / 明确并发修改规则 |
Spring Bean 注入失败 NoSuchBeanDefinitionException |
组件未扫描、@ComponentScan 路径不对、循环依赖 | 1. 检查 @Service/@Component 是否标注 2. 检查包路径是否被扫描 3. 检查 @Autowired 是否写错 |
补注解 / 调整扫描路径 / 用 @Lazy 解决循环依赖 | 使用 IDEA Spring 插件辅助检查 Bean |
IllegalArgumentException: Invalid character(URL/参数) |
参数含特殊字符未编码、JSON 格式错误 | 1. 打印原始请求参数 2. 检查前端是否 encodeURIComponent 3. 检查后端是否 URLDecoder.decode |
前后端统一编码规范 / 使用框架自动解析 | 使用 Swagger/Postman 调试接口 / 参数校验注解 |
| 定时任务不执行 / 执行多次 | 未启用 @EnableScheduling、集群未分布式锁、多个实例部署 | 1. 检查配置类是否有 @EnableScheduling 2. 检查是否多节点部署 3. 日志看是否触发 |
加 @EnableScheduling / 用 Redis 分布式锁 / Quartz 集群模式 | 集群环境必须考虑幂等和分布式调度 |
SSLHandshakeException / HTTPS 调用失败 |
证书过期、信任库缺失、TLS 版本不匹配 | 1. openssl s_client -connect host:port2. 检查 JDK cacerts 是否包含证书 3. 查 TLS 协议版本 |
导入证书到 truststore / 升级 JDK / 设置协议版本 | 测试环境导入证书 / 生产使用正规 CA 证书 |
Maven 编译报错:程序包 xxx 不存在 |
依赖未下载、仓库地址错误、本地仓库损坏 | 1. mvn clean compile -U2. 检查 settings.xml 镜像源 3. 删除 ~/.m2/repository 重下 |
更换镜像源 / 删除本地仓重下 / 检查网络 | 使用公司 Nexus 私服 / 配置稳定的阿里云镜像 |
🧪 四、典型案例详解
▶ 案例1:CPU 100% 排查
# 1. 找出 Java 进程 PID
ps -ef | grep java
# 2. 找出最耗 CPU 的线程
top -H -p <pid>
# 3. 转换线程 ID 为 16 进制
printf "%x\n" <tid>
# 4. 查看线程堆栈
jstack <pid> | grep -A 30 <hex_tid>
# 5. 用 Arthas 快速定位
thread -n 3 # 显示最忙的3个线程
→ 发现是 HashMap.get() 在多线程下形成死循环 → 改为 ConcurrentHashMap
▶ 案例2:内存泄漏排查
# 1. 导出堆 dump
jmap -dump:live,format=b,file=heap.hprof <pid>
# 2. 用 MAT 分析
# 打开 Eclipse MAT → 找 Dominator Tree → 看 Retained Heap 最大的对象
# 3. 用 Arthas 监控对象创建
watch com.example.Service fetchData returnObj -x 3
→ 发现是静态 Map 缓存用户数据且无过期 → 改用 Guava Cache / Caffeine
▶ 案例3:Spring 循环依赖报错
@Service
public class A {
@Autowired private B b;
}
@Service
public class B {
@Autowired private A a; // 循环依赖!
}
✅ 解决方案:
@Service
public class B {
@Lazy // 延迟初始化 A
@Autowired private A a;
}
🛡️ 五、预防胜于治疗 —— 日常开发最佳实践
| 场景 | 建议 |
|---|---|
| 空指针 | 使用 Objects.requireNonNull(), Optional, @NonNull 注解 |
| 资源关闭 | 一律使用 try-with-resources |
| 异常处理 | 不吞异常、记录完整堆栈、自定义异常带业务上下文 |
| 并发控制 | 优先使用 java.util.concurrent 包,避免手写 synchronized |
| 配置管理 | 使用 Spring Boot @ConfigurationProperties + 配置中心 |
| 依赖管理 | 定期 mvn dependency:analyze 清理无用依赖 |
| 日志规范 | 使用 SLF4J + Logback,格式:[类名] [方法] [关键参数] msg |
| 上线前检查 | 压测 + Arthas trace 热点方法 + SonarQube 扫描 |
📌 六、排错口诀(背下来!)
“一看日志二复现,
三用工具四隔离。
堆栈行号是路标,
最小代码定乾坤。
修复不忘加监控,
单元测试保太平。”
更多推荐

所有评论(0)