從o.getUuid() == "-1" Bug聊下JVM內存模型

最近測試自己的應用時發現了一個bug,這個bug就是用==去比較了字符串,我可真是憨批

丟錯誤代碼

即使要被罵憨批,還是要貼下錯誤代碼的,錯誤代碼如下:

從o.getUuid() ==


從對象中獲取的屬性直接用 == 和字符串常量進行比較。好歹也是寫了多年Java的人,真丟人


從o.getUuid() ==


為什麼不能用 == 比較就得從JVM內存模型先講起

JVM內存分配


從o.getUuid() ==


程序計數器 當前線程所執行的字節碼的行號指示器,字節碼解釋器工作時通過改變這個計數器的值來選取下一條需要執行的字節碼,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成

java虛擬機棧 描述的是java方法執行的內存模型:每個方法在執行時會創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接、方法出口信息等信息。每個 方法從調用到執行完成的過程,都對應一個棧幀在虛擬機棧中從入棧到出棧的過程,如下java棧模型

從o.getUuid() ==


  • 局部變量表 存儲方法中的局部變量(包括在方法中聲明的非靜態變量以及函數形參)。對於基本數據類型的變量,則直接存儲它的值,對於引用類型的變量,則存的是指向對象的引用 局部變量表的大小在編譯器就可以確定其大小了,因此在程序執行期間局部變量表的大小是不會改變的
  • 操作數棧 可以理解為被執行操作的數據,字節碼指令執行時從棧頂讀取操作的數據。舉個例子,當整數加法的字節碼指令iadd在運行的時候操作數棧中最接近棧頂的兩個元素已經存入了兩個int型的數值,當執行這個指令時,會將這兩個int值出棧並相加,然後將相加的結果存入棧中
  • 指向運行時常量池的引用 方法執行的過程中有可能需要用到類中的常量,而常量存儲在運行時常量池,因此需要有一個引用指向運行時常量
  • 方法返回地址 當一個方法執行完畢之後或遇到異常,要返回之前調用它的地方,因此在棧幀中必須保存一個方法返回地址

本地方法棧 和java虛擬機棧類似,只不過其存儲的是native方法執行的棧幀

幾乎所有的對象實例都在這裡分配內存 java堆可以分為新生代和老年代,新生代又可以繼續分為Eden空間和Survivor空間 可以處於物理上不連續的內存空間中,只要邏輯上是連續的即可

方法區 存儲已經被虛擬機加載的類信息(包括類的名稱、方法信息、字段信息)、常量、靜態變量、即時編譯器編譯後的代碼等數據

運行時常量池 在Class文件中除了類的字段、方法、接口等描述信息外,還有一項信息是常量池,用來存儲編譯期間生成的字面量和符號引用。 在方法區中有一個非常重要的部分就是運行時常量池,它是每一個類或接口的常量池的運行時表示形式,在類和接口被加載到JVM後,對應的運行時常量池就被創建出來。當然並非Class文件常量池中的內容才能進入運行時常量池,在運行期間也可將新的常量放入運行時常量池中,比如String的intern方法

Bug分析

1. ==比較的是什麼: java中==比較的是內存地址,而equals比較的是值

2. 錯誤代碼JVM分佈:

  • dataSetListVO 這是一個方法對象引用,是指向對象起始地址的引用指針。該對象的實例在堆中分配,因此引用指針指向的是堆區
  • “-1” -1是字面量,字面量會被分配在運行時常量池中,即方法區
  • JVM對象分配圖 可以看到,-1和uuid根本不在同一個內存地址, 因此dataSetListVO.getUuid == “-1”永遠會返回false


作者:何甜甜在嗎
鏈接:https://juejin.im/post/5e70bddb6fb9a07c9d6fbe36


分享到:


相關文章: