开发者社区 > 博文 > 记录工作以来遇到的最离谱的一个Bug
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

记录工作以来遇到的最离谱的一个Bug

  • yt****
  • 2024-04-24
  • IP归属:北京
  • 100浏览

    一、一次离谱的log记录

    相关的代码,可以简化成如下所示:

    Thread t = new Thread(() -> {
                Integer a = 0;
                while (true) {
                    System.out.println(a);
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (Throwable ignore) {
                    }
                }
            });
    t.start();

    简单来说,线程内有一个Integer类型的变量a,然后每隔1s,打印一下这个变量a的值。那么不出意外的话,这段代码的结果应该每秒打印一个0,但是不出意外的话,意外出现了。

    0
    0
    0
    0
    0
    1
    1
    1
    1

    在中间某一个时刻,突然打印出来变量的值变成了1,当时看到结果,我人都裂开了😢。裂开的原因如下:

    • 线程内部完全没有修改这个变量的操作
    • 这个变量是在方法内部创建的,并没有暴露给外部类

    二、一次更离谱的定位问题过程

    先从一个IDEA的智能提示开始,对于下面这个方法定义:

    public static void hahaha(Integer s){
    }

    如果我们在调用的时候这么写:

    hahaha(new Integer(0));

    此时,IDEA会机智的提示你:

    如果你按照IDEA的指示,移除"不必要的装箱",那么就会变成下面这种调用方式:

    hahaha(0);

    这么一看,好像也没什么问题对吧?我理解也是,IDEA真智能👍,我就这么改了。

    接下来回到问题的开始,我发现,我的程序就是调用了一个下面这样的方法后,打印出来的a变量的值就发生了变化

    public native int GetConfFile(String var1, int var2, StringBuffer var4);

    注意:

    1. 这是一个native方法,内部是通过JNI调用的C++的so文件
    2. 通常来说,c++都是通过指针操作,然后返回值也可能放在参数列表中,所以这个var2虽然是传递进去的参数,但同样也是这个方法的返回值

    接下来,我就按照IDEA的指示,这么调用了此方法:

    StringBuffer sb = new StringBuffer();
    int code = ConfigAccess.GetConfFile("myConfigFile", 0, sb); 

    这么一看好像,也没有什么问题,但就是调用了此方法以后,所有值等于0的Integer类,都变成了1!!!!😨

    三、原来是这样!

    《吃瓜吃到八股文头上是一种怎样的体验?》

    四、我们简单用Java模拟复现一下

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
            Thread t = new Thread(() -> {
                Integer a = 0;
                while (true) {
                    System.out.println(a);
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (Throwable ignore) {
                    }
                }
            });
            t.start();
            TimeUnit.SECONDS.sleep(5);
            Integer b = 0;
            // 获取Unsafe的实例
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            // 获取对象的字段
            Field field = Integer.class.getDeclaredField("value");
            // 计算字段在对象中的偏移量
            long offset = unsafe.objectFieldOffset(field);
            // 修改字段的值
            unsafe.putInt(b, offset, 1);
            System.out.println("+++++" + unsafe.getInt(b, offset));
        }

    见证奇迹的时刻到了

    0
    0
    0
    0
    0
    +++++1 //此时修改了内存中的值
    1
    1
    1
    1
    1

    五、小结

    1. 八股文有些时候还是有用的
    2. Java和C++打交道真的要谨慎、谨慎再谨慎,各种离奇的bug都能出现
    3. 当我循环打印一个没有任何改变的变量却发现内容变化时,我整个人真的裂开了😑
    文章数
    1
    阅读量
    0

    作者其他文章