You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何定位Java中NotSerializableException的触发根源?

这种栈跟踪完全没指向自己代码的情况确实挺烦人的,我有几个实用的办法帮你快速定位到藏着BufferedImage的对象:

方法1:自定义ObjectOutputStream跟踪序列化流程

这是最直观的方式,通过自定义输出流打印每一个被序列化的对象信息,就能精准抓到触发异常的那个对象。

先写一个跟踪用的输出流类:

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

public class TrackingObjectOutputStream extends ObjectOutputStream {
    public TrackingObjectOutputStream(OutputStream out) throws IOException {
        super(out);
    }

    @Override
    protected void writeObject(Object obj) throws IOException {
        // 打印当前序列化对象的类名和实例哈希值,方便区分不同实例
        System.out.println("正在序列化: " + obj.getClass().getName() + " (实例ID: " + System.identityHashCode(obj) + ")");
        super.writeObject(obj);
    }

    @Override
    protected void writeUnshared(Object obj) throws IOException {
        System.out.println("正在序列化(非共享): " + obj.getClass().getName());
        super.writeUnshared(obj);
    }
}

然后修改你的序列化工具方法,替换原来的ObjectOutputStream:

public static void serialize(Object o, String dirAndFileName) throws FileNotFoundException, IOException {
    FileOutputStream fileOut = new FileOutputStream(dirAndFileName);
    TrackingObjectOutputStream out = new TrackingObjectOutputStream(fileOut);
    out.writeObject(o);
    out.close();
}

运行测试后,控制台会打印所有被序列化的对象,最后一个打印出来的类就是触发异常的源头,你直接去检查这个类的字段就能找到BufferedImage了。

方法2:用反射直接排查目标对象

既然你已经定位到是passEffect序列化时出错,直接用反射递归遍历它的所有字段,一步找出藏着BufferedImage的地方。

写一个调试工具方法:

import java.awt.image.BufferedImage;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;

public class SerializationDebugger {
    // 用Set避免循环引用导致无限递归
    private static final Set<Object> checkedObjects = new HashSet<>();

    public static void findBufferedImage(Object obj) throws IllegalAccessException {
        if (obj == null || checkedObjects.contains(obj)) {
            return;
        }
        checkedObjects.add(obj);
        Class<?> clazz = obj.getClass();

        // 遍历当前类的所有非静态、非transient字段
        for (Field field : clazz.getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
                continue;
            }
            field.setAccessible(true);
            Object fieldValue = field.get(obj);

            // 找到BufferedImage字段直接输出信息
            if (fieldValue instanceof BufferedImage) {
                System.out.println("找到BufferedImage字段: " + clazz.getName() + "." + field.getName());
                return;
            }
            // 递归检查引用类型的字段(跳过JDK自带类,减少冗余)
            if (fieldValue != null && !fieldValue.getClass().isPrimitive() && !fieldValue.getClass().getName().startsWith("java.")) {
                findBufferedImage(fieldValue);
            }
        }

        // 递归检查父类字段
        if (clazz.getSuperclass() != null && !clazz.getSuperclass().equals(Object.class)) {
            findBufferedImage(clazz.getSuperclass().cast(obj));
        }
    }
}

然后在测试代码里,序列化前先调用这个方法排查:

System.out.println("Saving test");
try {
    System.out.println("passeff");
    // 先检查passEffect对象
    SerializationDebugger.findBufferedImage(main.turnHandler.latestEvent.passEffect);
    cTools.Tools.serialize(main.turnHandler.latestEvent.passEffect, getSaveDir() + "t1");
    // ... 剩下的代码
} catch (IOException | IllegalAccessException ex) {
    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}

运行后控制台会直接输出包含BufferedImage的字段所在类和字段名,一步到位找到问题根源。

方法3:临时给BufferedImage加"假序列化"(应急调试用)

这个方法有点取巧,但能让栈跟踪显示更多上下文。你可以创建一个包装类让BufferedImage临时"可序列化",故意抛出带上下文的异常:

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SerializableBufferedImage extends BufferedImage implements Serializable {
    public SerializableBufferedImage(BufferedImage img) {
        super(img.getWidth(), img.getHeight(), img.getType());
        getGraphics().drawImage(img, 0, 0, null);
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        // 抛出带上下文的异常,指向当前对象的持有者
        throw new IOException("尝试序列化BufferedImage,来自对象: " + this.getClass().getName());
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        throw new IOException("反序列化不支持");
    }
}

临时把你怀疑的类里的BufferedImage字段替换成这个包装类,再运行序列化。这时抛出的异常会包含更多上下文信息,帮你定位到持有它的对象。注意这只是临时调试用,用完记得改回原代码。

推荐优先用方法1或方法2,这两个方法不需要修改业务代码,就能快速定位问题。

内容的提问来源于stack exchange,提问作者WVrock

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:06:07