返回顶部
首页 > 资讯 > 精选 >JDK序列化Bug难题如何解决
  • 451
分享到

JDK序列化Bug难题如何解决

2023-07-05 13:07:41 451人浏览 独家记忆
摘要

这篇文章主要讲解了“jdk序列化Bug难题如何解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“JDK序列化Bug难题如何解决”吧!1、背景最近查看应用的崩溃记录的时候遇到了一个跟 Java

这篇文章主要讲解了“jdk序列化Bug难题如何解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“JDK序列化Bug难题如何解决”吧!

1、背景

最近查看应用的崩溃记录的时候遇到了一个跟 Java 序列化相关的崩溃,

JDK序列化Bug难题如何解决

从崩溃的堆栈来看,整个调用堆栈里没有我们自己的代码信息。崩溃的起点是 Android 系统自动存储 Fragment 的状态,也就是将数据序列化并写入 Bundle 时。最终出现问题的代码则位于 ArrayList 的 writeObject() 方法。

这里顺带说明一下,一般我们在使用序列化的时候只需要让自己的类实现 Serializable 接口即可,最多就是为自己的类增加一个名为 SerialVersionUID 的静态字段以标志序列化的版本号。但是,实际上序列化的过程是可以自定义的,也就是通过 writeObject()readObject() 实现。这两个方法看上去可能比较古怪,因为他们既不存在于 Object 类,也不存在于 Serializable 接口。所以,对它们没有覆写一说,并且还是 private 的。从上述堆栈也可以看出,调用这两个方法是通过反射的形式调用的。

2、分析

从堆栈看出来是序列化过程中报错,并且是因为 Fragment 状态自动保存过程中报错,报错的位置不在我们的代码中,无法也不应该使用 hook 的方式解决。

再从报错信息看,是多线程修改导致的,也就是因为 ArrayList 并不是线程安全的,所以,如果在调用序列化的过程中其他线程对 ArrayList 做了修改,那么此时就会抛出 ConcurrentModificationException 异常。

但是! 再进一步看,为了解决 ArrayList 在多线程环境中不安全的问题,我这里是用了同步容器进行包装。从堆栈也可以看出,堆栈中包含如下一行代码,

Collections$SynchronizedCollection.writeObject(Collections.java:2125)

这说明,整个序列化的操作是在同步代码块中执行的。而就在执行过程中,其他线程完成了对 ArrayList 的修改。

再看一下报错的 ArrayList 的代码,

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {    // Write out element count, and any hidden stuff    int expectedModCount = modCount; // 1    s.defaultWriteObject();    // Write out size as capacity for behavioural compatibility with clone()    s.writeInt(size);    // Write out all elements in the proper order.    for (int i=0; i<size; i++) {        s.writeObject(elementData[i]);    }    if (modCount != expectedModCount) { // 2        throw new ConcurrentModificationException();    }}

也就是说,在 writeObject 这个方法执行 1 和 2 之间的代码的时候,容器被修改了。

但是,该方法的调用是位于同步容器的同步代码块中的,这里出现同步错误,我首先想到的是如下几个原因:

  • 同步容器的同步没有覆盖所有的方法:基本不可能,标准 JDK 应该还是严谨的 ...

  • 外部通过反射直接调用了同步容器内的真实数据:一般不会有这种骚操作

  • 执行序列化过程的过程跳过了锁:虽然是反射调用,但是代码逻辑的执行是在代码块内部的

  • 执行序列化方法的过程中释放了锁

3、复现

带着上述问题,首先还是先复现该问题。

该异常还是比较容易复现,

private static final int TOTAL_TEST_LOOP = 100;private static final int TOTAL_THREAD_COUNT = 20;private static volatile int writeTaskNo = 0;private static final List<String> list = Collections.synchronizedList(new ArrayList<>());private static final Executor executor = Executors.newFixedThreadPool(TOTAL_THREAD_COUNT);public static void main(String...args) throws IOException {    for (int i = 0; i < TOTAL_TEST_LOOP; i++) {        executor.execute(new WriteListTask());        for (int j=0; j<TOTAL_THREAD_COUNT-1; j++) {            executor.execute(new ChangeListTask());        }    }}private static final class ChangeListTask implements Runnable {    @Override    public void run() {        list.add("hello");        System.out.println("change list job done");    }}private static final class WriteListTask implements Runnable {    @Override    public void run() {        File file = new File("temp");        OutputStream os = null;        ObjectOutputStream oos = null;        try {            os = new FileOutputStream(file);            oos = new ObjectOutputStream(os);            oos.writeObject(list);            oos.flush();            os.flush();        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                oos.close();                os.close();            } catch (IOException e) {                e.printStackTrace();            }        }        System.out.println(String.fORMat("write [%d] list job done", ++writeTaskNo));    }}

这里创建了一个容量为 20 的线程池,遍历 100 次循环,每次往线程池添加一个序列化的任务以及 19 个修改列表的操作。

按照上述操作,基本 100% 复现这个问题。

4、解决

如果只是从堆栈看,这个问题非常“诡异”,它看上去是在执行序列化的过程中把线程的锁释放了。所以,为了找到问题的原因我做了几个测试

当然,我首先想到的是解决并发修改的问题,除了使用同步容器,另外一种方式是使用并发容器。ArrayList 对应的并发容器是 CopyOnWriteArrayList。换了该容器之后可以修复这个问题。

此外,我用自定义同步锁的形式在序列化操作的外部对整个序列化过程进行同步,这种方式也可以解决上述问题。

不过,虽然解决了这个问题,此时还存在一个疑问就是序列化过程中锁是如何“丢”了的。为了更好地分析问题,我 Copy 了一份 JDK 的 SynchronizedList源码,并使用 Copy 的代码复现上述问题,试了很多次也没有出现。所以,这成了“看上去一样的代码,但是执行起来结果不同”。感觉非常“诡异”。

最后,我把这个问题放到了 StackOverflow 上面。国外的一个开发者解答了这个问题,

JDK序列化Bug难题如何解决

就是说,

这是 JDK 的一个 bug,并且到 OpenJDK 19.0.2 还没有解决的一个问题。bug 单位于,

bugs.openjdk.org/browse/JDK-&hellip;

这是因为当我们使用 Collections 的方法 synchronizedList 获取同步容器的时候(代码如下),

public static <T> List<T> synchronizedList(List<T> list) {    return (list instanceof RandoMaccess ?            new SynchronizedRandomAccessList<>(list) :            new SynchronizedList<>(list));}

它会根据被包装的容器是否实现了 RandomAccess 接口来判断使用 SynchronizedRandomAccessList 还是 SynchronizedList 进行包装。RandomAccess 的意思是是否可以在任意位置访问列表的元素,显然 ArrayList 实现了这个接口。所以,当我们使用同步容器进行包装的时候,返回的是 SynchronizedRandomAccessList 这个类而不是 SynchronizedList 的实例.

SynchronizedRandomAccessList,它有一个 writeReplace() 方法

private Object writeReplace() {    return new SynchronizedList<>(list);}

这个方法是用来兼容 1.4 之前版本的序列化的,所以,当对 SynchronizedRandomAccessList 执行序列化的时候会先调用 writeReplace() 方法,并将被包装的 list 对象传入,然后使用该方法返回的对象进行序列化而不是原始对象。

对于 SynchronizedRandomAccessList,它是 SynchronizedList 的子类,它们对私有锁的实现机制是相同的,即,两者都是对自身的实例 (也就是 this)进行加锁。所以,两者持有的 ArrayList 是同一实例,但是加锁的却是不同的对象。也就是说,序列化过程中加锁的对象是 writeReplace() 方法创建的 SynchronizedList 的实例,其他线程修改数据时加锁的是 SynchronizedRandomAccessList 的实例。

验证的方式比较简单,在 writeObject() 出打断点获取 this 对象和最初的同步容器返回结果做一个对比即可。

感谢各位的阅读,以上就是“JDK序列化Bug难题如何解决”的内容了,经过本文的学习后,相信大家对JDK序列化Bug难题如何解决这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: JDK序列化Bug难题如何解决

本文链接: https://lsjlt.com/news/351926.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • JDK序列化Bug难题如何解决
    这篇文章主要讲解了“JDK序列化Bug难题如何解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“JDK序列化Bug难题如何解决”吧!1、背景最近查看应用的崩溃记录的时候遇到了一个跟 Java...
    99+
    2023-07-05
  • JDK序列化Bug难题解决示例详解
    目录1、背景2、分析3、复现4、解决总结1、背景 最近查看应用的崩溃记录的时候遇到了一个跟 Java 序列化相关的崩溃, 从崩溃的堆栈来看,整个调用堆栈里没有我们自己的代码信息。...
    99+
    2023-03-19
    JDK序列化Bug解决 JDK序列化
  • Go json反序列化“null“的问题如何解决
    本文小编为大家详细介绍“Go json反序列化“null“的问题如何解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“Go json反序列化“null“的问题如何解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入...
    99+
    2023-07-05
  • SpringBoot之Json的序列化和反序列化问题怎么解决
    这篇文章主要讲解了“SpringBoot之Json的序列化和反序列化问题怎么解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SpringBoot之Json的序列化和反序列化问题怎么解决”吧...
    99+
    2023-07-02
  • CSS解决 DIV 3列居中难题
            在DIV+CSS中浮动功能非常的强大,能实现非常复杂的排版问题,2列居中没问题,但是DIV 3列居中经常出现混乱现象。现在为大家提供解决方法。        布局使用的是 左 -右 -中 的方法 ,在IE和Firefox测试...
    99+
    2023-01-31
    难题 CSS DIV
  • 如何解决使用Jackson反序列化遇到的问题
    这篇文章主要为大家展示了“如何解决使用Jackson反序列化遇到的问题”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何解决使用Jackson反序列化遇到的问题”这篇文章吧。Jackson反序列...
    99+
    2023-06-20
  • 如何解决jackson序列化和feign返回值的问题
    这篇文章给大家分享的是有关如何解决jackson序列化和feign返回值的问题的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。jackson序列化和feign返回值jackson注意点被序列化/反序列化的实体a.必...
    99+
    2023-06-29
  • redis反序列化报错如何解决
    这篇文章主要介绍“redis反序列化报错如何解决”,在日常操作中,相信很多人在redis反序列化报错如何解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”redis反序列化报错如何解决”的疑惑有所帮助!接下来...
    99+
    2023-07-05
  • Gojson反序列化“null“的问题解决
    目录实验其他测试有这么一段代码,可以先看一下有没有什么问题,作用是输入一段json字符串,反序列化成map,然后将另一个inputMap的内容,merge进这个map func me...
    99+
    2023-03-14
    Go json反序列化 Go json反序列化null
  • Vue JSON序列化问题怎么解决
    今天小编给大家分享一下Vue JSON序列化问题怎么解决的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。首先,我们需要了解常见...
    99+
    2023-07-06
  • 如何理解序列化
    本篇内容介绍了“如何理解序列化”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! 本文主要内容背景在...
    99+
    2024-04-02
  • 如何解决JSON反序列化Long变Integer或Double的问题
    这篇文章主要为大家展示了“如何解决JSON反序列化Long变Integer或Double的问题”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何解决JSON反序列化Long变Integer或Do...
    99+
    2023-06-26
  • 如何解决React.memo引起的bug问题
    这篇文章主要介绍如何解决React.memo引起的bug问题,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!与PureComponent不同的是PureComponent只是进行浅对比props来决定是否跳过更新数据这...
    99+
    2023-06-29
  • SpringBoot下Redis序列化乱码如何解决
    本篇内容主要讲解“SpringBoot下Redis序列化乱码如何解决”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“SpringBoot下Redis序列化乱码如何解决”吧!SpringBoot下R...
    99+
    2023-07-02
  • @NonNull导致无法序列化如何解决
    这篇文章主要介绍“@NonNull导致无法序列化如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“@NonNull导致无法序列化如何解决”文章能帮助大家解决问题。@NonNull导致无法序列化的...
    99+
    2023-07-04
  • 如何用Go语言解决LeetCode难题,优化HTTP请求?
    在当今的编程领域中,LeetCode是一个非常受欢迎的网站,它提供了大量的编程难题,帮助程序员们锻炼编程能力。而在实际开发中,我们经常会遇到需要优化HTTP请求的情况。那么,如何使用Go语言来解决这些问题呢?下面我们来详细了解一下。 使用...
    99+
    2023-11-02
    leetcode npm http
  • 解决json字符串序列化后的顺序问题
    1、应用场景: 如果项目中用到json字符串转为jsonObject的需求,并且,需要保证字符串的顺序转之前和转成jsonObject之后输出的结果完全一致。可能有点绕口,下面举一个...
    99+
    2024-04-02
  • redis反序列化对象失败如何解决
    在Redis中存储的数据是经过序列化的,通常使用的是JSON、MessagePack等格式。如果反序列化对象失败,可能是因为序列化和...
    99+
    2024-04-09
    redis
  • 如何解决因@click.stop引发的bug问题
    这篇文章将为大家详细讲解有关如何解决因@click.stop引发的bug问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。问题在项目页面中使用 element popov...
    99+
    2024-04-02
  • 解决SpringBoot下Redis序列化乱码的问题
    目录SpringBoot下Redis序列化乱码注意问题SpringBoot配置Redis序列化规则,防止乱码下面我们可以编写测试类了具体可以看下图我们需要对它进行配置SpringBo...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作