Java运行时错误与异常全指南

11 阅读14分钟

一、Java运行时异常体系结构

1. Java异常类层次结构

Throwable
├── Error (严重错误,通常不需要捕获)
│   ├── VirtualMachineError
│   │   ├── OutOfMemoryError
│   │   ├── StackOverflowError
│   │   └── InternalError
│   ├── LinkageError
│   │   ├── NoClassDefFoundError
│   │   ├── ClassFormatError
│   │   └── IncompatibleClassChangeError
│   └── AssertionError
│
└── Exception
    ├── RuntimeException (运行时异常,无需强制捕获)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── ClassCastException
    │   ├── IllegalArgumentException
    │   ├── IllegalStateException
    │   ├── ConcurrentModificationException
    │   ├── UnsupportedOperationException
    │   ├── ArithmeticException
    │   └── NumberFormatException
    │
    └── 其他检查型异常 (必须处理)

二、常见运行时异常详解

1. 空指针异常 (NullPointerException, NPE)

错误示例

String str = null;
int length = str.length();  // NullPointerException

String[] array = null;
String first = array[0];     // NullPointerException

Map<String, String> map = null;
String value = map.get("key");  // NullPointerException

解决方案

// 1. 明确检查null
String str = getString();
if (str != null) {
    int length = str.length();
}

// 2. 使用Objects.requireNonNull (Java 7+)
import java.util.Objects;
String input = getInput();
String validated = Objects.requireNonNull(input, "Input cannot be null");

// 3. 使用Optional (Java 8+)
import java.util.Optional;
Optional<String> optionalStr = Optional.ofNullable(getString());
int length = optionalStr.map(String::length).orElse(0);

// 4. 使用默认值
String str = getString();
int length = (str != null) ? str.length() : 0;

// 5. 使用第三方库的null安全方法
// Apache Commons
import org.apache.commons.lang3.StringUtils;
if (StringUtils.isNotEmpty(str)) {
    int length = str.length();
}

// 6. 返回空集合而不是null
public List<String> getItems() {
    List<String> items = fetchItems();
    return items != null ? items : Collections.emptyList();
}

// 7. 防御性编程
public void process(@NonNull String input) {  // 使用注解
    // 编译器或静态分析工具会检查
}

使用Lombok的@NonNull注解

import lombok.NonNull;

public class UserService {
    public void createUser(@NonNull String username, @NonNull String email) {
        // 参数自动进行null检查
        // Lombok会在编译时生成检查代码
    }
}

2. 数组索引越界异常 (ArrayIndexOutOfBoundsException)

错误示例

int[] arr = {1, 2, 3};
int value = arr[3];  // 有效索引: 0-2,访问3会抛出异常

String[] names = new String[0];
String first = names[0];  // 数组为空

解决方案

// 1. 检查索引范围
int[] arr = {1, 2, 3};
int index = 3;
if (index >= 0 && index < arr.length) {
    int value = arr[index];
} else {
    // 处理越界情况
    throw new IllegalArgumentException("索引越界: " + index);
}

// 2. 使用for循环而不是手动索引
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);  // 安全
}

// 3. 使用增强for循环
for (int num : arr) {
    System.out.println(num);  // 不会越界
}

// 4. 处理空数组
String[] names = getNames();
if (names != null && names.length > 0) {
    String first = names[0];
}

// 5. 使用List代替数组
List<Integer> list = Arrays.asList(1, 2, 3);
if (index >= 0 && index < list.size()) {
    int value = list.get(index);
}

3. 类型转换异常 (ClassCastException)

错误示例

Object obj = "Hello";
Integer num = (Integer) obj;  // ClassCastException: String cannot be cast to Integer

List rawList = new ArrayList();
rawList.add("String");
rawList.add(123);  // 混用类型
for (Object item : rawList) {
    String str = (String) item;  // 对123会抛出异常
}

解决方案

// 1. 使用instanceof检查
Object obj = getObject();
if (obj instanceof String) {
    String str = (String) obj;
    System.out.println(str.length());
} else if (obj instanceof Integer) {
    Integer num = (Integer) obj;
    System.out.println(num + 1);
}

// 2. 使用泛型避免类型转换
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// 不需要类型转换
for (String str : stringList) {
    System.out.println(str.length());
}

// 3. 使用模式匹配 (Java 16+)
Object obj = getObject();
if (obj instanceof String str) {  // 自动转换
    System.out.println(str.length());
} else if (obj instanceof Integer num) {
    System.out.println(num + 1);
}

// 4. 使用Class.cast()方法
Class<?> targetType = String.class;
Object obj = "Hello";
if (targetType.isInstance(obj)) {
    String str = targetType.cast(obj);
}

// 5. 安全的转换工具方法
public static <T> T safeCast(Object obj, Class<T> clazz) {
    if (clazz.isInstance(obj)) {
        return clazz.cast(obj);
    }
    return null;
}

String str = safeCast(obj, String.class);
if (str != null) {
    // 安全使用
}

4. 并发修改异常 (ConcurrentModificationException)

错误示例

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : list) {  // 使用迭代器
    if ("B".equals(item)) {
        list.remove(item);  // ConcurrentModificationException
    }
}

解决方案

// 1. 使用迭代器的remove方法
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if ("B".equals(item)) {
        iterator.remove();  // 安全
    }
}

// 2. 使用Java 8+的removeIf方法
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
list.removeIf(item -> "B".equals(item));  // 线程安全

// 3. 创建副本进行迭代
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> copy = new ArrayList<>(list);
for (String item : copy) {
    if ("B".equals(item)) {
        list.remove(item);  // 安全,因为迭代的是副本
    }
}

// 4. 使用for循环倒序遍历删除
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (int i = list.size() - 1; i >= 0; i--) {
    if ("B".equals(list.get(i))) {
        list.remove(i);
    }
}

// 5. 使用并发集合
import java.util.concurrent.CopyOnWriteArrayList;
List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : list) {  // 安全
    if ("B".equals(item)) {
        list.remove(item);
    }
}

5. 非法参数异常 (IllegalArgumentException)

错误示例

public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("无效的年龄: " + age);
    }
    this.age = age;
}

// BigDecimal的除法
BigDecimal dividend = new BigDecimal("10");
BigDecimal divisor = new BigDecimal("0");
BigDecimal result = dividend.divide(divisor);  // 抛出ArithmeticException

解决方案

// 1. 参数验证
public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("年龄不能为负数: " + age);
    }
    if (age > 150) {
        throw new IllegalArgumentException("年龄不合理: " + age);
    }
    this.age = age;
}

// 2. 使用验证库
// 使用Apache Commons Validator
import org.apache.commons.validator.routines.IntegerValidator;
IntegerValidator validator = IntegerValidator.getInstance();
if (!validator.isInRange(age, 0, 150)) {
    throw new IllegalArgumentException("无效的年龄: " + age);
}

// 3. 使用断言
public void setAge(int age) {
    assert age >= 0 : "年龄不能为负数";
    assert age <= 150 : "年龄不合理";
    this.age = age;
}
// 使用 -ea 启用断言

// 4. 安全的BigDecimal操作
BigDecimal dividend = new BigDecimal("10");
BigDecimal divisor = new BigDecimal("0");
if (divisor.compareTo(BigDecimal.ZERO) == 0) {
    throw new IllegalArgumentException("除数不能为零");
}
BigDecimal result = dividend.divide(divisor, 2, RoundingMode.HALF_UP);  // 指定精度

// 5. 使用Objects.requireNonNull检查null参数
public void process(String input) {
    Objects.requireNonNull(input, "输入参数不能为null");
    // 处理逻辑
}

6. 算术异常 (ArithmeticException)

错误示例

int a = 10;
int b = 0;
int result = a / b;  // ArithmeticException: / by zero

int min = Integer.MIN_VALUE;
int negated = -min;  // 溢出,实际还是MIN_VALUE
int result = Math.abs(min);  // 还是MIN_VALUE
int divided = min / -1;  // 溢出

解决方案

// 1. 检查除数
int a = 10;
int b = 0;
if (b != 0) {
    int result = a / b;
} else {
    throw new ArithmeticException("除数不能为零");
}

// 2. 使用BigInteger/BigDecimal
BigDecimal dividend = new BigDecimal("10");
BigDecimal divisor = new BigDecimal("3");
BigDecimal result = dividend.divide(divisor, 2, RoundingMode.HALF_UP);

// 3. 处理整数溢出
public int safeAdd(int a, int b) {
    long result = (long) a + (long) b;
    if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) {
        throw new ArithmeticException("整数溢出");
    }
    return (int) result;
}

// 4. 使用Math.*Exact方法 (Java 8+)
try {
    int result = Math.addExact(a, b);
    int product = Math.multiplyExact(a, b);
    int negated = Math.negateExact(a);
} catch (ArithmeticException e) {
    // 处理溢出
}

// 5. 使用无符号运算
int a = Integer.MIN_VALUE;
int b = Integer.divideUnsigned(a, 2);  // 无符号除法
long unsignedValue = Integer.toUnsignedLong(a);

7. 数字格式异常 (NumberFormatException)

错误示例

String str = "123abc";
int num = Integer.parseInt(str);  // NumberFormatException

String empty = "";
int value = Integer.parseInt(empty);  // NumberFormatException

String decimal = "12.34";
int intValue = Integer.parseInt(decimal);  // NumberFormatException

解决方案

// 1. 使用try-catch
String str = "123abc";
try {
    int num = Integer.parseInt(str);
} catch (NumberFormatException e) {
    System.err.println("无效的数字格式: " + str);
    // 使用默认值
    int num = 0;
}

// 2. 使用Scanner
String str = "123abc";
Scanner scanner = new Scanner(str);
if (scanner.hasNextInt()) {
    int num = scanner.nextInt();
} else {
    // 不是有效整数
}

// 3. 使用正则表达式验证
String str = "123abc";
if (str.matches("-?\d+")) {  // 匹配整数
    int num = Integer.parseInt(str);
}

// 4. 使用Apache Commons Lang
import org.apache.commons.lang3.math.NumberUtils;
String str = "123abc";
if (NumberUtils.isCreatable(str)) {
    int num = NumberUtils.createInteger(str);
}

// 5. 使用Java 8 Optional
import java.util.Optional;
String str = "123abc";
Optional<Integer> num = parseInteger(str);
num.ifPresentOrElse(
    n -> System.out.println("数字: " + n),
    () -> System.out.println("无效数字")
);

private Optional<Integer> parseInteger(String str) {
    try {
        return Optional.of(Integer.parseInt(str));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}

8. 不支持的操作异常 (UnsupportedOperationException)

错误示例

List<String> fixedList = Arrays.asList("A", "B", "C");
fixedList.add("D");  // UnsupportedOperationException

List<String> unmodifiable = Collections.unmodifiableList(new ArrayList<>());
unmodifiable.add("X");  // UnsupportedOperationException

解决方案

// 1. 创建可变副本
List<String> fixedList = Arrays.asList("A", "B", "C");
List<String> mutableList = new ArrayList<>(fixedList);
mutableList.add("D");  // 可以修改

// 2. 检查集合是否支持操作
List<String> list = getList();
if (list instanceof ArrayList) {  // 通常是可变的
    list.add("new item");
} else if (list.getClass().getSimpleName().contains("Unmodifiable")) {
    // 创建可变副本
    list = new ArrayList<>(list);
    list.add("new item");
}

// 3. 使用正确的集合类型
// 如果需要可变列表
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
list.add("D");  // 可以

// 如果需要不可变列表(Java 9+)
List<String> immutable = List.of("A", "B", "C");  // 完全不可变

// 4. 自定义集合
class CustomList<E> extends AbstractList<E> {
    private final List<E> delegate = new ArrayList<>();
    
    @Override
    public E get(int index) {
        return delegate.get(index);
    }
    
    @Override
    public int size() {
        return delegate.size();
    }
    
    // 实现add、remove等需要的方法
    @Override
    public boolean add(E e) {
        return delegate.add(e);
    }
}

9. 非法状态异常 (IllegalStateException)

错误示例

public class Connection {
    private boolean open = false;
    
    public void sendData(String data) {
        if (!open) {
            throw new IllegalStateException("连接未打开");
        }
        // 发送数据
    }
}

// Iterator使用
List<String> list = Arrays.asList("A", "B");
Iterator<String> it = list.iterator();
it.next();
it.remove();  // 可以
it.remove();  // IllegalStateException: 没有调用next

解决方案

// 1. 状态验证
public class Connection implements AutoCloseable {
    private boolean open = false;
    
    public void open() {
        if (open) {
            throw new IllegalStateException("连接已打开");
        }
        open = true;
    }
    
    public void sendData(String data) {
        ensureOpen();
        // 发送数据
    }
    
    private void ensureOpen() {
        if (!open) {
            throw new IllegalStateException("连接未打开");
        }
    }
    
    @Override
    public void close() {
        open = false;
    }
}

// 2. 正确的Iterator使用
List<String> list = new ArrayList<>(Arrays.asList("A", "B"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    if (shouldRemove(item)) {
        it.remove();  // 正确:每个remove前都有next
    }
}

// 3. 使用状态模式
interface ConnectionState {
    void sendData(String data);
    void open();
    void close();
}

class ClosedState implements ConnectionState {
    private final Connection connection;
    
    ClosedState(Connection connection) {
        this.connection = connection;
    }
    
    @Override
    public void sendData(String data) {
        throw new IllegalStateException("连接已关闭");
    }
    
    @Override
    public void open() {
        connection.setState(new OpenState(connection));
    }
    
    @Override
    public void close() {
        // 已经是关闭状态
    }
}

三、严重错误 (Errors)

1. 内存溢出错误 (OutOfMemoryError)

原因

  • 堆内存不足
  • 永久代/元空间不足
  • 栈内存不足
  • 创建过多线程

解决方案

// 1. 增加JVM堆内存
// java -Xms512m -Xmx2048m -XX:MaxMetaspaceSize=256m MyApp

// 2. 分析内存泄漏
// 使用MAT (Memory Analyzer Tool) 或 VisualVM

// 3. 使用软引用/弱引用缓存
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

// 软引用:内存不足时会被回收
SoftReference<BigObject> softRef = new SoftReference<>(new BigObject());

// 弱引用:GC时立即回收
WeakReference<BigObject> weakRef = new WeakReference<>(new BigObject());

// 4. 及时释放资源
try (InputStream is = new FileInputStream("largefile.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
    // 处理文件
}  // 自动关闭

// 5. 使用对象池
class ObjectPool<T> {
    private final Queue<T> pool = new ConcurrentLinkedQueue<>();
    private final Supplier<T> creator;
    
    public ObjectPool(Supplier<T> creator, int size) {
        this.creator = creator;
        for (int i = 0; i < size; i++) {
            pool.offer(creator.get());
        }
    }
    
    public T borrow() {
        T obj = pool.poll();
        return obj != null ? obj : creator.get();
    }
    
    public void returnObject(T obj) {
        pool.offer(obj);
    }
}

JVM参数调优

# 堆内存设置
-Xms512m              # 初始堆大小
-Xmx2048m             # 最大堆大小
-Xmn256m              # 新生代大小
-XX:NewRatio=3        # 新生代:老年代 = 1:3

# 元空间设置
-XX:MaxMetaspaceSize=256m
-XX:MetaspaceSize=128m

# 栈大小设置
-Xss1m                # 每个线程栈大小

# GC设置
-XX:+UseG1GC          # 使用G1垃圾收集器
-XX:+UseConcMarkSweepGC  # 使用CMS收集器
-XX:+PrintGCDetails   # 打印GC详情
-XX:+HeapDumpOnOutOfMemoryError  # OOM时生成堆转储

2. 栈溢出错误 (StackOverflowError)

错误示例

// 递归无终止条件
public int infiniteRecursion(int n) {
    return infiniteRecursion(n + 1);  // StackOverflowError
}

// 相互递归
public void methodA() {
    methodB();
}

public void methodB() {
    methodA();  // 相互调用导致栈溢出
}

解决方案

// 1. 添加递归终止条件
public int factorial(int n) {
    if (n <= 1) {  // 终止条件
        return 1;
    }
    return n * factorial(n - 1);
}

// 2. 使用迭代代替递归
public int factorialIterative(int n) {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

// 3. 使用尾递归优化
public int factorialTailRecursive(int n, int accumulator) {
    if (n <= 1) {
        return accumulator;
    }
    return factorialTailRecursive(n - 1, n * accumulator);
}
// 注意:Java不直接支持尾调用优化,但有些JVM会尝试

// 4. 增加栈大小
// java -Xss2m MyApp  # 设置栈大小为2MB

// 5. 使用循环和栈数据结构
public void depthFirstSearch(Node root) {
    if (root == null) return;
    
    Deque<Node> stack = new ArrayDeque<>();
    stack.push(root);
    
    while (!stack.isEmpty()) {
        Node node = stack.pop();
        process(node);
        
        for (Node child : node.getChildren()) {
            stack.push(child);
        }
    }
}

3. 类定义未找到错误 (NoClassDefFoundError)

原因

  • 类路径中缺少类文件
  • 静态初始化失败
  • 版本不兼容

解决方案

// 1. 检查类路径
System.out.println("Classpath: " + System.getProperty("java.class.path"));

// 2. 使用正确的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
    Class<?> clazz = classLoader.loadClass("com.example.MyClass");
} catch (ClassNotFoundException e) {
    // 处理类找不到的情况
}

// 3. 使用Maven/Gradle管理依赖
// pom.xml
<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>mylibrary</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

// 4. 检查静态初始化块
public class ProblematicClass {
    static {
        // 如果这里抛出异常,会导致NoClassDefFoundError
        if (System.currentTimeMillis() % 2 == 0) {
            throw new RuntimeException("静态初始化失败");
        }
    }
}

// 解决方案:将初始化移到静态方法中
public class SafeClass {
    private static volatile boolean initialized = false;
    
    public static synchronized void init() {
        if (!initialized) {
            // 初始化代码
            initialized = true;
        }
    }
}

四、并发编程运行时错误

1. 死锁 (Deadlock)

public class DeadlockExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void method1() {
        synchronized (lock1) {
            synchronized (lock2) {  // 可能阻塞
                // 临界区
            }
        }
    }
    
    public void method2() {
        synchronized (lock2) {
            synchronized (lock1) {  // 可能阻塞
                // 临界区
            }
        }
    }
}

解决方案

// 1. 按相同顺序获取锁
public class SafeLocking {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void method1() {
        synchronized (lock1) {
            synchronized (lock2) {
                // 临界区
            }
        }
    }
    
    public void method2() {
        synchronized (lock1) {  // 相同顺序
            synchronized (lock2) {
                // 临界区
            }
        }
    }
}

// 2. 使用ReentrantLock和tryLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TryLockExample {
    private final Lock lock1 = new ReentrantLock();
    private final Lock lock2 = new ReentrantLock();
    
    public boolean tryMethod() {
        boolean gotLock1 = false;
        boolean gotLock2 = false;
        
        try {
            gotLock1 = lock1.tryLock(100, TimeUnit.MILLISECONDS);
            gotLock2 = lock2.tryLock(100, TimeUnit.MILLISECONDS);
            
            if (gotLock1 && gotLock2) {
                // 成功获取两个锁
                return true;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (gotLock1) lock1.unlock();
            if (gotLock2) lock2.unlock();
        }
        return false;
    }
}

// 3. 使用Lock.tryLock()和超时
public void methodWithTimeout() throws InterruptedException {
    if (lock1.tryLock(1, TimeUnit.SECONDS)) {
        try {
            if (lock2.tryLock(1, TimeUnit.SECONDS)) {
                try {
                    // 成功获取两个锁
                } finally {
                    lock2.unlock();
                }
            }
        } finally {
            lock1.unlock();
        }
    }
}

// 4. 使用死锁检测
// 在代码审查时检查锁的顺序
// 使用jstack或VisualVM检测运行时的死锁

使用jstack检测死锁

# 1. 查找Java进程ID
jps

# 2. 生成线程转储
jstack <pid> > thread_dump.txt

# 3. 搜索"deadlock"关键字

2. 线程安全问题

// 错误示例:非线程安全的单例
public class UnsafeSingleton {
    private static UnsafeSingleton instance;
    
    public static UnsafeSingleton getInstance() {
        if (instance == null) {  // 竞争条件
            instance = new UnsafeSingleton();
        }
        return instance;
    }
}

解决方案

// 1. 同步方法
public class SynchronizedSingleton {
    private static SynchronizedSingleton instance;
    
    public static synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}

// 2. 双重检查锁定 (正确版本)
public class DoubleCheckedSingleton {
    private static volatile DoubleCheckedSingleton instance;
    
    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {  // 第二次检查
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

// 3. 静态内部类 (推荐)
public class StaticHolderSingleton {
    private StaticHolderSingleton() {}
    
    private static class Holder {
        static final StaticHolderSingleton INSTANCE = new StaticHolderSingleton();
    }
    
    public static StaticHolderSingleton getInstance() {
        return Holder.INSTANCE;
    }
}

// 4. 枚举单例 (最佳实践)
public enum EnumSingleton {
    INSTANCE;
    
    public void doSomething() {
        // ...
    }
}

// 5. 使用java.util.concurrent类
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private final AtomicInteger count = new AtomicInteger(0);
    private final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
    
    public void increment() {
        count.incrementAndGet();  // 线程安全
    }
    
    public void addToMap(String key, String value) {
        map.put(key, value);  // 线程安全
    }
}

五、调试和诊断工具

1. JVM监控工具

# 1. jps - 查看Java进程
jps -l

# 2. jstack - 线程转储
jstack <pid> > thread_dump.txt

# 3. jmap - 内存分析
jmap -heap <pid>  # 堆信息
jmap -histo <pid>  # 直方图
jmap -dump:format=b,file=heap.bin <pid>  # 堆转储

# 4. jstat - 统计信息
jstat -gc <pid> 1000 10  # 每1秒收集一次GC信息,共10次

# 5. jcmd - 多功能命令
jcmd <pid> VM.version
jcmd <pid> GC.class_histogram
jcmd <pid> Thread.print
jcmd <pid> GC.heap_dump filename=heap.hprof

2. VisualVM使用

// 启用JMX监控
// java -Dcom.sun.management.jmxremote \
//      -Dcom.sun.management.jmxremote.port=9010 \
//      -Dcom.sun.management.jmxremote.ssl=false \
//      -Dcom.sun.management.jmxremote.authenticate=false \
//      MyApp

3. Java Flight Recorder (JFR)

# 启用JFR
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder MyApp

# 记录JFR
jcmd <pid> JFR.start name=myrecording duration=60s filename=recording.jfr
jcmd <pid> JFR.dump name=myrecording filename=recording.jfr
jcmd <pid> JFR.stop name=myrecording

六、防御性编程和最佳实践

1. 输入验证

public class InputValidator {
    public static String validateUsername(String username) {
        if (username == null) {
            throw new IllegalArgumentException("用户名不能为null");
        }
        
        username = username.trim();
        
        if (username.isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        
        if (username.length() < 3 || username.length() > 20) {
            throw new IllegalArgumentException("用户名长度必须在3-20之间");
        }
        
        if (!username.matches("^[a-zA-Z0-9_]+$")) {
            throw new IllegalArgumentException("用户名只能包含字母、数字和下划线");
        }
        
        return username;
    }
    
    public static int validateAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄必须在0-150之间");
        }
        return age;
    }
}

2. 使用不可变对象

// 不可变类
public final class ImmutablePerson {
    private final String name;
    private final int age;
    private final List<String> hobbies;
    
    public ImmutablePerson(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
    public List<String> getHobbies() { return hobbies; }  // 不可修改
}

3. 资源管理

// 使用try-with-resources
public void processFile(String filename) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(filename));
         BufferedWriter writer = new BufferedWriter(new FileWriter(filename + ".out"))) {
        
        String line;
        while ((line = reader.readLine()) != null) {
            String processed = processLine(line);
            writer.write(processed);
            writer.newLine();
        }
    }  // 自动关闭资源
}

// 使用AutoCloseable
class DatabaseConnection implements AutoCloseable {
    private Connection connection;
    
    public DatabaseConnection(String url) throws SQLException {
        this.connection = DriverManager.getConnection(url);
    }
    
    public void execute(String sql) throws SQLException {
        try (Statement stmt = connection.createStatement()) {
            stmt.execute(sql);
        }
    }
    
    @Override
    public void close() throws SQLException {
        if (connection != null && !connection.isClosed()) {
            connection.close();
        }
    }
}

七、异常处理最佳实践

1. 正确的异常处理

public class ExceptionHandling {
    // 1. 捕获具体异常
    public void processFile(String filename) {
        try {
            readFile(filename);
        } catch (FileNotFoundException e) {
            // 文件不存在
            logger.error("文件不存在: {}", filename, e);
            throw new BusinessException("文件不存在", e);
        } catch (IOException e) {
            // IO错误
            logger.error("读取文件失败: {}", filename, e);
            throw new BusinessException("读取失败", e);
        } catch (Exception e) {
            // 其他异常
            logger.error("处理文件时发生未知错误: {}", filename, e);
            throw new BusinessException("处理失败", e);
        }
    }
    
    // 2. 使用finally清理资源
    public void processResource() {
        Resource resource = null;
        try {
            resource = acquireResource();
            useResource(resource);
        } catch (ResourceException e) {
            handleException(e);
        } finally {
            if (resource != null) {
                try {
                    resource.close();
                } catch (Exception e) {
                    logger.warn("关闭资源时出错", e);
                }
            }
        }
    }
    
    // 3. 不要吞掉异常
    public void badPractice() {
        try {
            doSomething();
        } catch (Exception e) {
            // 错误:吞掉异常,没有记录或重新抛出
        }
    }
    
    // 4. 使用自定义异常
    public class BusinessException extends RuntimeException {
        private final String errorCode;
        
        public BusinessException(String errorCode, String message) {
            super(message);
            this.errorCode = errorCode;
        }
        
        public BusinessException(String errorCode, String message, Throwable cause) {
            super(message, cause);
            this.errorCode = errorCode;
        }
        
        public String getErrorCode() { return errorCode; }
    }
}

八、常见运行时错误速查表

错误类型常见原因预防措施工具检测
NullPointerException解引用null使用Optional,检查nullSpotBugs, NullAway
ArrayIndexOutOfBoundsException数组越界检查索引范围代码审查,测试
ClassCastException类型转换错误使用instanceof检查使用泛型
ConcurrentModificationException并发修改集合使用迭代器remove,CopyOnWriteArrayList测试
IllegalArgumentException无效参数参数验证单元测试
IllegalStateException状态不正确状态检查状态机验证
OutOfMemoryError内存不足内存管理,使用缓存VisualVM, MAT
StackOverflowError递归过深迭代代替递归栈大小分析
NoClassDefFoundError类路径问题依赖管理Maven/Gradle
Deadlock锁顺序问题固定锁顺序jstack, VisualVM
NumberFormatException格式错误验证输入,使用try-catch输入验证
ArithmeticException除以零等检查除数数学库函数

九、工具和库推荐

1. 静态分析工具

# SpotBugs (原FindBugs)
mvn spotbugs:check

# Checkstyle
mvn checkstyle:check

# PMD
mvn pmd:check

# Error Prone
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <compilerArgs>
                    <arg>-XDcompilePolicy=simple</arg>
                    <arg>-Xplugin:ErrorProne</arg>
                </compilerArgs>
                <annotationProcessorPaths>
                    <path>
                        <groupId>com.google.errorprone</groupId>
                        <artifactId>error_prone_core</artifactId>
                        <version>2.10.0</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2. 测试框架

// JUnit 5测试异常
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

@Test
void testException() {
    Exception exception = assertThrows(
        IllegalArgumentException.class,
        () -> validator.validateUsername(null)
    );
    
    assertEquals("用户名不能为null", exception.getMessage());
}

// 使用AssertJ
import static org.assertj.core.api.Assertions.*;

@Test
void testWithAssertJ() {
    assertThatThrownBy(() -> validator.validateUsername(""))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessage("用户名不能为空");
}

3. 日志框架

// 使用SLF4J + Logback
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyService {
    private static final Logger logger = LoggerFactory.getLogger(MyService.class);
    
    public void process() {
        try {
            doSomething();
        } catch (Exception e) {
            logger.error("处理失败: {}", e.getMessage(), e);
            // 结构化日志
            logger.error("处理失败: userId={}, action={}", userId, action, e);
        }
    }
}

记住:预防胜于治疗。良好的编码习惯、充分的测试、代码审查和静态分析可以避免大多数运行时错误。当错误发生时,清晰的日志记录和适当的异常处理可以帮助快速定位和解决问题。