為什么我的對象被 IDEA 悄悄的修改了?


背景

    最近,在復習JUC的時候調試了一把ConcurrentLinkedQueue的offer方法,意外的發現Idea在debug模式下竟然會 “自動修改” 已經創建的Java對象,當時覺得這個現象很是奇怪,現在把問題的原因以及解決過程記錄下來,希望你在調試的時候不要踩坑。

調試代碼

    調試的代碼很簡單,就是多次調用offer方法,然后觀察ConcurrentLinkedQueue的headtail屬性。

import java.lang.reflect.Field;
import java.util.concurrent.ConcurrentLinkedQueue;

public class ConcurrentLinkedQueueTest {

    public static void main(String[] args) {
        ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
        print(queue);
        queue.offer("aaa");
        print(queue);
        queue.offer("bbb");
        print(queue);
        queue.offer("ccc");
        print(queue);
    }

    /**
     * 打印並發隊列head屬性的identityHashCode
     * @param queue
     */
    private static void print(ConcurrentLinkedQueue queue) {
        Field field = null;
        boolean isAccessible = false;
        try {
            field = ConcurrentLinkedQueue.class.getDeclaredField("head");
            isAccessible = field.isAccessible();
            if (!isAccessible) {
                field.setAccessible(true);
            }
            System.out.println("head: " + System.identityHashCode(field.get(queue)));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            field.setAccessible(isAccessible);
        }
    }
}

調試過程

    上述代碼在Idea中debug模式下head屬性會無緣無故的被修改(run模式下正常,debug模式下關閉所有斷點也正常),檢查ConcurrentLinkedQueue的源碼發現,head屬性只有在構造器和反序列化的readObject共3處地方才會被直接賦值(不是cas修改),我也仔細檢查了offer方法,確實沒有修改head的地方。而Idea在debug時,把head屬性修改為第一次offer的Node節點,這個現象就很奇怪了。

  • 在run模式下的輸出結果,多次調用offer方法,head屬性都是同一個對象(debug模式下關閉所有斷點也是同樣的效果)

  • 在offer方法中斷點,然后debug並單步調試(Step over)

  • 在offer方法中斷點,然后debug並直接運行到下一個斷點(Resume program)

    由上可見,在debug進入offer方法之后head屬性確實被修改了(對象已經不是同一個),而且這不是偶爾出現,而是一直可以復現的,Step over和Resume program也表現出了修改head屬性不同的時機,這讓人很費解。 更費解的是就算不在offer方法體里斷點,在main方法中斷點也會出現head被修改的現象。

  • 轉戰到Eclipse,同樣的環境,同樣的操作,在run和debug模式下都不會出現head被修改的情況

分析

    了解到Idea在debug模式下默認開啟了toString預覽特性(Settings>>Build,Execution,Deployment>>Debugger>>Data Views>>Java>>Enable 'toString()' object view),可是調用toString方法也不至於把對象本身都修改了啊,也專門看了下ConcurrentLinkedQueue的內部類Node,並沒有復寫toString方法(事后回顧,當時在這里疏忽了,下文會再介紹),但還是關掉特性再測試一遍,然而還是同樣的結果,head屬性任然被悄悄的修改了。第二天來到公司在同事的環境(IntelliJ IDEA 2019.1)上驗證了下,還是同樣的問題,排除Idea版本的因素。

    郁悶了一會兒,就向"網友"提問了鏈接,不久就得到了IntelliJ IDEA的產品經理yole的回復,他的意思還是Idea 的 Data Views 的 toString 在作怪,上文已經說過關掉toString特性還是有這個問題,但是他給了我一個重要的思路就是:在debug模式下,ConcurrentLinkedQueue的對象也會被調用toString方法的,在隊列的toString方法中會獲取隊列的迭代器,而創建迭代器時會調用first方法,first方法里就會cas修改head屬性。(之前確實沒考慮到隊列本身的toString方法,而是去看Node是否重寫了toString,手動哭臉😭)
    這里需要注意的是,盡管關掉toString特性上面問題還是存在,原因就在於ConcurrentLinkedQueue是一個Collection,Data Views 還有一個選項“Enable alternative view for Collections classes” (平時沒注意...)所以也會在debug時造成隊列迭代器的遍歷。把這個特性也一並關掉,則上面的問題就不會再出現了。

總結

    之前看到有網友在調試低版本的fastjson的反序列化時也遇到過Idea toString的問題,雖然這可能不會影響程序的正常執行,但是作為開發人員,在debug時完全可能會遇到這種被Idea挖坑的情況。對於Data views特性我目前也持保留態度,如果不是特別依賴就直接關掉吧。

參考:
https://blog.csdn.net/dajiangqingzhou/article/details/78676459
https://www.cnblogs.com/oldtrafford/p/8612089.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM