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

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

1. Java异常类层次结构

php 复制代码
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)

错误示例

ini 复制代码
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

解决方案

java 复制代码
// 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注解

less 复制代码
import lombok.NonNull;

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

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

错误示例

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

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

解决方案

ini 复制代码
// 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)

错误示例

ini 复制代码
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会抛出异常
}

解决方案

ini 复制代码
// 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)

错误示例

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

解决方案

ini 复制代码
// 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)

错误示例

java 复制代码
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

解决方案

java 复制代码
// 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)

错误示例

ini 复制代码
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;  // 溢出

解决方案

ini 复制代码
// 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)

错误示例

ini 复制代码
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

解决方案

ini 复制代码
// 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)

错误示例

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

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

解决方案

csharp 复制代码
// 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)

错误示例

arduino 复制代码
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

解决方案

typescript 复制代码
// 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)

原因

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

解决方案

java 复制代码
// 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参数调优

ruby 复制代码
# 堆内存设置
-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)

错误示例

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

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

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

解决方案

arduino 复制代码
// 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)

原因

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

解决方案

typescript 复制代码
// 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)

java 复制代码
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) {  // 可能阻塞
                // 临界区
            }
        }
    }
}

解决方案

java 复制代码
// 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检测死锁

shell 复制代码
# 1. 查找Java进程ID
jps

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

# 3. 搜索"deadlock"关键字

2. 线程安全问题

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

解决方案

java 复制代码
// 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监控工具

ini 复制代码
# 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使用

ini 复制代码
// 启用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)

ini 复制代码
# 启用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. 输入验证

typescript 复制代码
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. 使用不可变对象

arduino 复制代码
// 不可变类
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. 资源管理

java 复制代码
// 使用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. 正确的异常处理

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

九、工具和库推荐

1. 静态分析工具

xml 复制代码
# 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. 测试框架

typescript 复制代码
// 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. 日志框架

java 复制代码
// 使用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);
        }
    }
}

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

相关推荐
Mintopia4 小时前
诗词如何影响人:从认知机制到可落地的文本分析技术路线
前端·代码规范
C澒18 小时前
React + TypeScript 编码规范|统一标准 & 高效维护
前端·react.js·typescript·团队开发·代码规范
桦说编程1 天前
我把 CompletableFuture 踩坑经验写成了 AI Skill,比自己写代码还靠谱
java·ai编程·代码规范
NineData1 天前
NineData 社区版 V4.10.0 正式发布
数据库·mysql·代码规范
visual_zhang1 天前
iOS NotificationCenter Observer 的隐性性能代价
性能优化·代码规范
电子科技圈1 天前
IAR扩展嵌入式开发平台,推出面向安全关键型应用的长期支持(LTS)服务
嵌入式硬件·安全·设计模式·软件工程·代码规范·设计规范·代码复审
桦说编程3 天前
Harness Engineering — AI 时代的工程最佳实践
人工智能·架构·代码规范
昭昭日月明3 天前
搭建高可用私有 NPM 镜像
node.js·代码规范
KaneLogger4 天前
AI 时代编程范式迁移的思考
人工智能·程序员·代码规范