Java问题排查汇总(附示例与解法)
一、高频问题:编译与运行时异常(60%+)
1. 空指针异常(NullPointerException)
// 错误示例1 public class Main { public static void main(String[] args) { String str = null; System.out.println(str.length()); // 触发NullPointerException } } // 错误示例2 public class User { private String name; public void printName() { System.out.println(name.toUpperCase()); // name为null } }解决方法:
// 方法1:提前判空 String str = null; if (str != null) { System.out.println(str.length()); } else { System.out.println("字符串为空"); } // 方法2:使用三元运算符 String result = (str != null) ? str : "默认值"; System.out.println(result.length()); // 方法3:Java 8+ Optional类 import java.util.Optional; String str = null; Optional.ofNullable(str) .ifPresent(s -> System.out.println(s.length())); // 方法4:防御性编程 public class User { private String name = ""; // 初始化默认值 public void printName() { if (name != null) { System.out.println(name.toUpperCase()); } } public void setName(String name) { this.name = (name == null) ? "" : name; // 入参校验 } }2. 数组越界(ArrayIndexOutOfBoundsException)
// 错误示例 public class Main { public static void main(String[] args) { int[] arr = {1, 2, 3}; System.out.println(arr[5]); // 数组长度3,访问索引5 } }解决方法:
// 方法1:边界检查 int[] arr = {1, 2, 3}; int index = 5; if (index >= 0 && index < arr.length) { System.out.println(arr[index]); } else { System.out.println("索引越界: " + index); } // 方法2:使用增强for循环 for (int num : arr) { System.out.println(num); // 避免手动索引 } // 方法3:安全访问工具方法 public class ArrayUtils { public static <T> T safeGet(T[] array, int index) { if (array == null || index < 0 || index >= array.length) { return null; } return array[index]; } }3. 类型转换异常(ClassCastException)
// 错误示例 public class Main { public static void main(String[] args) { Object obj = "Hello"; Integer num = (Integer) obj; // String无法转为Integer } }解决方法:
// 方法1:使用instanceof检查 Object obj = "Hello"; if (obj instanceof Integer) { Integer num = (Integer) obj; System.out.println(num); } else if (obj instanceof String) { String str = (String) obj; System.out.println(str); } // 方法2:安全转换工具 public class CastUtils { public static <T> T safeCast(Object obj, Class<T> clazz) { if (obj != null && clazz.isInstance(obj)) { return clazz.cast(obj); } return null; } public static Integer toInteger(Object obj) { if (obj == null) return null; if (obj instanceof Integer) { return (Integer) obj; } if (obj instanceof Number) { return ((Number) obj).intValue(); } if (obj instanceof String) { try { return Integer.parseInt((String) obj); } catch (NumberFormatException e) { return null; } } return null; } }4. 数字格式异常(NumberFormatException)
// 错误示例 public class Main { public static void main(String[] args) { String str = "abc123"; int num = Integer.parseInt(str); // 包含非数字字符 } }解决方法:
// 方法1:捕获异常 String str = "abc123"; try { int num = Integer.parseInt(str); } catch (NumberFormatException e) { System.out.println("无效的数字格式: " + str); int num = 0; // 设置默认值 } // 方法2:正则表达式验证 public class NumberUtils { public static boolean isNumeric(String str) { if (str == null || str.trim().isEmpty()) { return false; } return str.matches("-?\\d+(\\.\\d+)?"); } public static Integer parseIntegerSafe(String str) { if (str == null) return null; try { return Integer.parseInt(str.trim()); } catch (NumberFormatException e) { return null; } } } // 使用示例 String str = "123"; if (NumberUtils.isNumeric(str)) { int num = Integer.parseInt(str); }二、中频问题:IO、资源与逻辑错误(30%+)
1. 文件未找到(FileNotFoundException)
// 错误示例 import java.io.FileReader; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { FileReader reader = new FileReader("不存在的文件.txt"); // 使用reader reader.close(); } }解决方法:
import java.io.File; import java.io.FileReader; import java.io.IOException; public class Main { public static void main(String[] args) { // 方法1:先检查文件是否存在 File file = new File("data.txt"); if (file.exists() && file.isFile()) { try (FileReader reader = new FileReader(file)) { // 使用reader } catch (IOException e) { e.printStackTrace(); } } else { System.out.println("文件不存在: " + file.getAbsolutePath()); } // 方法2:使用try-with-resources自动关闭资源 try (FileReader reader = new FileReader("data.txt")) { // 使用reader } catch (IOException e) { System.out.println("读取文件失败: " + e.getMessage()); // 创建默认文件或使用默认值 } } }2. 资源未关闭导致内存泄漏
// 错误示例 public class ResourceLeak { public void readFile() throws IOException { FileInputStream fis = new FileInputStream("largefile.bin"); // 处理文件 // 忘记调用 fis.close(); } }解决方法:
// 方法1:try-with-resources(Java 7+) public void readFile() { try (FileInputStream fis = new FileInputStream("largefile.bin"); BufferedInputStream bis = new BufferedInputStream(fis)) { // 自动关闭资源 } catch (IOException e) { e.printStackTrace(); } } // 方法2:finally块确保关闭 public void readFile() { FileInputStream fis = null; try { fis = new FileInputStream("largefile.bin"); // 处理文件 } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } }3. 并发修改异常(ConcurrentModificationException)
// 错误示例 import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); for (String item : list) { // 增强for循环使用迭代器 if ("B".equals(item)) { list.remove(item); // 在遍历时修改列表 } } } }解决方法:
import java.util.*; public class Main { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); // 方法1:使用迭代器的remove方法 Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if ("B".equals(item)) { iterator.remove(); // 使用迭代器删除 } } // 方法2:Java 8+ removeIf list.removeIf(item -> "B".equals(item)); // 方法3:记录要删除的元素,遍历后删除 List<String> toRemove = new ArrayList<>(); for (String item : list) { if ("B".equals(item)) { toRemove.add(item); } } list.removeAll(toRemove); // 方法4:使用CopyOnWriteArrayList(线程安全,适合读多写少) List<String> safeList = new CopyOnWriteArrayList<>(list); for (String item : safeList) { if ("B".equals(item)) { safeList.remove(item); } } } }三、低频但棘手问题(<10%)
1. 内存溢出(OutOfMemoryError)
// 错误示例 import java.util.ArrayList; import java.util.List; public class MemoryLeak { private static List<byte[]> cache = new ArrayList<>(); public void processData() { while (true) { cache.add(new byte[1024 * 1024]); // 每次添加1MB } } }解决方法:
// 方法1:使用JVM参数调优 /* 启动参数示例: -Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -Xms512m: 初始堆大小512MB -Xmx1024m: 最大堆大小1024MB -XX:+HeapDumpOnOutOfMemoryError: 内存溢出时生成堆转储文件 */ // 方法2:使用弱引用或软引用 import java.lang.ref.WeakReference; import java.util.Map; import java.util.WeakHashMap; public class CacheManager { // 使用WeakHashMap,当键不再被强引用时,自动回收 private Map<Object, Object> cache = new WeakHashMap<>(); // 使用SoftReference,内存不足时才回收 private SoftReference<byte[]> dataCache; } // 方法3:监控内存使用 public class MemoryMonitor { public static void printMemoryInfo() { Runtime runtime = Runtime.getRuntime(); long maxMemory = runtime.maxMemory(); long totalMemory = runtime.totalMemory(); long freeMemory = runtime.freeMemory(); long usedMemory = totalMemory - freeMemory; System.out.printf("最大内存: %.2f MB\n", maxMemory / 1024.0 / 1024.0); System.out.printf("已用内存: %.2f MB\n", usedMemory / 1024.0 / 1024.0); System.out.printf("可用内存: %.2f MB\n", freeMemory / 1024.0 / 1024.0); } }2. 死锁(Deadlock)
// 错误示例 public class DeadlockExample { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void method1() { synchronized (lock1) { System.out.println("线程1获得lock1"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock2) { System.out.println("线程1获得lock2"); } } } public void method2() { synchronized (lock2) { System.out.println("线程2获得lock2"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock1) { System.out.println("线程2获得lock1"); } } } }解决方法:
// 方法1:按相同顺序获取锁 public class DeadlockSolution { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void method1() { synchronized (lock1) { System.out.println("线程1获得lock1"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock2) { System.out.println("线程1获得lock2"); } } } public void method2() { synchronized (lock1) { // 改为先获取lock1 System.out.println("线程2获得lock1"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock2) { System.out.println("线程2获得lock2"); } } } } // 方法2:使用tryLock避免死锁 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TryLockSolution { private final Lock lock1 = new ReentrantLock(); private final Lock lock2 = new ReentrantLock(); public void method1() { while (true) { if (lock1.tryLock()) { try { System.out.println("线程1获得lock1"); if (lock2.tryLock()) { try { System.out.println("线程1获得lock2"); break; // 成功获取两个锁 } finally { lock2.unlock(); } } } finally { lock1.unlock(); } } // 未获取到锁,短暂休眠后重试 try { Thread.sleep(10); } catch (InterruptedException e) {} } } }3. 线程安全问题
// 错误示例 public class Counter { private int count = 0; public void increment() { count++; // 非原子操作 } public int getCount() { return count; } }解决方法:
// 方法1:使用synchronized public class SynchronizedCounter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } // 方法2:使用AtomicInteger import java.util.concurrent.atomic.AtomicInteger; public class AtomicCounter { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } public int getCount() { return count.get(); } } // 方法3:使用ReentrantLock import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockCounter { private int count = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }四、Java问题排查工具箱
1. 命令行工具
# 查看Java进程 jps -l # 查看堆内存使用 jmap -heap <pid> # 生成堆转储文件 jmap -dump:format=b,file=heapdump.hprof <pid> # 线程转储 jstack <pid> > threaddump.txt # 查看GC情况 jstat -gc <pid> 1000 10 # 每秒一次,共10次2. 使用JVM参数进行调试
# 开启调试信息 java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar app.jar # 内存溢出时生成堆转储 java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./oom.hprof -jar app.jar # 设置堆大小 java -Xms512m -Xmx2g -Xmn256m -jar app.jar3. 使用JMX监控
// 在启动参数中添加 // -Dcom.sun.management.jmxremote // -Dcom.sun.management.jmxremote.port=9010 // -Dcom.sun.management.jmxremote.authenticate=false // -Dcom.sun.management.jmxremote.ssl=false4. 使用日志记录
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Service { private static final Logger logger = LoggerFactory.getLogger(Service.class); public void process() { logger.debug("开始处理"); try { // 业务逻辑 logger.info("处理成功"); } catch (Exception e) { logger.error("处理失败", e); // 记录完整异常栈 } } }5. 断点调试技巧
// 条件断点 public void process(List<User> users) { for (User user : users) { // 在下一行设置条件断点:user.getName().equals("张三") System.out.println(user.getName()); } } // 异常断点 // 在IDE中设置捕获特定异常时自动暂停快速排查流程图
问题发生 ↓ 查看控制台输出 ↓ 阅读异常堆栈(关键信息:异常类型、位置、原因) ↓ 复现问题(构造最小可复现代码) ↓ 断点调试(单步执行,观察变量变化) ↓ 日志分析(查看相关日志) ↓ 工具分析(jstack、jmap、jvisualvm) ↓ 代码审查(检查逻辑错误、并发问题) ↓ 测试验证(确保修复后不再出现问题)黄金法则:
优先阅读异常信息,Java的异常信息通常很详细
使用try-with-resources管理资源
对可能为null的对象进行判空
多线程环境下使用线程安全的集合
定期检查代码中的内存泄漏点
